Pentest 2026-05-20 Pen-28-Befunde (LOW/INFO)

28.1 URI-Schema unvollstaendig:
DANGEROUS_URI_SCHEMES erweitert um file:/ftp: – "ftp://evil.com/x.js"
und "file:///etc/passwd" wurden vorher in companyName akzeptiert.

28.2 HTML-Entity-Decoding-Bypass:
stripHtml() lief direkt ueber den Roh-String, "javascript:",
"<script>" und "<script>" umgingen die Regex.
decodeHtmlEntities() dekodiert jetzt numerische (decimal+hex) +
gaengige named entities VOR dem Tag-/URI-Strip.

28.3 Vollmacht-Upload Magic-Byte-Check:
multer pruefte nur client-MIME, HTML/PHP/Shell-Scripts kamen als
application/pdf durch. uploadAuthorizationDocument liest jetzt die
ersten 5 Bytes und verlangt "%PDF-", sonst Loeschen + 400.

28.4 Rate-Limit auf /api/public/consent:
30 Requests pro IP pro 15min. Brute-Force-sicher war der 128-bit-
UUID-Hash schon, aber ohne Limit konnte ein Angreifer das System
mit Audit-Log- und Mail-Spam belasten.

Live-verifiziert auf dev: alle vier Bypaesse blockiert, legitime
Eingaben unangetastet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 17:46:15 +02:00
parent 8e48d3b432
commit 65ec07e274
5 changed files with 121 additions and 7 deletions
+32 -6
View File
@@ -241,16 +241,42 @@ const USER_CREATE_FIELDS = [
* companyName landete vorher ungefiltert in der DB.
*
* Pentest 2026-05-20 (LOW): zusätzlich werden Skript-URI-Schemata
* unschädlich gemacht (`javascript:`, `data:`, `vbscript:`). Plain-Text-
* Felder enthalten legitime URLs ohnehin selten; ein gespeicherter
* `javascript:alert(1)` würde ansonsten in einem `<a href={value}>`
* sofort feuern.
* unschädlich gemacht (`javascript:`, `data:`, `vbscript:`, `file:`,
* `ftp:`). Plain-Text-Felder enthalten legitime URLs ohnehin selten;
* ein gespeicherter `javascript:alert(1)` würde ansonsten in einem
* `<a href={value}>` sofort feuern. `file:` + `ftp:` ergänzt nach
* Pentest 28.1 kein direkter XSS, aber Data-Exfil/Lokalpfad-Vektor.
*
* Pentest 28.2: HTML-Entity-Decoding VOR dem Strip, sonst umgehen
* `&#106;avascript:` und `&#60;script&#62;` die Regex.
*/
const DANGEROUS_URI_SCHEMES = /(?:javascript|data|vbscript)\s*:/gi;
const DANGEROUS_URI_SCHEMES = /(?:javascript|data|vbscript|file|ftp)\s*:/gi;
function decodeHtmlEntities(s: string): string {
return s
// Numeric decimal: &#106; → 'j'
.replace(/&#(\d+);?/g, (_m, code) => {
const n = parseInt(code, 10);
return n >= 0 && n <= 0x10FFFF ? String.fromCodePoint(n) : '';
})
// Numeric hex: &#x6A; → 'j'
.replace(/&#x([0-9a-fA-F]+);?/g, (_m, code) => {
const n = parseInt(code, 16);
return n >= 0 && n <= 0x10FFFF ? String.fromCodePoint(n) : '';
})
// Häufige Named-Entities (bewusst klein gehalten wir wollen nur
// XSS-relevante Bypässe verhindern, nicht jeden Entity-Sonderfall).
// &amp; ZULETZT, sonst doppel-dekodiert (`&amp;lt;` → `<`).
.replace(/&lt;/gi, '<')
.replace(/&gt;/gi, '>')
.replace(/&quot;/gi, '"')
.replace(/&#?apos;/gi, "'")
.replace(/&amp;/gi, '&');
}
export function stripHtml(value: unknown): unknown {
if (typeof value !== 'string') return value;
return value
return decodeHtmlEntities(value)
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<\/?[a-z][^>]*>/gi, '')