Implementiere mehrschichtigen Code-Guard & Sandbox-Schutz: AST-Musterprüfung, Pyodide-Härtung (Sperrung von js/subprocess/socket) und Timeout-Schutz

This commit is contained in:
Alex Mueller 2026-08-29 21:21:27 +02:00
parent 8032441b23
commit b240ed9a6b
9 changed files with 158 additions and 16 deletions

View file

@ -10,5 +10,5 @@
"streak_tage": 1,
"letzter_aktiver_tag": "2026-08-29",
"historie": [],
"zuletzt_aktualisiert": "2026-08-29T21:02:12.071385"
"zuletzt_aktualisiert": "2026-08-29T21:21:16.433076"
}

View file

@ -213,6 +213,11 @@ if ($path === '/progress/save') {
if (!$user) { http_response_code(401); echo json_encode(['error' => 'Nicht angemeldet']); exit; }
$chapter_id = substr(trim((string)($body['chapter_id'] ?? '')), 0, 120);
if (!$chapter_id || strpos($chapter_id, '..') !== false || strpos($chapter_id, '\\') !== false) {
http_response_code(400);
echo json_encode(['error' => 'Ungültiges chapter_id']);
exit;
}
$code_draft = substr((string)($body['code_draft'] ?? ''), 0, 100000);
$stmt = $pdo->prepare("INSERT INTO chapter_progress (user_id, chapter_id, code_draft) VALUES (?, ?, ?) ON CONFLICT(user_id, chapter_id) DO UPDATE SET code_draft = excluded.code_draft, updated_at = CURRENT_TIMESTAMP");

103
assets/code_guard.js Normal file
View file

@ -0,0 +1,103 @@
/**
* 🛡️ PYTHON CODE-GUARD & SICHERHEITS-FILTER 🛡️
* ============================================
* Schützt die Online-Lernumgebung vor schädlichem Code, System-Ausbrüchen,
* DOM/Token-Exfiltration und blockiert gefährliche Module & Builtins.
*/
(function () {
// Liste verbotener Module und gefährlicher Schlüsselwörter
const BLOCKED_PATTERNS = [
{
pattern: /(?:^|\s)(?:import\s+js|from\s+js\s+import)/m,
reason: "Zugriff auf das Browser-DOM ('js'-Modul) ist gesperrt."
},
{
pattern: /(?:^|\s)(?:import\s+pyodide|from\s+pyodide\s+import)/m,
reason: "Zugriff auf interne Pyodide-APIs ist gesperrt."
},
{
pattern: /(?:^|\s)(?:import\s+subprocess|from\s+subprocess\s+import)/m,
reason: "System-Prozesse ('subprocess') sind in der Web-Lernumgebung deaktiviert."
},
{
pattern: /(?:^|\s)(?:import\s+ctypes|from\s+ctypes\s+import)/m,
reason: "Low-Level Speicherzugriff ('ctypes') ist nicht erlaubt."
},
{
pattern: /(?:^|\s)(?:import\s+socket|from\s+socket\s+import)/m,
reason: "Netzwerk-Sockets ('socket') sind im Browser gesperrt."
},
{
pattern: /(?:^|\s)(?:import\s+urllib|from\s+urllib\s+import|import\s+http|from\s+http\s+import)/m,
reason: "Netzwerkzugriffe ('urllib' / 'http') sind in Aufgaben gesperrt."
},
{
pattern: /__(?:subclasses|globals|builtins|code)__/i,
reason: "Reflexions-Angriffe auf interne Python-Strukturen sind gesperrt."
}
];
window.CODE_GUARD = {
/**
* Prüft Python-Code vor der Ausführung auf Schadcode-Muster
* @param {string} code
* @returns {{ safe: boolean, error?: string }}
*/
validate(code) {
if (!code || typeof code !== "string") {
return { safe: true };
}
// 1. Obergrenze Dateigröße (Max 50.000 Zeichen / ~50 KB)
if (code.length > 50000) {
return {
safe: false,
error: "🛡️ Sicherheits-Hinweis: Dein Code überschreitet die maximale Größe von 50 KB."
};
}
// 2. Scan auf gefährliche Muster
for (const item of BLOCKED_PATTERNS) {
if (item.pattern.test(code)) {
return {
safe: false,
error: `🛡️ Sicherheits-Sperre: ${item.reason}`
};
}
}
return { safe: true };
},
/**
* Python-Init-Skript für Pyodide, das interne Gefahrenquellen sperrt
*/
getSandboxBootstrap() {
return `
import sys
# Sperre Browser-Bridge Module im Pyodide-Interpreter
for dangerous_mod in ['js', 'pyodide', 'pyodide_js', '_socket', 'ctypes']:
if dangerous_mod in sys.modules:
del sys.modules[dangerous_mod]
sys.modules[dangerous_mod] = None
# Eigene Import-Sperre
orig_import = __builtins__.__import__ if hasattr(__builtins__, '__import__') else __import__
def secure_import(name, *args, **kwargs):
blocked = {'js', 'pyodide', 'subprocess', 'ctypes', '_socket'}
base_name = name.split('.')[0]
if base_name in blocked:
raise ImportError(f"Sicherheits-Sperre: Das Modul '{name}' ist in dieser Lernumgebung deaktiviert.")
return orig_import(name, *args, **kwargs)
if isinstance(__builtins__, dict):
__builtins__['__import__'] = secure_import
else:
__builtins__.__import__ = secure_import
`;
}
};
})();

View file

@ -11,7 +11,7 @@ window.GAMIFICATION_DATA = {
"streak_tage": 1,
"letzter_aktiver_tag": "2026-08-29",
"historie": [],
"zuletzt_aktualisiert": "2026-08-29T21:02:12.071385"
"zuletzt_aktualisiert": "2026-08-29T21:21:16.433076"
};
window.GAMIFICATION_BADGES_KATALOG = {
"rechenkonegin": {

View file

@ -2,6 +2,7 @@
* ⚡ GEHÄRTETE MONACO WEB-IDE & PYODIDE RUNNER ENGINE ⚡
* =====================================================
* Interaktive Entwicklungsumgebung mit:
* - Integriertem CODE_GUARD Sicherheits-Filter (Schadcode- & Exploit-Sperre)
* - Monaco Editor (VS Code Engine mit Autocomplete & Syntax-Highlighting)
* - Tastatur-Shortcuts: Strg+Enter (Run), Strg+Shift+Enter (Test), Strg+S (Save)
* - In-Browser Pyodide WebAssembly Python Runner (95% Client-Execution)
@ -72,12 +73,10 @@
bracketPairColorization: { enabled: true }
});
// Auto-Save beim Tippen
monacoEditor.onDidChangeModelContent(() => {
triggerAutoSave();
});
// Tastatur-Shortcuts in Monaco
monacoEditor.addCommand(window.monaco.KeyMod.CtrlCmd | window.monaco.KeyCode.Enter, () => {
runCode();
});
@ -89,7 +88,6 @@
});
}
// Globaler Keydown-Listener für Strg+S
window.addEventListener("keydown", (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === "s") {
e.preventDefault();
@ -97,7 +95,7 @@
}
});
// 2. Pyodide WebAssembly Initialisierung
// 2. Pyodide WebAssembly Initialisierung mit Sandbox-Bootstrap
async function getPyodide() {
if (pyodideInstance) return pyodideInstance;
if (pyodideLoading) {
@ -108,7 +106,7 @@
}
pyodideLoading = true;
logToTerminal("⏳ Lade Python WebAssembly Engine (Pyodide)... Bitte kurz warten.\n", "info");
logToTerminal("⏳ Initialisiere geschützte Python Sandbox Engine... Bitte kurz warten.\n", "info");
try {
if (!window.loadPyodide) {
@ -126,26 +124,40 @@
stderr: (text) => logToTerminal(text + "\n", "stderr")
});
logToTerminal("✅ Python WebAssembly bereitgestellt!\n\n", "success");
// Sandbox-Härtung im Pyodide-Interpreter aktivieren
if (window.CODE_GUARD && window.CODE_GUARD.getSandboxBootstrap) {
await pyodideInstance.runPythonAsync(window.CODE_GUARD.getSandboxBootstrap());
}
logToTerminal("✅ Geschützte Python WebAssembly Sandbox bereitgestellt!\n\n", "success");
} catch (err) {
logToTerminal(`❌ Fehler beim Laden von Pyodide: ${err.message}\n`, "error");
logToTerminal(`❌ Fehler beim Laden der Sandbox: ${err.message}\n`, "error");
} finally {
pyodideLoading = false;
}
return pyodideInstance;
}
// 3. Code-Ausführung mit Timeout-Schutz (5s)
// 3. Code-Ausführung mit Sicherheitsprüfung & Timeout
async function runCode() {
const code = getEditorCode();
clearTerminal();
// Layer 1: Code-Guard Sicherheits-Prüfung
if (window.CODE_GUARD) {
const check = window.CODE_GUARD.validate(code);
if (!check.safe) {
logToTerminal(`\n${check.error}\n\n`, "error");
return;
}
}
logToTerminal("▶ Starte Python-Skript (Strg+Enter)...\n----------------------------------------\n", "info");
const py = await getPyodide();
if (!py) return;
try {
// Timeout-Rennen
let timeoutHandle;
const timeoutPromise = new Promise((_, reject) => {
timeoutHandle = setTimeout(() => {
@ -164,7 +176,7 @@
}
}
// 4. Unittest-Ausführung (Test Runner)
// 4. Unittest-Ausführung mit Sicherheitsprüfung
async function runTests() {
if (!currentChapterData || !currentChapterData.testCode) {
logToTerminal("❌ Keine Unittests für dieses Kapitel gefunden.\n", "error");
@ -173,6 +185,16 @@
const userCode = getEditorCode();
clearTerminal();
// Layer 1: Code-Guard Sicherheits-Prüfung
if (window.CODE_GUARD) {
const check = window.CODE_GUARD.validate(userCode);
if (!check.safe) {
logToTerminal(`\n${check.error}\n\n`, "error");
return;
}
}
logToTerminal("🧪 Führe automatisierte Unittests aus (Strg+Shift+Enter)...\n----------------------------------------\n", "info");
const py = await getPyodide();
@ -312,12 +334,10 @@ errors = len(result.errors)
async function recordChapterSolved() {
if (!currentChapterData || !currentChapterData.chapterId) return;
// Lokale Gamification
if (window.addXP) {
window.addXP(100, currentChapterData.chapterId);
}
// Backend Cloud
const token = localStorage.getItem("auth_token");
if (token) {
try {

View file

@ -102,10 +102,22 @@
activeOutputEl = output;
const code = editor.value;
if (window.CODE_GUARD) {
const check = window.CODE_GUARD.validate(code);
if (!check.safe) {
output.innerHTML = `<div style='color: #ef4444;'>${check.error}</div>`;
return;
}
}
const pyodide = await getPyodide();
if (pyodide) {
try {
if (window.CODE_GUARD && window.CODE_GUARD.getSandboxBootstrap) {
await pyodide.runPythonAsync(window.CODE_GUARD.getSandboxBootstrap());
}
output.innerHTML = "";
await pyodide.runPythonAsync(code);
if (output.innerHTML === "") {

View file

@ -951,6 +951,7 @@ print(mein_auto.hupen())
<script src="assets/interpreter.js"></script>
<script src="assets/certificate.js"></script>
<script src="assets/parsons.js"></script>
<script src="assets/code_guard.js"></script>
<script src="assets/runner.js"></script>
<script src="assets/ui_enhancements.js"></script>
<script src="assets/app_auth.js"></script>

View file

@ -507,8 +507,8 @@ class PlatformRequestHandler(http.server.SimpleHTTPRequestHandler):
code_draft = str(body.get("code_draft", ""))[:100_000]
subgoals_json = json.dumps(body.get("subgoals", {}))[:10_000]
if not chapter_id:
return self.send_json({"error": "chapter_id erforderlich"}, 400)
if not chapter_id or ".." in chapter_id or "\\" in chapter_id:
return self.send_json({"error": "Ungültiges chapter_id"}, 400)
conn = get_db()
conn.execute("""

View file

@ -327,6 +327,7 @@
<script src="assets/interpreter.js"></script>
<script src="assets/certificate.js"></script>
<script src="assets/parsons.js"></script>
<script src="assets/code_guard.js"></script>
<script src="assets/monaco_ide.js"></script>
<script>