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:
@@ -4,6 +4,8 @@
|
||||
* Verschlüsselungen oder Reset-Tokens versehentlich durch die API leaken.
|
||||
*/
|
||||
|
||||
import { ApiError } from './apiError.js';
|
||||
|
||||
// Felder die NIE in einer API-Response an den Client gehen dürfen
|
||||
const SENSITIVE_CUSTOMER_FIELDS = [
|
||||
'portalPasswordHash',
|
||||
@@ -246,6 +248,45 @@ export function assertValidDocumentPath(v: string | null | undefined, fieldLabel
|
||||
}
|
||||
}
|
||||
|
||||
// Pentest 68.1 (LOW, 2026-06-03): PDFs mit JavaScript, /Launch (externes
|
||||
// Programm), /EmbeddedFile (eingebettete Executables) oder /RichMedia
|
||||
// (Flash) kamen durch den reinen Magic-Byte-Check (%PDF-) und wurden
|
||||
// inline ausgeliefert. Browser-PDF-Viewer (PDFium/PDF.js) führen kein JS
|
||||
// aus, aber sobald jemand die PDF in Adobe Acrobat öffnet, läuft sie.
|
||||
// → Wir blocken das schon beim Upload.
|
||||
//
|
||||
// PDF-Name-Objekte sind laut PDF 32000-1:2008 §7.3.5 case-sensitive, also
|
||||
// kein /i auf den Patterns. Whitespace nach `/` ist im Standard zwar
|
||||
// erlaubt, in real-world Exploits aber praktisch nie zu sehen – wir
|
||||
// bleiben hier pragmatisch.
|
||||
//
|
||||
// Hinweis: erkannt wird nur, was im Klartext im PDF-Body steht.
|
||||
// Komprimierte oder verschlüsselte Streams entgehen dem String-Scan.
|
||||
// Für unser Bedrohungsmodell (kompromittierter Staff-Account, LOW) reicht
|
||||
// das – ein vollständiger PDF-Parser wäre Overkill.
|
||||
const PDF_DANGER_PATTERNS: { pattern: RegExp; label: string }[] = [
|
||||
{ pattern: /\/JavaScript\b/, label: 'JavaScript-Action' },
|
||||
{ pattern: /\/JS\b/, label: 'JavaScript-Action' },
|
||||
{ pattern: /\/Launch\b/, label: 'Launch-Action (externes Programm)' },
|
||||
{ pattern: /\/EmbeddedFile\b/, label: 'eingebettete Datei' },
|
||||
{ pattern: /\/RichMedia\b/, label: 'RichMedia-Inhalt (Flash)' },
|
||||
];
|
||||
|
||||
export function assertSafePdf(buf: Buffer): void {
|
||||
if (buf.length < 5 || buf.subarray(0, 5).toString('latin1') !== '%PDF-') {
|
||||
return; // keine PDF → andere Validatoren zuständig
|
||||
}
|
||||
const content = buf.toString('latin1');
|
||||
for (const { pattern, label } of PDF_DANGER_PATTERNS) {
|
||||
if (pattern.test(content)) {
|
||||
throw new ApiError(
|
||||
415,
|
||||
`PDF enthält nicht erlaubte aktive Inhalte (${label}). Bitte ohne JavaScript / Auto-Actions / eingebettete Dateien hochladen.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pentest 51.3 + 60.3 (MEDIUM, 2026-06-01): Telefon-/Vorwahl-Felder
|
||||
// dürfen NIE CRLF oder andere Control-Chars enthalten – sonst sind sie
|
||||
// ein Header-Injection-Vektor (Mail, HTTP), wenn der Wert mal in einen
|
||||
|
||||
Reference in New Issue
Block a user