cf8c6c84c2
M2-Reste – XSS-Strings + Mass-Assignment-Settings noch in DB:
Idempotentes Cleanup-Script prisma/cleanup-xss-and-mass-assignment.ts.
Strippt HTML aus Customer/User-String-Feldern, entfernt AppSettings
ohne Whitelist-Eintrag. Wird im entrypoint.sh nach Migrations + Seed
einmalig pro Container-Start ausgeführt.
User-Update + password-Feld:
password aus USER_UPDATABLE_FIELDS raus (CREATE behält es), neuer
dedizierter Endpoint POST /api/users/:id/password mit Audit-Log
"Passwort … durch Admin gesetzt" und Komplexitäts-Check.
JS-Runtime-Fehler-Leak:
ORM_LEAK_PATTERNS um TypeError/ReferenceError/SyntaxError/RangeError +
"Cannot read properties of undefined/null" + "is not a function/
defined" erweitert. Greift im globalen res.json()-Wrapper.
POST /contracts substring-Crash:
Controller validiert type/customerId, sonst 400. generateContractNumber
fängt nullish type ab (Fallback "CON").
Seed-Admin-Passwort:
Default "admin" verletzte 12-Zeichen-Policy. Jetzt 16-char
Zufallspasswort (alle 4 Klassen garantiert via Fisher-Yates) oder per
SEED_ADMIN_PASSWORD-ENV überschreibbar. BCRYPT-Cost 12 (war 10).
Passwort wird einmalig in stdout ausgegeben mit Warnung.
AppSettings-Whitelist: companyName + defaultEmailDomain ergänzt
(kamen aus seed.ts, in 1. Whitelist vergessen).
Live-verifiziert:
- POST /contracts {} → 400 "Vertrags-Typ erforderlich" (vorher
TypeError-Stack)
- PUT /users/6 {password:"HackerPW2026!"} → 200 aber Login mit altem
PW geht weiter
- POST /users/6/password mit "kurz" → 400 mit Komplexitäts-Fehlern
- Cleanup-Script: planted XSS bereinigt, hackerSetting+debugMode
entfernt, idempotenter Re-Lauf
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
93 lines
3.0 KiB
TypeScript
93 lines
3.0 KiB
TypeScript
/**
|
|
* Einmal-Bereinigung für Pentest-Reste (Runde 12 / 2026-05-18):
|
|
*
|
|
* 1. HTML-Tags aus Customer/User-Stringfeldern strippen (M2-Stored-XSS-Reste)
|
|
* 2. Unbekannte App-Settings entfernen, die durch Mass-Assignment in die DB
|
|
* gerutscht sind, BEVOR die Whitelist eingezogen wurde (M1-Reste).
|
|
*
|
|
* Idempotent: wenn nichts zu tun ist, ändert sich nichts. Bei Bedarf
|
|
* mehrfach aufrufbar.
|
|
*/
|
|
import prisma from '../src/lib/prisma.js';
|
|
import { stripHtml } from '../src/utils/sanitize.js';
|
|
import { ALLOWED_SETTING_KEYS } from '../src/services/appSetting.service.js';
|
|
|
|
const CUSTOMER_STRING_FIELDS = [
|
|
'salutation', 'firstName', 'lastName', 'companyName',
|
|
'birthPlace', 'email', 'phone', 'mobile',
|
|
'taxNumber', 'commercialRegisterNumber', 'notes',
|
|
];
|
|
|
|
const USER_STRING_FIELDS = [
|
|
'firstName', 'lastName', 'email',
|
|
'whatsappNumber', 'telegramUsername', 'signalNumber',
|
|
];
|
|
|
|
async function cleanupXss() {
|
|
const customers = await prisma.customer.findMany();
|
|
let touched = 0;
|
|
for (const c of customers) {
|
|
const updates: Record<string, string> = {};
|
|
for (const field of CUSTOMER_STRING_FIELDS) {
|
|
const v = (c as any)[field];
|
|
if (typeof v === 'string') {
|
|
const cleaned = stripHtml(v) as string;
|
|
if (cleaned !== v) updates[field] = cleaned;
|
|
}
|
|
}
|
|
if (Object.keys(updates).length > 0) {
|
|
console.log(` Customer #${c.id}: bereinigt:`, Object.keys(updates).join(', '));
|
|
await prisma.customer.update({ where: { id: c.id }, data: updates });
|
|
touched++;
|
|
}
|
|
}
|
|
console.log(` → Customer bereinigt: ${touched}`);
|
|
|
|
const users = await prisma.user.findMany();
|
|
let userTouched = 0;
|
|
for (const u of users) {
|
|
const updates: Record<string, string> = {};
|
|
for (const field of USER_STRING_FIELDS) {
|
|
const v = (u as any)[field];
|
|
if (typeof v === 'string') {
|
|
const cleaned = stripHtml(v) as string;
|
|
if (cleaned !== v) updates[field] = cleaned;
|
|
}
|
|
}
|
|
if (Object.keys(updates).length > 0) {
|
|
console.log(` User #${u.id}: bereinigt:`, Object.keys(updates).join(', '));
|
|
await prisma.user.update({ where: { id: u.id }, data: updates });
|
|
userTouched++;
|
|
}
|
|
}
|
|
console.log(` → User bereinigt: ${userTouched}`);
|
|
}
|
|
|
|
async function cleanupAppSettings() {
|
|
const settings = await prisma.appSetting.findMany();
|
|
const removed: string[] = [];
|
|
for (const s of settings) {
|
|
if (!ALLOWED_SETTING_KEYS.has(s.key)) {
|
|
removed.push(s.key);
|
|
await prisma.appSetting.delete({ where: { key: s.key } });
|
|
}
|
|
}
|
|
console.log(` → AppSettings entfernt: ${removed.length}${removed.length ? ' (' + removed.join(', ') + ')' : ''}`);
|
|
}
|
|
|
|
async function main() {
|
|
console.log('=== Cleanup: XSS-Reste + Mass-Assignment-AppSettings ===');
|
|
await cleanupXss();
|
|
await cleanupAppSettings();
|
|
console.log('=== Fertig. ===');
|
|
}
|
|
|
|
main()
|
|
.catch((e) => {
|
|
console.error('Cleanup fehlgeschlagen:', e);
|
|
process.exit(1);
|
|
})
|
|
.finally(async () => {
|
|
await prisma.$disconnect();
|
|
});
|