Security-Hardening Runde 14: Factory-Reset, Settings-Whitelist, Prisma-Leak, XSS-Strip
Pentest Runde 11:
C2 KRITISCH – Factory Reset ohne Bestätigung:
Eingeloggter Admin konnte mit leerem oder beliebigem Body die DB
plätten (3× in einer Pentest-Session passiert). Server erzwingt jetzt
confirm:"FACTORY-RESET-BESTAETIGT" als String. Frontend-API sendet
den Wert automatisch mit.
M1 – Settings Mass Assignment:
PUT /api/settings akzeptierte beliebige Keys (superAdminEmail,
debugMode, allowedOrigins). Neue Whitelist ALLOWED_SETTING_KEYS in
appSetting.service.ts; updateSetting + updateSettings prüfen jeden
Key, unbekannte → 400.
M3 – Prisma-Error-Leak:
Statt 30+ Controller einzeln zu fixen, globaler res.json()-Wrapper
unter /api: error/details-Strings werden durch Pattern-Filter
geschickt, der ORM-/Stack-Trace-Muster zu "Operation fehlgeschlagen"
ersetzt. Original bleibt im Server-Log.
M2 – Stored XSS in Customer/User-Strings:
Neuer stripHtml()-Helper. pickCustomerUpdate/Create + pickUserUpdate/
Create rufen ihn auf jeden String-Wert. Defense-in-Depth gegen PDF/
E-Mail-Template-XSS-Vektoren – React-Frontend ist eh auto-escaped.
Live-verifiziert:
- factory-reset {} / {confirm:true} / {confirm:false} → 400, DB ok
- PUT /settings {superAdminEmail,...} → 400 + Keys aufgezählt;
PUT /settings {customerSupportTicketsEnabled:"true"} → 200
- PUT /users/99999 → "Operation fehlgeschlagen" (vorher Prisma-Stack)
- PUT /customers/3 {companyName:"<script>...</script>EvilCorp"} →
gespeichert als "EvilCorp"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -262,6 +262,44 @@ app.use('/api', (_req, res, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
// Globaler Sanitizer für Fehler-Antworten: bekannte ORM-/Stack-Trace-Muster
|
||||
// in `error`/`details`-Strings ersetzen, bevor sie an den Client gehen.
|
||||
// So leakten frühere Builds bei z.B. `PUT /api/users/99999` rohe
|
||||
// Prisma-Internals wie "Invalid `prisma.user.update()` invocation:
|
||||
// Record to update not found" (Pentest Runde 11 M3). Der Original-Text
|
||||
// landet weiterhin im Server-Log.
|
||||
const ORM_LEAK_PATTERNS: RegExp[] = [
|
||||
/Invalid `prisma\./i,
|
||||
/PrismaClient/i,
|
||||
/^\s*at\s+[A-Za-z]+\s+\(/m, // Stack-Frame
|
||||
/at\s+[A-Za-z][\w.<>]*\s*\([^)]*:\d+:\d+\)/, // file:line:col
|
||||
];
|
||||
function sanitizeErrorString(s: string): string {
|
||||
if (!s) return s;
|
||||
for (const re of ORM_LEAK_PATTERNS) {
|
||||
if (re.test(s)) {
|
||||
console.error('[orm-leak-guard] Maskierte Fehlermeldung:', s.slice(0, 300));
|
||||
return 'Operation fehlgeschlagen';
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
app.use('/api', (_req, res, next) => {
|
||||
const originalJson = res.json.bind(res);
|
||||
res.json = (body: any) => {
|
||||
if (body && typeof body === 'object') {
|
||||
if (typeof body.error === 'string') {
|
||||
body.error = sanitizeErrorString(body.error);
|
||||
}
|
||||
if (typeof body.details === 'string') {
|
||||
body.details = sanitizeErrorString(body.details);
|
||||
}
|
||||
}
|
||||
return originalJson(body);
|
||||
};
|
||||
next();
|
||||
});
|
||||
|
||||
// Numerische ID-Parameter strikt validieren. parseInt('6abc') liefert 6, was
|
||||
// dazu führt, dass `/api/customers/6abc` als `/api/customers/6` interpretiert
|
||||
// wurde – kein Auth-Bypass (Prisma fängt SQL-Injection), aber fehlende Input-
|
||||
|
||||
Reference in New Issue
Block a user