Pentest 2026-05-20 Pen-30-Befunde (MEDIUM+INFO)
30.13 MIME-Extension-XSS (MEDIUM): GET /api/files/download lieferte hochgeladene Dateien via res.sendFile() aus. Da multer nur den client-gemeldeten MIME prueft, konnte eine als application/pdf deklarierte .html-Datei auf Disk landen – Express liest beim Senden den Content-Type aus der Extension (text/html), Browser haette gerendert → Stored XSS. Fix: Content-Disposition: attachment + safe filename. Browser laedt jetzt herunter statt zu rendern, egal welcher Content-Type. UX-Cost ist gering (PDF-Preview offnet halt aus dem Download-Ordner). X-Content-Type-Options: nosniff bleibt zusaetzlich gesetzt. 30.14 SSRF Private-IP-Block opt-in (INFO): ssrfGuard erlaubte private IPs (127/10/172.16/192.168) bewusst, weil On-Prem-Setups Plesk/Dovecot/Postfix lokal laufen lassen. Fuer Cloud-Deployments ist das ein SSRF-Vektor. Neuer Env-Flag SSRF_BLOCK_PRIVATE_IPS=true erweitert die Block-Liste um alle privaten Ranges + ::1 + fc00::/7 + IPv4-mapped + localhost/ ip6-localhost. Default off (on-prem-kompatibel). Live-verifiziert auf dev: - Download-Header: Content-Disposition: attachment + safe filename - Default: 127.0.0.1/10.x/192.168.x/localhost durchgelassen, 169.254.169.254 (Cloud-Metadata) weiter geblockt - SSRF_BLOCK_PRIVATE_IPS=true: alle privaten Ranges geblockt, 8.8.8.8 (legitim) durchgelassen Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -78,7 +78,21 @@ export async function downloadFile(req: AuthRequest, res: Response): Promise<voi
|
||||
return;
|
||||
}
|
||||
|
||||
// Content-Type aus Extension bestimmen (konservativ – Express macht das eh)
|
||||
// Stored-XSS-Schutz (Pentest 2026-05-20 MEDIUM 30.13):
|
||||
// Multer prüfte beim Upload nur den client-gemeldeten MIME-Type.
|
||||
// Eine `.html`-Datei mit `Content-Type: application/pdf` rutschte
|
||||
// durch und wurde mit Original-Extension auf Disk geschrieben.
|
||||
// Beim Download bestimmt res.sendFile() den Content-Type aus der
|
||||
// Extension – also `text/html` – und der Browser hätte das als
|
||||
// Stored-XSS gerendert. `X-Content-Type-Options: nosniff` schützt
|
||||
// nicht, wenn der Server selbst text/html liefert.
|
||||
//
|
||||
// Fix: alle Files via Content-Disposition: attachment ausliefern.
|
||||
// Der Browser lädt herunter statt zu rendern, egal welcher Type.
|
||||
// Für legitime PDF/Bild-Vorschau ist das vertretbar – Browser
|
||||
// öffnen den Download dann eben aus dem Datei-Manager.
|
||||
const filename = path.basename(absolute).replace(/[^A-Za-z0-9._-]/g, '_');
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
||||
res.sendFile(absolute);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user