Pentest 2026-05-20 MEDIUM+LOW Follow-ups
MEDIUM – Consent-Mass-Assignment:
PUT /api/gdpr/customer/:id/consents/:type nahm source/documentPath/
version ungefiltert aus dem Body. Portal-User konnte
source="ADMIN_OVERRIDE", version="<script>" oder
documentPath="../../etc/passwd" durchschmuggeln.
Fix: nur status aus Body, source server-seitig auf "portal"
hardcoded, documentPath/version bleiben NULL (werden dediziert
vom Authorization-Upload server-seitig gesetzt). Whitelist
ALLOWED_CONSENT_SOURCES für source-Werte. grantAuthorization
(Admin) erzwingt die Whitelist ebenfalls; notes läuft jetzt
durch stripHtml.
LOW – javascript:-URI in companyName:
stripHtml() entfernte HTML-Tags, ließ aber javascript:/data:/
vbscript:-Schemata stehen. companyName="javascript:alert(1)"
hätte in <a href={companyName}> aktiv werden können.
Fix: stripHtml ersetzt jene Schemata mit "blocked:" – legitimer
Text bleibt unangetastet, das Schema wird unschädlich.
LOW – documentPath ohne Validierung:
Bereits durch obigen Consent-Fix erledigt; Cleanup-Pass strippt
zusätzlich vorhandene dreckige Pfade.
cleanup-xss-and-mass-assignment.ts: neue cleanupConsents() läuft
beim Container-Start, normalisiert source per Whitelist auf
"unknown" + stripHtml über version/documentPath.
Live-verifiziert auf dev (alle drei Payloads geblockt + Cleanup
auf dirty DB greift).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -77,7 +77,52 @@ function stripHtmlString(s: string): string {
|
||||
return s
|
||||
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
||||
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
||||
.replace(/<\/?[a-z][^>]*>/gi, '');
|
||||
.replace(/<\/?[a-z][^>]*>/gi, '')
|
||||
.replace(/(?:javascript|data|vbscript)\s*:/gi, 'blocked:');
|
||||
}
|
||||
|
||||
// Legitime CustomerConsent.source-Werte. Alles andere wird beim Cleanup
|
||||
// auf 'unknown' normalisiert. Pentest 2026-05-20.
|
||||
const ALLOWED_CONSENT_SOURCES = new Set([
|
||||
'portal',
|
||||
'public-link',
|
||||
'telefon',
|
||||
'papier',
|
||||
'email',
|
||||
'crm-backend',
|
||||
]);
|
||||
|
||||
async function cleanupConsents() {
|
||||
// version + documentPath: HTML strippen (waren ohne Validierung).
|
||||
// source: Whitelist erzwingen.
|
||||
let versionStripped = 0;
|
||||
let pathStripped = 0;
|
||||
let sourceFixed = 0;
|
||||
const consents = await prisma.customerConsent.findMany({
|
||||
select: { id: true, source: true, documentPath: true, version: true },
|
||||
});
|
||||
for (const c of consents) {
|
||||
const data: Record<string, string | null> = {};
|
||||
if (c.version && c.version !== stripHtmlString(c.version)) {
|
||||
data.version = stripHtmlString(c.version);
|
||||
versionStripped++;
|
||||
}
|
||||
if (c.documentPath && c.documentPath !== stripHtmlString(c.documentPath)) {
|
||||
data.documentPath = stripHtmlString(c.documentPath);
|
||||
pathStripped++;
|
||||
}
|
||||
if (c.source && !ALLOWED_CONSENT_SOURCES.has(c.source)) {
|
||||
data.source = 'unknown';
|
||||
sourceFixed++;
|
||||
}
|
||||
if (Object.keys(data).length > 0) {
|
||||
await prisma.customerConsent.update({ where: { id: c.id }, data });
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
` → Consent bereinigt: version-stripped=${versionStripped}, ` +
|
||||
`documentPath-stripped=${pathStripped}, source-whitelist=${sourceFixed}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function cleanupAppSettings() {
|
||||
@@ -182,6 +227,7 @@ async function main() {
|
||||
console.log('=== Cleanup: XSS-Reste + Mass-Assignment-AppSettings ===');
|
||||
await cleanupXss();
|
||||
await cleanupAppSettings();
|
||||
await cleanupConsents();
|
||||
await findOrPurgePentestRecords();
|
||||
console.log('=== Fertig. ===');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user