RainLoop is an open-source webmail client used by thousands of organizations to exchange sensitive messages and files via email. In this blog post, we are warning RainLoop users about a code vulnerability that allows attackers to steal emails from the inboxes of victims. At the time of writing, no official patch is available.
The code vulnerability described in this blog post can be easily exploited by an attacker by sending a malicious email to a victim that uses RainLoop as a mail client. When the email is viewed by the victim, the attacker gains full control over the session of the victim and can steal any of their emails, including those that contain highly sensitive information such as passwords, documents, and password reset links. Let's have a look what happened and what we can learn from it.
RainLoop’s backend is a PHP application that acts as a proxy between a user and their mail server. Similar to mail clients, such as Thunderbird, it enables a user to log into a mail server, fetch emails, view them, and send emails.
On a high level, RainLoop deploys the following flow to achieve this:
- Receive the raw, untrusted HTML code from the mail server
- Create an instance of the built-in
DOMDocumentclass in PHP. This parses HTML into a tree structure of HTML elements and their attributes
- Depending on the configuration, use an allow or deny list to remove any dangerous contents in the tree structure
- Convert the sanitized tree structure of the
DOMDocumentinto HTML code
Intuitively it makes sense to analyze the code that attempts to remove any dangerous HTML code (step 3 in the above’s list) and find a weakness inside of that code to bypass the sanitizer. However, our experience has shown there are often logic bugs after the sanitization steps have been performed. From the security researcher's point of view, they are much easier to spot and are often overlooked by developers: for good examples of previous findings using this pattern, see Zimbra Stored XSS and WordPress CSRF to RCE.
We mentioned that the 4th step converts the tree structure of the
DOMDocument into HTML code. Usually, this step is trivial as the
DOMDocument class has the built-in
saveHTML() method which does exactly what is required.
One last problem must be solved before the sanitized HTML code can be rendered to the user: due to normalization performed by the
DOMDocument class, the HTML code
saveHTML() emits contains
<body> tags. Although this is perfectly valid and harmless, the front end page of RainLoop that renders the email already contains
<body> tags might contain important attributes such as styles and classes that must be preserved. RainLoop solves these problems by parsing the attributes from the
<body> tag of the email structure and then wrapping the HTML code of the email in a fake body that contains the original
In the following paragraphs, we will describe how this process works in RainLoop, show the corresponding code snippets and finally describe a logic flaw in this process that leads to a Stored XSS vulnerability.
In the first step, RainLoop fetches references to the
<body> nodes from the tree structure and then calls
saveHTML() on all children to get the sanitized HTML code without
In the next step, the attributes of the
<html> node are fetched and added to a newly created
<div> tag to simulate the
This process is repeated for the
<body> tag, but with an important difference: The
<div> tag that is created to preserve the
<body> attributes is created with the text content
___xxx___. This fake
<body> is then appended to the fake
<html> node and dumped to HTML code:
Let’s walk through this code with an example. Let’s assume an attacker sent the following email:
The process we described thus far would then yield the following HTML code, stored in the
In the final step, the rest of the email is inserted in the wrapping code above. This is done by replacing the
___xxx___ inside of the fake wrapping body with the previously generated HTML code:
This would finally yield the following HTML code:
As an attacker can control the attributes of a
<body> tag and their values, they could create a
<body> tag with an attribute value of
This could, for example, result in the following HTML markup:
str_replace() replaces the
___xxx___ string as many times as it can find, an attacker can insert controlled user input into the quoted value of the
data-some-attr. Let’s assume an attacker crafted an email as follows:
In this case, the HTML markup would result in the following after replacing
___xxx___ with the rest of the HTML code:
At the time of writing, no official patch is available. We recommend the RainLoop fork SnappyMail. It has great security improvements and is actively maintained. We would like to thank the maintainers of this fork for their quick response and analysis of this issue. They confirmed to us that they are not affected. For this reason, we recommend users of RainLoop migrate to SnappyMail in the long term.
To help in the short term, we encourage users to apply the following inofficial patch that we developed (please carefully use at your own risk):
In order to use this patch:
- Create a backup of your RainLoop files!
- Upload the patch file contents above to a file called
rainloop_xss.patchand store it in the root directory of your RainLoop installation
- Run the following command:
Please note that your path may vary, depending on the version of RainLoop you use. In the example above, version 1.13.0 is used. Make sure to use the correct version in your path.
|We request a security contact by contacting email@example.com. No response
|We request a security contact by creating a GitHub issue. No response
|We contact the vendor via email and the GitHub issue and inform them of our 90-day disclosure policy. No response
In this blog post, we analyzed a Persistent Cross-Site-Scripting vulnerability in RainLoop that triggers when a victim views a maliciously crafted email. The vulnerability occurred due to a logic bug after the sanitization process, which is often overlooked by security audits. We have found similar bugs in high-profile targets such as Zimbra and WordPress. In general, we recommend developers to not modifying any data after it has been sanitized, as any modification could reverse the sanitization step. Additionally, it is recommended to work with a DOM tree object, rather than operating on HTML text, as this leaves much more room for mistakes.