Add lessons
This commit is contained in:
@@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user