Alok Menghrajani

Security engineer at Square. Previously co-author of Hack and put the 's' in https at Facebook. Maker of CTFs.

Home | Contact me | Github | Twitter | Facebook

Web applications sometimes need to render a piece of HTML that has been supplied by the users. This happens for example when dealing with content ediable/rich text editors or 3rd party integration (ads, games, etc.).

The web security risk with having user supplied HTML in a page is obvious: if the page fails to properly strip all scripts, a malicious user will be able to run arbitrary javascript and hijack the user experience (i.e. the page will be vulnerable to XSS).

This page presents a a robust way to sanitize user supplied HTML and CSS in ~100 lines of JavaScript.

Traditional approaches

A perfectly safe way to isolate user supplied HTML is to enable a strict CSP ruleset, render the content in an iframe or host the entire page on a sandbox subdomain.

In some cases, these isolation methods aren't flexible enough and web developers need a way to sanitize the user supplied HTML. Writing a parser works but can be tricky: you need to handle the complexity of the HTML specification yet only allow a whitelist of tags.

Leveraging the browser

We can however let the browser parse the user supplied string (something browsers are really good at doing) and then recursively sanitize the DOM tree before attaching the content to the page.

I believe this approach is very robust for two reasons: we are manipulating DOM nodes instead of strings and there is no risk of "time of check time to use" bugs because the same browser is used to parse the HTML and to render the sanitized string.

Given that strings like <sc%00ript> can end up trigger a XSS on a subset of browsers, any method which involves parsing user supplied strings into a tree and then returning a serialized version of a subset of the tree is going to be more bullet-proof than code trying to directly sanitize a string. Failing to write a proper parser is one of the reasons FBML was riddled with security bugs.

Finally I like the approach of walking the DOM tree because it's simple and can be implemented in a small amount of JavaScript.

Demo

Below is a demonstration of this method. The input field lets you enter arbitrary HTML but only keeps <a>, <img> <p>, <div>, <span>, <br>, <b>, <i> and <u> tags. In addition, the sanitizer allows setting border, margin and padding CSS properties.

Can you get the page to run alert(1)?


Output as string:



  

Output as html:

Code



Next steps and links

If people find this sanitizer useful I'm going to turn it in "production" code by adding some tests, getting it peer reviewed and packaging it into a NPM.

Credits to Erling for pointing out the need for document.implementation.createHTMLDocument()!