
सुरक्षा सलाह: अनपार्स्ड ट्रांसफर-एन्कोडिंग मानों के माध्यम से HTTP रिक्वेस्ट स्मगलिंग (tiny_http)
निर्धारित CVE ID: CVE-2026-66752
tiny_http केवल यह जाँचता है कि Transfer-Encoding हेडर मौजूद है या नहीं। यह मान को कभी नहीं देखता। कोई भी मान — जिसमें ऐसे कोडिंग शामिल हैं जो chunked नहीं हैं और ऐसी कोडिंग सूचियाँ जिनका अंतिम तत्व chunked नहीं है — बॉडी को chunk-डिकोड करवा देता है, और साथ ही Content-Length को भी हटा दिया जाता है।
एक फ्रंट एंड जो ट्रांसफर कोडिंग को सही ढंग से पार्स करता है, ऐसी रिक्वेस्ट को tiny_http से अलग तरीके से फ्रेम करेगा। एक कनेक्शन पर दो प्रतिभागियों के दो अलग-अलग फ्रेमिंग होना रिक्वेस्ट स्मगलिंग के लिए पूर्व-शर्त है। गैर-chunked बॉडी को गैर-chunked कोडिंग के साथ भेजना भी tiny_http को बॉडी रीड में विफल कर देता है और वह कोई उत्तर नहीं देता।
रेपो 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 |
| समाधान | लेखन के समय कोई फिक्स्ड संस्करण उपलब्ध नहीं |
CWE-444 (HTTP रिक्वेस्ट की असंगत व्याख्या)।
CVSS 4.0 आधार स्कोर 6.3 (मध्यम)
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:L/SC:L/SI:L/SA:N
एक रिमोट, अनप्रमाणित क्लाइंट जो रॉ HTTP भेजता है। कोई क्रेडेंशियल या उपयोगकर्ता इंटरैक्शन नहीं।
स्मगलिंग मामले के लिए आवश्यक है कि tiny_http किसी ऐसे फ्रंट एंड या CDN के पीछे स्थित हो जो Transfer-Encoding और Content-Length को आगे भेजता है और RFC 9112 धारा 6.1 को सही ढंग से लागू करता है, अर्थात वह ऐसी कोडिंग सूची जिसका अंतिम तत्व chunked नहीं है, को chunked नहीं मानता और Content-Length पर वापस आ जाता है। तब वह फ्रंट एंड और tiny_http इस बात पर असहमत होते हैं कि बॉडी कहाँ समाप्त होती है।
अनुत्तरित-रिक्वेस्ट मामले के लिए कनेक्ट करने की क्षमता के अलावा किसी और चीज़ की आवश्यकता नहीं है।
tiny_http-0.12.0/src/request.rs, पंक्तियाँ 143 से 153। हेडर का पता लगाकर क्लोन किया जाता है, लेकिन मान की केवल उपस्थिति के लिए जाँच की जाती है:
143 // finding the transfer-encoding header
144 let transfer_encoding = headers
145 .iter()
146 .find(|h: &&Header| h.field.equiv("Transfer-Encoding"))
147 .map(|h| h.value.clone());
148
149 // finding the content-length header
150 let content_length = if transfer_encoding.is_some() {
151 // if transfer-encoding is specified, the Content-Length
152 // header must be ignored (RFC2616 #4.4)
153 None
tiny_http-0.12.0/src/request.rs, पंक्तियाँ 218 से 221, फिर बिना शर्त chunked डिकोडर लागू करता है:
218 } else if transfer_encoding.is_some() {
219 // if a transfer-encoding was specified, then "chunked" is ALWAYS applied
220 // over the message (RFC2616 #3.6)
221 Box::new(FusedReader::new(Decoder::new(source_data))) as Box<dyn Read + Send + 'static>
transfer_encoding एक Option<AsciiString> है जिसमें रॉ मान होता है, और कोई भी चीज़ इसकी सामग्री का निरीक्षण नहीं करती। RFC 9112 धारा 6.1 के अनुसार किसी रिक्वेस्ट की अंतिम कोडिंग chunked होनी चाहिए और अन्यथा सर्वर को मैसेज अस्वीकार करना आवश्यक है, और धारा 6.3 के अनुसार ऐसी रिक्वेस्ट को अस्वीकार करना आवश्यक है जिसमें Transfer-Encoding और Content-Length दोनों हों।
चरण 1. एक सर्वर शुरू करें जो फ्रेमिंग हेडर और उसके द्वारा पढ़ी गई बॉडी की रिपोर्ट करता है।
use std::io::Read;
use tiny_http::{Response, Server};
fn main() {
let server = Server::http("127.0.0.1:8005").unwrap();
for mut request in server.incoming_requests() {
let te = request.headers().iter()
.find(|h| h.field.equiv("Transfer-Encoding"))
.map(|h| h.value.as_str().to_string());
let cl = request.headers().iter()
.find(|h| h.field.equiv("Content-Length"))
.map(|h| h.value.as_str().to_string());
let mut body = Vec::new();
let r = request.as_reader().read_to_end(&mut body);
println!("TE={:?} CL={:?} read={:?} body={:?}",
te, cl, r, String::from_utf8_lossy(&body));
let _ = request.respond(Response::from_string("ok"));
}
}
चरण 2. Transfer-Encoding: identity, एक मेल खाता Content-Length, और एक सादा बॉडी के साथ एक रिक्वेस्ट भेजें। कोई भी फ्रंट एंड इस बॉडी को पाँच बाइट्स hello के रूप में फ्रेम करेगा।
printf 'POST / HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: identity\r\nContent-Length: 5\r\n\r\nhello' | nc -w 2 127.0.0.1 8005
परिणाम. tiny_http ने एक ऐसी बॉडी को chunk-डिकोड किया जो chunked नहीं है, उसे हटा दिया, और कोई प्रतिक्रिया नहीं भेजी। क्लाइंट अपने स्वयं के टाइमआउट तक प्रतीक्षा करता है और कनेक्शन समाप्त हो जाता है।
TE=Some("identity") CL=Some("5") read=Err(Custom { kind: InvalidInput, error: DecoderError }) body=""
चरण 3. एक ऐसी कोडिंग सूची भेजें जिसका अंतिम तत्व chunked नहीं है।
printf 'POST / HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: chunked, identity\r\n\r\n5\r\nhello\r\n0\r\n\r\n' | nc -w 2 127.0.0.1 8005
परिणाम. tiny_http इसे स्वीकार करता है और chunked के रूप में डिकोड करता है, जबकि RFC 9112 धारा 6.1 में अस्वीकृति आवश्यक है:
TE=Some("chunked, identity") CL=None read=Ok(5) body="hello"
इसके विपरीत, अकेला Content-Length और एक सही Transfer-Encoding: chunked दोनों ठीक से व्यवहार करते हैं और body="hello" उत्पन्न करते हैं।
जहाँ tiny_http एक ऐसे फ्रंट एंड के पीछे होता है जो ट्रांसफर कोडिंग को सही ढंग से पार्स करता है, दोनों घटक इस बात पर असहमत होते हैं कि रिक्वेस्ट बॉडी कहाँ समाप्त होती है। हमलावर उस असहमति के दोनों ओर के बाइट्स चुनता है, जो फ्रंट एंड की रूटिंग और एक्सेस कंट्रोल को पार करते हुए एक रिक्वेस्ट को स्मगल करने का मानक तरीका है।
किसी भी फ्रंट एंड से स्वतंत्र रूप से, चरण 2 और 3 एक साधारण रूप से गलत-निर्मित रिक्वेस्ट दिखाते हैं जो एक वर्कर को व्यस्त रखती है और उसे कोई प्रतिक्रिया नहीं मिलती, इसलिए क्लाइंट यह नहीं बता सकता कि रिक्वेस्ट अस्वीकार कर दी गई थी।
Transfer-Encoding को अल्पविराम-पृथक सूची के रूप में पार्स करें, जैसा कि यह वास्तव में है, src/request.rs में लगभग पंक्ति 144 पर:
बॉडी ले जाने वाली रिक्वेस्ट के लिए अंतिम कोडिंग का chunked होना आवश्यक करें, और किसी भी अन्य कोडिंग को chunk-डिकोड करने के बजाय 400 के साथ अस्वीकार करें। ऐसी रिक्वेस्ट को अस्वीकार करें जिसमें Transfer-Encoding और Content-Length दोनों हों, जैसा कि RFC 9112 धारा 6.3 एक गैर-प्रॉक्सी सर्वर के लिए आवश्यक करती है, बजाय पंक्ति 150 पर Content-Length को चुपचाप अनदेखा करने के।
जब बॉडी रीडर विफल हो जाए, तो रिक्वेस्ट को बिना प्रतिक्रिया के छोड़ने के बजाय 400 भेजें।