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
@@ -891,6 +891,33 @@ export async function uploadAuthorizationDocument(req: AuthRequest, res: Respons
return res.status(400).json({ success: false, error: 'Keine Datei hochgeladen' });
}
// Magic-Byte-Check: multer prüft nur den client-gemeldeten MIME-Type,
// ein Angreifer kann beliebige Daten als "application/pdf" hochladen.
// PDF beginnt mit "%PDF-" (0x25 0x50 0x44 0x46 0x2D). Wenn nicht,
// gleich wegwerfen. (Pentest 2026-05-20 LOW 28.3)
const PDF_MAGIC = Buffer.from([0x25, 0x50, 0x44, 0x46, 0x2d]); // %PDF-
try {
const fd = fs.openSync(req.file.path, 'r');
const head = Buffer.alloc(5);
fs.readSync(fd, head, 0, 5, 0);
fs.closeSync(fd);
if (!head.equals(PDF_MAGIC)) {
// Hochgeladene Nicht-PDF-Datei sofort wieder löschen, sonst
// bleibt der Müll im uploads/-Volume.
try { fs.unlinkSync(req.file.path); } catch { /* ignore */ }
return res.status(400).json({
success: false,
error: 'Datei ist keine gültige PDF (Magic-Bytes fehlen).',
});
}
} catch (e) {
try { fs.unlinkSync(req.file.path); } catch { /* ignore */ }
return res.status(400).json({
success: false,
error: 'Hochgeladene Datei konnte nicht gelesen werden.',
});
}
const documentPath = `/uploads/authorizations/${req.file.filename}`;
const auth = await authorizationService.updateAuthorizationDocument(
customerId,