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:
parent
8032441b23
commit
b240ed9a6b
9 changed files with 158 additions and 16 deletions
|
|
@ -10,5 +10,5 @@
|
||||||
"streak_tage": 1,
|
"streak_tage": 1,
|
||||||
"letzter_aktiver_tag": "2026-08-29",
|
"letzter_aktiver_tag": "2026-08-29",
|
||||||
"historie": [],
|
"historie": [],
|
||||||
"zuletzt_aktualisiert": "2026-08-29T21:02:12.071385"
|
"zuletzt_aktualisiert": "2026-08-29T21:21:16.433076"
|
||||||
}
|
}
|
||||||
5
api.php
5
api.php
|
|
@ -213,6 +213,11 @@ if ($path === '/progress/save') {
|
||||||
if (!$user) { http_response_code(401); echo json_encode(['error' => 'Nicht angemeldet']); exit; }
|
if (!$user) { http_response_code(401); echo json_encode(['error' => 'Nicht angemeldet']); exit; }
|
||||||
|
|
||||||
$chapter_id = substr(trim((string)($body['chapter_id'] ?? '')), 0, 120);
|
$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);
|
$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");
|
$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
103
assets/code_guard.js
Normal 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
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -11,7 +11,7 @@ window.GAMIFICATION_DATA = {
|
||||||
"streak_tage": 1,
|
"streak_tage": 1,
|
||||||
"letzter_aktiver_tag": "2026-08-29",
|
"letzter_aktiver_tag": "2026-08-29",
|
||||||
"historie": [],
|
"historie": [],
|
||||||
"zuletzt_aktualisiert": "2026-08-29T21:02:12.071385"
|
"zuletzt_aktualisiert": "2026-08-29T21:21:16.433076"
|
||||||
};
|
};
|
||||||
window.GAMIFICATION_BADGES_KATALOG = {
|
window.GAMIFICATION_BADGES_KATALOG = {
|
||||||
"rechenkonegin": {
|
"rechenkonegin": {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
* ⚡ GEHÄRTETE MONACO WEB-IDE & PYODIDE RUNNER ENGINE ⚡
|
* ⚡ GEHÄRTETE MONACO WEB-IDE & PYODIDE RUNNER ENGINE ⚡
|
||||||
* =====================================================
|
* =====================================================
|
||||||
* Interaktive Entwicklungsumgebung mit:
|
* Interaktive Entwicklungsumgebung mit:
|
||||||
|
* - Integriertem CODE_GUARD Sicherheits-Filter (Schadcode- & Exploit-Sperre)
|
||||||
* - Monaco Editor (VS Code Engine mit Autocomplete & Syntax-Highlighting)
|
* - Monaco Editor (VS Code Engine mit Autocomplete & Syntax-Highlighting)
|
||||||
* - Tastatur-Shortcuts: Strg+Enter (Run), Strg+Shift+Enter (Test), Strg+S (Save)
|
* - Tastatur-Shortcuts: Strg+Enter (Run), Strg+Shift+Enter (Test), Strg+S (Save)
|
||||||
* - In-Browser Pyodide WebAssembly Python Runner (95% Client-Execution)
|
* - In-Browser Pyodide WebAssembly Python Runner (95% Client-Execution)
|
||||||
|
|
@ -72,12 +73,10 @@
|
||||||
bracketPairColorization: { enabled: true }
|
bracketPairColorization: { enabled: true }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto-Save beim Tippen
|
|
||||||
monacoEditor.onDidChangeModelContent(() => {
|
monacoEditor.onDidChangeModelContent(() => {
|
||||||
triggerAutoSave();
|
triggerAutoSave();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Tastatur-Shortcuts in Monaco
|
|
||||||
monacoEditor.addCommand(window.monaco.KeyMod.CtrlCmd | window.monaco.KeyCode.Enter, () => {
|
monacoEditor.addCommand(window.monaco.KeyMod.CtrlCmd | window.monaco.KeyCode.Enter, () => {
|
||||||
runCode();
|
runCode();
|
||||||
});
|
});
|
||||||
|
|
@ -89,7 +88,6 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Globaler Keydown-Listener für Strg+S
|
|
||||||
window.addEventListener("keydown", (e) => {
|
window.addEventListener("keydown", (e) => {
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === "s") {
|
if ((e.ctrlKey || e.metaKey) && e.key === "s") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -97,7 +95,7 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Pyodide WebAssembly Initialisierung
|
// 2. Pyodide WebAssembly Initialisierung mit Sandbox-Bootstrap
|
||||||
async function getPyodide() {
|
async function getPyodide() {
|
||||||
if (pyodideInstance) return pyodideInstance;
|
if (pyodideInstance) return pyodideInstance;
|
||||||
if (pyodideLoading) {
|
if (pyodideLoading) {
|
||||||
|
|
@ -108,7 +106,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
pyodideLoading = true;
|
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 {
|
try {
|
||||||
if (!window.loadPyodide) {
|
if (!window.loadPyodide) {
|
||||||
|
|
@ -126,26 +124,40 @@
|
||||||
stderr: (text) => logToTerminal(text + "\n", "stderr")
|
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) {
|
} catch (err) {
|
||||||
logToTerminal(`❌ Fehler beim Laden von Pyodide: ${err.message}\n`, "error");
|
logToTerminal(`❌ Fehler beim Laden der Sandbox: ${err.message}\n`, "error");
|
||||||
} finally {
|
} finally {
|
||||||
pyodideLoading = false;
|
pyodideLoading = false;
|
||||||
}
|
}
|
||||||
return pyodideInstance;
|
return pyodideInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Code-Ausführung mit Timeout-Schutz (5s)
|
// 3. Code-Ausführung mit Sicherheitsprüfung & Timeout
|
||||||
async function runCode() {
|
async function runCode() {
|
||||||
const code = getEditorCode();
|
const code = getEditorCode();
|
||||||
clearTerminal();
|
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");
|
logToTerminal("▶ Starte Python-Skript (Strg+Enter)...\n----------------------------------------\n", "info");
|
||||||
|
|
||||||
const py = await getPyodide();
|
const py = await getPyodide();
|
||||||
if (!py) return;
|
if (!py) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Timeout-Rennen
|
|
||||||
let timeoutHandle;
|
let timeoutHandle;
|
||||||
const timeoutPromise = new Promise((_, reject) => {
|
const timeoutPromise = new Promise((_, reject) => {
|
||||||
timeoutHandle = setTimeout(() => {
|
timeoutHandle = setTimeout(() => {
|
||||||
|
|
@ -164,7 +176,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Unittest-Ausführung (Test Runner)
|
// 4. Unittest-Ausführung mit Sicherheitsprüfung
|
||||||
async function runTests() {
|
async function runTests() {
|
||||||
if (!currentChapterData || !currentChapterData.testCode) {
|
if (!currentChapterData || !currentChapterData.testCode) {
|
||||||
logToTerminal("❌ Keine Unittests für dieses Kapitel gefunden.\n", "error");
|
logToTerminal("❌ Keine Unittests für dieses Kapitel gefunden.\n", "error");
|
||||||
|
|
@ -173,6 +185,16 @@
|
||||||
|
|
||||||
const userCode = getEditorCode();
|
const userCode = getEditorCode();
|
||||||
clearTerminal();
|
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");
|
logToTerminal("🧪 Führe automatisierte Unittests aus (Strg+Shift+Enter)...\n----------------------------------------\n", "info");
|
||||||
|
|
||||||
const py = await getPyodide();
|
const py = await getPyodide();
|
||||||
|
|
@ -312,12 +334,10 @@ errors = len(result.errors)
|
||||||
async function recordChapterSolved() {
|
async function recordChapterSolved() {
|
||||||
if (!currentChapterData || !currentChapterData.chapterId) return;
|
if (!currentChapterData || !currentChapterData.chapterId) return;
|
||||||
|
|
||||||
// Lokale Gamification
|
|
||||||
if (window.addXP) {
|
if (window.addXP) {
|
||||||
window.addXP(100, currentChapterData.chapterId);
|
window.addXP(100, currentChapterData.chapterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backend Cloud
|
|
||||||
const token = localStorage.getItem("auth_token");
|
const token = localStorage.getItem("auth_token");
|
||||||
if (token) {
|
if (token) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -102,10 +102,22 @@
|
||||||
activeOutputEl = output;
|
activeOutputEl = output;
|
||||||
|
|
||||||
const code = editor.value;
|
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();
|
const pyodide = await getPyodide();
|
||||||
|
|
||||||
if (pyodide) {
|
if (pyodide) {
|
||||||
try {
|
try {
|
||||||
|
if (window.CODE_GUARD && window.CODE_GUARD.getSandboxBootstrap) {
|
||||||
|
await pyodide.runPythonAsync(window.CODE_GUARD.getSandboxBootstrap());
|
||||||
|
}
|
||||||
output.innerHTML = "";
|
output.innerHTML = "";
|
||||||
await pyodide.runPythonAsync(code);
|
await pyodide.runPythonAsync(code);
|
||||||
if (output.innerHTML === "") {
|
if (output.innerHTML === "") {
|
||||||
|
|
|
||||||
|
|
@ -951,6 +951,7 @@ print(mein_auto.hupen())
|
||||||
<script src="assets/interpreter.js"></script>
|
<script src="assets/interpreter.js"></script>
|
||||||
<script src="assets/certificate.js"></script>
|
<script src="assets/certificate.js"></script>
|
||||||
<script src="assets/parsons.js"></script>
|
<script src="assets/parsons.js"></script>
|
||||||
|
<script src="assets/code_guard.js"></script>
|
||||||
<script src="assets/runner.js"></script>
|
<script src="assets/runner.js"></script>
|
||||||
<script src="assets/ui_enhancements.js"></script>
|
<script src="assets/ui_enhancements.js"></script>
|
||||||
<script src="assets/app_auth.js"></script>
|
<script src="assets/app_auth.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -507,8 +507,8 @@ class PlatformRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||||
code_draft = str(body.get("code_draft", ""))[:100_000]
|
code_draft = str(body.get("code_draft", ""))[:100_000]
|
||||||
subgoals_json = json.dumps(body.get("subgoals", {}))[:10_000]
|
subgoals_json = json.dumps(body.get("subgoals", {}))[:10_000]
|
||||||
|
|
||||||
if not chapter_id:
|
if not chapter_id or ".." in chapter_id or "\\" in chapter_id:
|
||||||
return self.send_json({"error": "chapter_id erforderlich"}, 400)
|
return self.send_json({"error": "Ungültiges chapter_id"}, 400)
|
||||||
|
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
conn.execute("""
|
conn.execute("""
|
||||||
|
|
|
||||||
|
|
@ -327,6 +327,7 @@
|
||||||
<script src="assets/interpreter.js"></script>
|
<script src="assets/interpreter.js"></script>
|
||||||
<script src="assets/certificate.js"></script>
|
<script src="assets/certificate.js"></script>
|
||||||
<script src="assets/parsons.js"></script>
|
<script src="assets/parsons.js"></script>
|
||||||
|
<script src="assets/code_guard.js"></script>
|
||||||
<script src="assets/monaco_ide.js"></script>
|
<script src="assets/monaco_ide.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue