
GHSA-c4j6-fc7j-m34r / CVE-2026-44578 (Next.js WebSocket-upgrade SSRF) के लिए OOB verifier
GHSA-c4j6-fc7j-m34r / CVE-2026-44578 के लिए इन-बैंड वेरिफायर — Next.js में WebSocket अपग्रेड अनुरोधों के माध्यम से सर्वर-साइड रिक्वेस्ट फोर्जरी (SSRF)।
⚠️ केवल अधिकृत सुरक्षा परीक्षण के लिए। आप यह सुनिश्चित करने के लिए ज़िम्मेदार हैं कि आपके पास उस हर टारगेट का परीक्षण करने की अनुमति है जिसे आप इस स्क्रिप्ट को देते हैं।
| फ़ील्ड | मान |
|---|---|
| CVE | CVE-2026-44578 |
| GHSA | GHSA-c4j6-fc7j-m34r |
| CWE | CWE-918 (SSRF) |
| CVSS v3.1 | 8.6 (उच्च) — AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N |
| प्रभावित संस्करण | next >=13.4.13 <15.5.16, >=16.0.0 <16.2.5 |
| पैच किए गए संस्करण | 15.5.16, 16.2.5 |
| फिक्स कमिट | c4f69086 |
| प्रभावित नहीं | Vercel-होस्टेड; output: "export"; ऐसी तैनाती जो रिवर्स प्रॉक्सी के पीछे हो और Upgrade को फॉरवर्ड न करे |
एक हमलावर एक सेल्फ-होस्टेड Next.js प्रोसेस के लिए एक TCP कनेक्शन खोलता है और एक HTTP/1.1 WebSocket अपग्रेड भेजता है जिसका request-URI एक एब्सोल्यूट URL है:
GET http://anything/<path> HTTP/1.1
Host: <target>
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
resolveRoutes में, URL में // होता है (हर एब्सोल्यूट URI में होता है), जो "normalize repeated slashes" शाखा से मेल खाता है। वह शाखा { finished: true, statusCode: 308, parsedUrl: <mangled> } के साथ जल्दी रिटर्न करती है। नॉर्मलाइज़र http://host/path को http:/host/path (एक स्लैश) में बदल देता है।
router-server.ts में, पैच-पूर्व अपग्रेड हैंडलर finished/statusCode को अनदेखा करता था और केवल parsedUrl.protocol की जाँच करता था। चूँकि प्रोटोकॉल नॉर्मलाइज़ेशन से बच जाता है, यह proxyRequest(...) को कॉल करता था।
proxyRequest मैंगल किए गए URL पर url.format(parsedUrl) चलाता है, जिससे http:/host:port/path मिलता है। http-proxy उस टारगेट को पार्स करता है, कोई होस्ट नहीं पाता (url.parse('http:/...').host === null), और अपने डिफ़ॉल्ट डेस्टिनेशन पर वापस गिर जाता है: localhost:80 (या https के लिए localhost:443)।
इसलिए व्यवहार में, SSRF आपको हमलावर-नियंत्रित पथ के साथ Next को Next.js होस्ट के अपने localhost:80 / localhost:443 पर WebSocket अपग्रेड खोलने देता है।
फिक्स (कमिट c4f69086) ने अपग्रेड हैंडलर को प्रॉक्सी करने से पहले finished && !statusCode जाँचने के लिए बदल दिया। 308-नॉर्मलाइज़ेशन मामला अब !statusCode जाँच में विफल हो जाता है और इसके बजाय सॉकेट बंद कर दिया जाता है।
प्रॉक्सी कभी भी किसी बाहरी होस्ट तक नहीं पहुँचता। यदि आप एक interactsh / Burp Collaborator / webhook कैनरी सेट करते हैं और उम्मीद करते हैं कि Next प्रोसेस फोन होम करेगा, तो ऐसा नहीं होगा — कनेक्शन टारगेट मशीन के localhost पर जाता है। इसलिए यह वेरिफायर अपग्रेड सॉकेट से पढ़े गए इन-बैंड सिग्नल का उपयोग करता है: एक कमज़ोर सर्वर एक पहचानने योग्य एरर बॉडी लौटाता है, जबकि एक पैच किया हुआ सर्वर कुछ भी नहीं लौटाता।
SSRF टारगेट सीमित है, लेकिन वास्तविक डिप्लॉयमेंट में फिर भी महत्वपूर्ण है:
127.0.0.1:80 या :443 पर बाइंड होते हैं और लोकलहोस्ट-उत्पन्न अनुरोधों पर भरोसा करते हैं।127.0.0.1:80 पर एक्सपोज़्ड Docker सॉकेट (असामान्य लेकिन देखा गया है)।AWS / GCP / Azure मेटाडेटा एंडपॉइंट (169.254.169.254) सीधे पहुँच योग्य नहीं हैं क्योंकि बग डेस्टिनेशन को लोकलहोस्ट पर पिन कर देता है।
प्रत्येक टारगेट के लिए, स्क्रिप्ट एक कच्चा TCP (या TLS) सॉकेट खोलती है, तैयार किया गया अपग्रेड भेजती है, प्रतिक्रिया पढ़ती है, और दो सिग्नल उत्पन्न करती है:
verdict — क्या बग मौजूद है।impact_confirmed — क्या SSRF ने वास्तव में डेटा एक्सफ़िलट्रेट किया (यानी टारगेट के localhost:80/443 पर एक सह-स्थित सेवा ने उत्तर दिया और हमें उसकी प्रतिक्रिया वापस मिली)।| प्रतिक्रिया | निर्णय | impact_confirmed |
|---|---|---|
Internal Server Error शामिल है | vulnerable | false — बग सिद्ध हुआ, लेकिन प्रॉक्सी को लोकलहोस्ट पर कुछ नहीं मिला |
HTTP/1. से शुरू होता है | vulnerable_proxy_succeeded | true — वास्तविक प्रतिक्रिया डेटा एक्सफ़िलट्रेट हुआ |
| खाली / स्वच्छ समापन | likely_patched | false — "Next नहीं", "रिवर्स प्रॉक्सी ने Upgrade हटा दिया", "Vercel" को भी कवर करता है |
| बिना-Upgrade नियंत्रण के समान | front_end_intercepts | false — फ्रंट-एंड प्रॉक्सी ने दोनों प्रोबों को शॉर्ट-सर्किट कर दिया; SSRF कभी Next तक नहीं पहुँचा |
| कुछ और | inconclusive | false |
जब impact_confirmed सत्य होता है, तो JSON आउटपुट में लीक हुई प्रतिक्रिया से पार्स किए गए upstream_status, upstream_server और upstream_content_type भी शामिल होते हैं (ट्राइएज / रिपोर्ट-लेखन के लिए उपयोगी)।
डिफ़ॉल्ट रूप से, प्रत्येक टारगेट को समान एब्सोल्यूट-URI रिक्वेस्ट लाइन के साथ लेकिन बिना Upgrade हेडर (Connection: close) के एक कंट्रोल प्रोब भी मिलता है। यदि फ्रंट-एंड दोनों प्रोबों को एक जैसी प्रतिक्रिया लौटाता है (स्टेटस लाइन + सहनशीलता के भीतर आकार), तो होस्ट का अपना फ्रंट-एंड स्वयं एब्सोल्यूट-URI रिक्वेस्ट लाइन को अस्वीकार कर रहा है — nginx 400, Apache 400, CDN एज — और SSRF कभी Next तक नहीं पहुँचा। निर्णय घटकर front_end_intercepts हो जाता है और JSON आउटपुट में प्रॉक्सी की प्रतिक्रिया से पार्स किए गए front_end_status और front_end_server शामिल होते हैं ताकि ऑपरेटर पहचान सके कि क्या इंटरसेप्ट कर रहा है।
यह एक वास्तविक-विश्व गलत सकारात्मक (false positive) को समाप्त करता है जो तब देखा गया जब सेल्फ-होस्टेड Next nginx/Apache के पीछे होता है: वे प्रॉक्सी प्रोब की GET http:///x HTTP/1.1 रिक्वेस्ट लाइन को सामान्य 400 के साथ अस्वीकार कर देते हैं, जिसे डिटेक्टर पहले vulnerable_proxy_succeeded के रूप में गलत पढ़ता था। ऑप्ट आउट करने और कच्चे निर्णय देखने के लिए --no-control-probe पास करें।
# single target
python3 verify_ghsa_c4j6.py --target https://app.example.com
# multiple targets via flag repetition
python3 verify_ghsa_c4j6.py \
--target https://app1.example.com \
--target app2.example.com:3000 \
--target 10.0.0.5:80
# from a file (one target per line; '#' for comments)
python3 verify_ghsa_c4j6.py --targets-file targets.txt
# from stdin
cat targets.txt | python3 verify_ghsa_c4j6.py
# JSON Lines output for downstream tooling
python3 verify_ghsa_c4j6.py --targets-file targets.txt --json
# Enumerate co-located services on the target's localhost:80/443 via the bug
python3 verify_ghsa_c4j6.py --target https://app.example.com --scan
# Same, with a custom path list
python3 verify_ghsa_c4j6.py --target ... --scan-paths-file my_paths.txt
--scan SSRF गैजेट के माध्यम से सामान्य पथों की एक अंतर्निहित सूची (Apache/nginx स्टेटस मॉड्यूल, हेल्थ और मेट्रिक्स एंडपॉइंट, Spring Boot Actuator, Go pprof, Docker डेमन एंडपॉइंट, सामान्य एडमिन पैनल, लीकी कॉन्फ़िग फ़ाइलें, Elasticsearch रूट्स, आदि) की जाँच करता है।
डिफ़ॉल्ट रूप से, स्कैन मोड प्रति टारगेट एक अतिरिक्त डिफरेंशियल बेसलाइन प्रोब यादृच्छिक गैर-मौजूद पथ के साथ चलाता है। बाद के प्रोबों को DIFF तभी टैग किया जाता है जब उनका (status, body length) सिग्नेचर बेसलाइन से विचलित होता है — "कुछ नहीं मिला" अपस्ट्रीम से समान 404s को noise के रूप में चिह्नित किया जाता है और हिट गिनती को बढ़ाते नहीं हैं। हर उस प्रोब की रिपोर्ट करने के लिए --no-differential पास करें जो किसी सेवा तक पहुँचा (पुराना व्यवहार)।
आउटपुट प्रति टारगेट समूहीकृत है: