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;