375 lines
14 KiB
JavaScript
375 lines
14 KiB
JavaScript
const LessonModule = require('../base/LessonModule');
|
|
const progressQueries = require('../../../src/models/queries/progress.queries');
|
|
|
|
/**
|
|
* Beginner-Friendly SQL Injection Shop Lesson
|
|
* Simplified to 3 progressive challenges with helpful hints
|
|
*
|
|
* Activity data structure:
|
|
* {
|
|
* discoveries: ['GENERIC', 'BYPASS_FILTER', 'UNION_SELECT'],
|
|
* timerStart: timestamp,
|
|
* unionHintShown: boolean
|
|
* }
|
|
*/
|
|
class SQLInjectionShopLesson extends LessonModule {
|
|
constructor(config) {
|
|
super(config);
|
|
}
|
|
|
|
/**
|
|
* Get activity data from database
|
|
*/
|
|
async _getActivityData(participantId, eventLessonId) {
|
|
try {
|
|
const data = await progressQueries.getActivityData(participantId, eventLessonId);
|
|
console.log(`[SQL Injection] Loading activity data for participant ${participantId}, event ${eventLessonId}:`, data);
|
|
return {
|
|
discoveries: data.discoveries || [],
|
|
timerStart: data.timerStart || null,
|
|
unionHintShown: data.unionHintShown || false
|
|
};
|
|
} catch (error) {
|
|
console.error('[SQL Injection] Error loading activity data:', error);
|
|
return {
|
|
discoveries: [],
|
|
timerStart: null,
|
|
unionHintShown: false
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save activity data to database
|
|
*/
|
|
async _saveActivityData(participantId, eventLessonId, activityData) {
|
|
try {
|
|
console.log(`[SQL Injection] Saving activity data for participant ${participantId}, event ${eventLessonId}:`, activityData);
|
|
await progressQueries.updateActivityData(participantId, eventLessonId, activityData);
|
|
console.log('[SQL Injection] Activity data saved successfully');
|
|
} catch (error) {
|
|
console.error('[SQL Injection] Error saving activity data:', error);
|
|
throw error; // Re-throw to see the error in the main flow
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Start challenge timer
|
|
*/
|
|
async startTimer(participantId, eventLessonId) {
|
|
const activityData = await this._getActivityData(participantId, eventLessonId);
|
|
|
|
if (!activityData.timerStart) {
|
|
activityData.timerStart = Date.now();
|
|
await this._saveActivityData(participantId, eventLessonId, activityData);
|
|
}
|
|
|
|
const discoveries = new Set(activityData.discoveries);
|
|
const totalDiscoveries = 3;
|
|
|
|
return {
|
|
started: true,
|
|
duration: 600, // 10 minutes in seconds
|
|
message: 'Timer gestartet! Du hast 10 Minuten Zeit.',
|
|
discoveries: {
|
|
found: discoveries.size,
|
|
total: totalDiscoveries,
|
|
types: Array.from(discoveries)
|
|
},
|
|
unionHintShown: activityData.unionHintShown
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get elapsed time for participant
|
|
*/
|
|
async _getElapsedTime(participantId, eventLessonId) {
|
|
const activityData = await this._getActivityData(participantId, eventLessonId);
|
|
const start = activityData.timerStart;
|
|
if (!start) return 0;
|
|
return Math.floor((Date.now() - start) / 1000);
|
|
}
|
|
|
|
// Mock database with products
|
|
getMockDatabase() {
|
|
return {
|
|
products: [
|
|
{ id: 1, name: 'Laptop Pro 15', price: 1299.99, category: 'Electronics', stock: 15 },
|
|
{ id: 2, name: 'Wireless Mouse', price: 29.99, category: 'Accessories', stock: 50 },
|
|
{ id: 3, name: 'USB-C Cable', price: 12.99, category: 'Accessories', stock: 100 },
|
|
{ id: 4, name: 'Gaming Keyboard', price: 89.99, category: 'Electronics', stock: 25 },
|
|
{ id: 5, name: 'Monitor 27"', price: 349.99, category: 'Electronics', stock: 20 }
|
|
],
|
|
users: [
|
|
{ id: 1, username: 'admin', password: 'hashed_admin_password', role: 'admin' },
|
|
{ id: 2, username: 'john_doe', password: 'hashed_user_password', role: 'customer' },
|
|
{ id: 3, username: 'jane_smith', password: 'hashed_user_password', role: 'customer' }
|
|
]
|
|
};
|
|
}
|
|
|
|
// Simulate vulnerable SQL query
|
|
async executeVulnerableQuery(searchTerm, participantId, eventLessonId) {
|
|
const db = this.getMockDatabase();
|
|
|
|
// Build the "vulnerable" query string for educational display
|
|
const vulnerableQuery = `SELECT * FROM products WHERE name LIKE '%${searchTerm}%'`;
|
|
|
|
// Detect SQL injection attempts
|
|
const injectionDetected = this.detectInjection(searchTerm);
|
|
|
|
let results = [];
|
|
let injectionType = null;
|
|
let explanation = '';
|
|
let pointsAwarded = 0;
|
|
let isNewDiscovery = false;
|
|
let unionHintMessage = null;
|
|
|
|
// Load activity data from database
|
|
const activityData = await this._getActivityData(participantId, eventLessonId);
|
|
const discoveries = new Set(activityData.discoveries);
|
|
console.log(`[SQL Injection] Current discoveries:`, Array.from(discoveries));
|
|
|
|
if (injectionDetected) {
|
|
const injectionInfo = this.analyzeInjection(searchTerm);
|
|
injectionType = injectionInfo.type;
|
|
explanation = injectionInfo.explanation;
|
|
console.log(`[SQL Injection] Injection detected: ${injectionType}`);
|
|
|
|
// Check if this is a new discovery
|
|
isNewDiscovery = !discoveries.has(injectionType);
|
|
console.log(`[SQL Injection] Is new discovery: ${isNewDiscovery}`);
|
|
|
|
// Award points only for NEW discoveries
|
|
if (isNewDiscovery && participantId && eventLessonId) {
|
|
console.log(`[SQL Injection] Awarding points for new discovery: ${injectionType}`);
|
|
const pointsMap = {
|
|
'GENERIC': 30, // Challenge 1: Discovering injection is possible
|
|
'BYPASS_FILTER': 40, // Challenge 2: Showing all products
|
|
'UNION_SELECT': 80 // Challenge 3: Extracting user data (Easter egg!)
|
|
};
|
|
|
|
pointsAwarded = pointsMap[injectionType] || 0;
|
|
|
|
// Time bonus
|
|
const elapsedTime = await this._getElapsedTime(participantId, eventLessonId);
|
|
if (elapsedTime > 0 && elapsedTime < 600) {
|
|
const timeBonus = Math.max(0, Math.floor((600 - elapsedTime) / 60));
|
|
if (timeBonus > 0) {
|
|
pointsAwarded += timeBonus;
|
|
explanation += ` 🎯 Zeit-Bonus: +${timeBonus} Punkte!`;
|
|
}
|
|
}
|
|
|
|
try {
|
|
await this.awardPoints(participantId, eventLessonId, pointsAwarded,
|
|
`SQL Injection discovered: ${injectionType}`);
|
|
|
|
// Mark as discovered and save to database
|
|
discoveries.add(injectionType);
|
|
activityData.discoveries = Array.from(discoveries);
|
|
await this._saveActivityData(participantId, eventLessonId, activityData);
|
|
|
|
// Show UNION hint after completing challenge 2
|
|
if (injectionType === 'BYPASS_FILTER' && !activityData.unionHintShown) {
|
|
activityData.unionHintShown = true;
|
|
await this._saveActivityData(participantId, eventLessonId, activityData);
|
|
|
|
unionHintMessage = {
|
|
title: '🎯 Neue Herausforderung freigeschaltet!',
|
|
content: 'Du kannst jetzt versuchen, Daten aus anderen Tabellen zu extrahieren! Die Datenbank hat eine "users" Tabelle mit den Spalten: id, username, password, role. Verwende UNION SELECT um diese Daten zu kombinieren. Die Anzahl der Spalten muss übereinstimmen (5 Spalten).',
|
|
hint: "Versuche: ' UNION SELECT id, username, password, role, 'X' FROM users--"
|
|
};
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to award SQL injection points:', error);
|
|
}
|
|
}
|
|
|
|
// Simulate different injection results
|
|
if (injectionInfo.type === 'BYPASS_FILTER') {
|
|
// Return all products
|
|
results = db.products;
|
|
} else if (injectionInfo.type === 'UNION_SELECT') {
|
|
// Simulate UNION attack showing user data
|
|
results = [
|
|
{ id: 'USER', name: 'admin', price: 'hashed_admin_password', category: 'admin', stock: 'LEAKED!' },
|
|
{ id: 'USER', name: 'john_doe', price: 'hashed_user_password', category: 'customer', stock: 'LEAKED!' },
|
|
{ id: 'USER', name: 'jane_smith', price: 'hashed_user_password', category: 'customer', stock: 'LEAKED!' }
|
|
];
|
|
} else if (injectionInfo.type === 'GENERIC') {
|
|
// Generic injection - show it affects the query
|
|
results = db.products;
|
|
}
|
|
} else {
|
|
// Normal search - filter products by name
|
|
results = db.products.filter(p =>
|
|
p.name.toLowerCase().includes(searchTerm.toLowerCase())
|
|
);
|
|
}
|
|
|
|
const totalDiscoveries = 3; // Only 3 challenges now
|
|
const elapsedTime = await this._getElapsedTime(participantId, eventLessonId);
|
|
|
|
return {
|
|
query: vulnerableQuery,
|
|
results,
|
|
injectionDetected,
|
|
injectionType,
|
|
explanation,
|
|
recordCount: results.length,
|
|
pointsAwarded: isNewDiscovery ? pointsAwarded : 0,
|
|
isNewDiscovery,
|
|
unionHintMessage,
|
|
discoveries: {
|
|
found: discoveries.size,
|
|
total: totalDiscoveries,
|
|
types: Array.from(discoveries)
|
|
},
|
|
elapsedTime
|
|
};
|
|
}
|
|
|
|
// Detect if input contains SQL injection
|
|
detectInjection(input) {
|
|
const injectionPatterns = [
|
|
/'/, // Single quote
|
|
/union/i, // UNION keyword
|
|
/select/i, // SELECT keyword
|
|
/or\s+['"]?\d+['"]?\s*=\s*['"]?\d+['"]?/i // OR 1=1 pattern
|
|
];
|
|
|
|
return injectionPatterns.some(pattern => pattern.test(input));
|
|
}
|
|
|
|
// Analyze the type of SQL injection
|
|
analyzeInjection(input) {
|
|
const lowerInput = input.toLowerCase();
|
|
|
|
// Challenge 3: UNION SELECT (most advanced)
|
|
// Must include UNION SELECT FROM users/user to be valid
|
|
if (lowerInput.includes('union') && lowerInput.includes('select') &&
|
|
lowerInput.includes('from') && (lowerInput.includes('users') || lowerInput.includes('user'))) {
|
|
return {
|
|
type: 'UNION_SELECT',
|
|
explanation: '🎉 Perfekt! UNION SELECT Injection erfolgreich! Du hast Daten aus der users-Tabelle extrahiert. **Challenge 3 abgeschlossen!** ⭐'
|
|
};
|
|
}
|
|
|
|
// Challenge 2: Bypass filter to show all products
|
|
if (lowerInput.includes("'") && (lowerInput.includes('or') || lowerInput.includes('||'))) {
|
|
if (lowerInput.match(/or\s+['"]?\d+['"]?\s*=\s*['"]?\d+['"]?/)) {
|
|
return {
|
|
type: 'BYPASS_FILTER',
|
|
explanation: "✅ Super! Die Bedingung '1'='1' ist immer wahr und umgeht den Filter. Jetzt werden ALLE Produkte angezeigt. **Challenge 2 abgeschlossen!**"
|
|
};
|
|
}
|
|
}
|
|
|
|
// Challenge 1: Generic injection - just discovered manipulation is possible
|
|
if (lowerInput.includes("'")) {
|
|
return {
|
|
type: 'GENERIC',
|
|
explanation: "🔓 Gut gemacht! Das Anführungszeichen (') zeigt, dass die Abfrage manipuliert werden kann. Du hast entdeckt, dass SQL Injection möglich ist! **Challenge 1 abgeschlossen!**"
|
|
};
|
|
}
|
|
|
|
return {
|
|
type: 'NONE',
|
|
explanation: 'Keine SQL Injection erkannt.'
|
|
};
|
|
}
|
|
|
|
// Demonstrate safe parameterized query
|
|
executeSafeQuery(searchTerm) {
|
|
const db = this.getMockDatabase();
|
|
|
|
const safeQuery = `SELECT * FROM products WHERE name LIKE ?`;
|
|
const parameter = `%${searchTerm}%`;
|
|
|
|
const results = db.products.filter(p =>
|
|
p.name.toLowerCase().includes(searchTerm.toLowerCase())
|
|
);
|
|
|
|
return {
|
|
query: safeQuery,
|
|
parameter,
|
|
results,
|
|
explanation: '✅ Parameterized query verwendet! Benutzereingaben werden als Daten behandelt, nie als SQL-Code. Injection ist unmöglich.',
|
|
recordCount: results.length
|
|
};
|
|
}
|
|
|
|
// Get interactive data for the SQL shop demo
|
|
async getInteractiveData(stepId) {
|
|
if (stepId === 'shop-demo') {
|
|
return {
|
|
timerDuration: 600, // 10 minutes
|
|
totalChallenges: 3, // Simplified to 3 challenges
|
|
database: this.getMockDatabase(),
|
|
challenges: [
|
|
{
|
|
id: 'GENERIC',
|
|
difficulty: 'Anfänger',
|
|
points: 30,
|
|
title: 'SQL Injection entdecken',
|
|
hint: "Versuche ein ' (Anführungszeichen) einzugeben"
|
|
},
|
|
{
|
|
id: 'BYPASS_FILTER',
|
|
difficulty: 'Anfänger',
|
|
points: 40,
|
|
title: 'Filter umgehen (alle Produkte zeigen)',
|
|
hint: "Verwende: ' OR '1'='1"
|
|
},
|
|
{
|
|
id: 'UNION_SELECT',
|
|
difficulty: 'Fortgeschritten',
|
|
points: 80,
|
|
title: 'Benutzerdaten extrahieren',
|
|
hint: 'Wird nach Challenge 2 freigeschaltet',
|
|
isEasterEgg: true
|
|
}
|
|
],
|
|
externalResources: [
|
|
{
|
|
title: 'OWASP SQL Injection',
|
|
url: 'https://owasp.org/www-community/attacks/SQL_Injection',
|
|
type: 'documentation',
|
|
description: 'Grundlagen zu SQL Injection-Angriffen'
|
|
},
|
|
{
|
|
title: 'SQL Tutorial (W3Schools)',
|
|
url: 'https://www.w3schools.com/sql/',
|
|
type: 'tutorial',
|
|
description: 'SQL Grundlagen lernen'
|
|
},
|
|
{
|
|
title: 'SQL Injection Cheat Sheet',
|
|
url: 'https://portswigger.net/web-security/sql-injection/cheat-sheet',
|
|
type: 'reference',
|
|
description: 'Schnellreferenz für SQL Injection'
|
|
}
|
|
],
|
|
schemaInfo: {
|
|
tables: ['products', 'users'],
|
|
productsColumns: ['id', 'name', 'price', 'category', 'stock'],
|
|
usersColumns: ['id', 'username', 'password', 'role']
|
|
},
|
|
initialHint: {
|
|
title: '💡 Einstiegshilfe',
|
|
content: 'Du hast erfahren, dass dieser Shop anfällig für SQL Injection ist. Beginne mit einem einfachen Test: Gib ein Anführungszeichen (\') ein und beobachte was passiert. Dann versuche den Filter zu umgehen.',
|
|
examples: [
|
|
{ label: "Challenge 1", payload: "'", description: "Entdecke die Schwachstelle" },
|
|
{ label: "Challenge 2", payload: "' OR '1'='1", description: "Zeige alle Produkte (Filter umgehen)" }
|
|
]
|
|
}
|
|
};
|
|
}
|
|
|
|
return await super.getInteractiveData(stepId);
|
|
}
|
|
}
|
|
|
|
module.exports = SQLInjectionShopLesson;
|