Pentest 43.6 MEDIUM + 43.5 INFO: History-XSS + blocked:-Marker

43.6 MEDIUM: ContractHistoryEntry.title + .description waren auf
beiden Pfaden ungestrippt – Admin konnte HTML/Script-Tags
einschreiben, Portal-User las sie roh zurück. Fix: stripHtml()
auf Create + Update (Write-Pfad) und sanitizeEntry() im List +
Get (Read-Pfad), damit Alt-Daten ebenfalls clean rausgehen.

43.5 INFO: stripHtml ersetzt javascript: -> blocked: – sinnvoll
bei URL-Feldern, hässlich in Tarif-/Preis-Namen ("blocked:alert(1)"
als Preis). Neuer stripForDisplay-Wrapper entfernt den Marker
zusätzlich in CONTRACT_DISPLAY_STRING_FIELDS + CUSTOMER_DISPLAY_
STRING_FIELDS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 19:58:20 +02:00
parent b9a6d99d50
commit 83f1984f12
2 changed files with 39 additions and 8 deletions
+16 -2
View File
@@ -122,7 +122,7 @@ export function sanitizeCustomer<T extends Record<string, unknown>>(customer: T
}
for (const field of CUSTOMER_DISPLAY_STRING_FIELDS) {
if (typeof copy[field] === 'string') {
copy[field] = stripHtml(copy[field]);
copy[field] = stripForDisplay(copy[field]);
}
}
if (Array.isArray(copy.contracts)) {
@@ -166,6 +166,20 @@ export function sanitizeCustomers<T extends Record<string, unknown>>(customers:
* Provider-Passwort (nur über den dedizierten /password-Endpoint mit
* Audit-Log abrufbar) und sanitisiert das embedded customer.
*/
// Hilfs-Wrapper: stripHtml + Cleanup des `blocked:`-Markers in reinen
// Display-Strings. Der Marker ist sinnvoll bei URL-Feldern (man sieht,
// dass ein gefährliches Scheme abgewehrt wurde), in einem Tarif-Namen
// oder Preisfeld ist er nur kosmetischer Müll.
// Pentest 2026-05-30 (INFO, 43.5): `javascript:alert(1)` in
// priceFirst12Months wurde als "blocked:alert(1)" angezeigt.
function stripForDisplay(value: unknown): unknown {
const stripped = stripHtml(value);
if (typeof stripped === 'string' && stripped.includes('blocked:')) {
return stripped.replace(/blocked:/g, '').trim();
}
return stripped;
}
export function sanitizeContract<T extends Record<string, unknown>>(contract: T | null): T | null {
if (!contract) return contract;
const copy: Record<string, unknown> = { ...contract };
@@ -174,7 +188,7 @@ export function sanitizeContract<T extends Record<string, unknown>>(contract: T
}
for (const field of CONTRACT_DISPLAY_STRING_FIELDS) {
if (typeof copy[field] === 'string') {
copy[field] = stripHtml(copy[field]);
copy[field] = stripForDisplay(copy[field]);
}
}
// Nested: previousProviderName liegt im energyDetails-Sub-Objekt