Pentest 68.1 (LOW) + 68.2 (INFO): PDF-Active-Content-Filter + Modal-Limit
68.1: Magic-Byte-Check prüfte nur %PDF-. PDFs mit /JavaScript, /JS, /Launch, /EmbeddedFile, /RichMedia (Flash) kamen durch und wurden inline ausgeliefert – Browser-Viewer ignorieren JS, Adobe Acrobat nicht. - Neuer Helper assertSafePdf(buf) in utils/sanitize.ts mit case-sensitivem String-Scan auf die fünf Action-Patterns (\b-Word-Boundary verhindert False-Positives bei /JSXForm etc.). - Neue Middleware pdfUploadSafety.ts mit zwei Varianten: requireSafeUploadedPdf (PDF-only) und scanUploadedPdfIfPresent (durchwinkt JPG/PNG, scannt nur PDFs). - Eingehängt in: upload.routes (Magic-Byte-Validator erweitert), gdpr.routes Vollmacht-Upload, pdfTemplate.routes Template-Upload, contract.routes Vertragsdokumente, cachedEmail.controller (saveAttachmentTo, saveAttachmentAsInvoice, saveAttachmentAsContractDocument). - Inline-Vorschau bleibt – Pentester-Empfehlung "disposition=inline abschalten" wurde bewusst nicht umgesetzt (löst Acrobat-Risiko nicht, bricht aber ~20 UI-Stellen). - Smoke-Test: 5 Payload-Typen abgelehnt, clean PDF + Non-PDF + JSXForm durchgewinkt. 68.2: JpgToPdfModal-Self-DoS – MAX_IMAGES=50, MAX_IMAGE_BYTES=25MB.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import fs from 'fs';
|
||||
import { assertSafePdf } from '../utils/sanitize.js';
|
||||
import { ApiError } from '../utils/apiError.js';
|
||||
|
||||
/**
|
||||
* Express-Middleware nach multer.single(...): wenn die abgelegte Datei
|
||||
* eine PDF ist (Magic-Byte %PDF-), wird sie auf gefährliche aktive
|
||||
* Inhalte (JS / Launch / EmbeddedFile / RichMedia) gescannt. Bei
|
||||
* Verstoß: Datei vom Disk löschen + JSON-Error zurückgeben. Non-PDF-
|
||||
* Dateien passieren ohne Validierung – diese Middleware ist NICHT der
|
||||
* Magic-Byte-Check für andere Typen.
|
||||
*
|
||||
* Pentest 68.1 (LOW, 2026-06-03): Routen, die PDFs annehmen
|
||||
* (gdpr.routes Vollmacht, contract.routes Vertragsdokumente,
|
||||
* pdfTemplate.routes) haben bisher nur den client-gemeldeten mimetype
|
||||
* geprüft; gefährliche PDFs kamen durch.
|
||||
*/
|
||||
export function scanUploadedPdfIfPresent(req: Request, res: Response, next: NextFunction): void {
|
||||
const file = (req as Request & { file?: Express.Multer.File }).file;
|
||||
if (!file) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const buf = fs.readFileSync(file.path);
|
||||
if (buf.length >= 5 && buf.subarray(0, 5).toString('latin1') === '%PDF-') {
|
||||
assertSafePdf(buf);
|
||||
}
|
||||
next();
|
||||
} catch (e) {
|
||||
try { fs.unlinkSync(file.path); } catch { /* ignore */ }
|
||||
const status = e instanceof ApiError ? e.statusCode : 415;
|
||||
const message = e instanceof Error ? e.message : 'PDF ungültig';
|
||||
res.status(status).json({ success: false, error: message });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strikte Variante: Datei MUSS eine PDF sein. Sonst 415. Für Routen, die
|
||||
* ausschliesslich PDFs zulassen (z.B. Vollmacht-Upload).
|
||||
*/
|
||||
export function requireSafeUploadedPdf(req: Request, res: Response, next: NextFunction): void {
|
||||
const file = (req as Request & { file?: Express.Multer.File }).file;
|
||||
if (!file) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const buf = fs.readFileSync(file.path);
|
||||
if (buf.length < 5 || buf.subarray(0, 5).toString('latin1') !== '%PDF-') {
|
||||
throw new ApiError(415, 'Datei ist keine gültige PDF.');
|
||||
}
|
||||
assertSafePdf(buf);
|
||||
next();
|
||||
} catch (e) {
|
||||
try { fs.unlinkSync(file.path); } catch { /* ignore */ }
|
||||
const status = e instanceof ApiError ? e.statusCode : 415;
|
||||
const message = e instanceof Error ? e.message : 'PDF ungültig';
|
||||
res.status(status).json({ success: false, error: message });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user