How to use .innerHTML safely

OpenJavaScript 0
Reading Time: 3 minutes 🕑

Last updated: September 27, 2022.

The .innerHTML method is extremely useful for setting or getting the HTML inside of an element. With it, we can write HTML directly in JavaScript and have it rendered as such to the DOM.

But this usefulness is the source of a security risk: if used in certain situations, .innerHTML can allow malicious actors to run JavaScript directly on your website. This can enable access to your backend server and any sensitive information stored there, such as user data and passwords. In another scenario, a malicious script can be placed on your website and run every time a user accesses it, exposing your users directly to a security risk.

In this article, we first look at how .innerHTML can be exploited in these ways. Following this, we look at several options and workarounds to keep your server and users secure.

⚠ How .innerHTML can make your site vulnerable

The risk of using .innerHTML is that it can allow someone to run JavaScript on your website. How can this occur?

The vulnerability occurs when there is an opportunity for user input. For example, consider the following live code example:

See the Pen Untitled by James (@openjavascriptadmin) on CodePen.

The content of the input text field is printed beneath it when the user clicks submit. There are many use cases for this: chat apps, social media and profile editing.

Now, instead, of entering an innocent ‘about me’ text, enter the following: <img src="https://unsplash.it/200/200" onload="alert('User-run JavaScript!');" />.

And just like that, JavaScript is being run from the host website. In this case, we only create an alert. But the point here is what could be run.

Vulnerability #1: Access to the backend server

A fetch request could be made to the backend server that would not be rejected as foreign because it is being made from the host website. This provides a gateway for access to everything stored on your server.

Vulnerability #2: Malicious script targeting the end-user

The opportunity for user-input could be use to place a script on the page that will run every time the page is accessed in the future. This script could scrape user information from the browser, including sensitive user information such as usernames and passwords.

Solutions

These scenarios should be enough to give any webmaster running a website with user-input possibilities a fright. In most cases, removing user interaction from your website is unthinkable.

Thankfully, there are solutions that can protect your website and users from this vulnerability.

✅ #1: Use .textContent or .innerText where possible

When rendering text only to the DOM (e.g. as in our live code example) it is not necessary to use .innerHTML. Instead, we can use other methods available to us to write content to the DOM: .textContent and .innerText. When using these, if HTML is included in the input, it will not be rendered as such, but as text only.

For example, below we modify our live code from earlier so that the user input is rendered to the DOM using textContent. Now, try again entering in the text input field: <img src="https://unsplash.it/200/200" onload="alert('User-run JavaScript!');" />.

See the Pen Untitled by James (@openjavascriptadmin) on CodePen.

It renders as pure text to the DOM now and no script is run! The same result would be achieved is we used innerText as well.

So the first solution is to avoid using .innerHTML where .textContent or .innerText could be used.

✅ #2: Use a HTML sanitizer if .innerHTML is necessary

However, there may be some cases where we have user input and we want it to be rendered as HTML. For example, so that a HTML emoji is displayed.

In this case, we should use a HTML sanitizer such as dompurify.

For this tutorial, we import dompurify into our project by including the CDN link in the head of our HTML. Then, in our JavaScript file, we wrap whatever we want to write as HTML to the DOM inside the call DOMPurify.sanitize().

Let’s apply this to our live code example. This time, let’s enter the potentially malicious code snippet and some HTML emojis: <img src="https://unsplash.it/200/200" onload="alert('User-run JavaScript!');" /> 😜 😝.

See the Pen Untitled by James (@openjavascriptadmin) on CodePen.

Now, it is run as HTML, with the image and emojis being displayed. But the JavaScript that should run upon the loading of the image no longer does so. We have effectively sanitized the user-input HTML.

Conclusion: Is it still okay to use .innerHTML?

Yes! There are plenty of use cases where it is perfectly safe to use .innerHTML to render content to the DOM. For example, .innerHTML is very useful and safe when rendering static content (and dynamic content you are completely in control of) to the DOM.

However, the security risks associated with .innerHTML occur when this is used when working with user input. In these cases, we recommend the following two steps outlined in this article: using .textContent or .innerText over .innerHTML when they can achieve the same outcome; if .innerHTML is necessary , employ a HTML sanitizer so purify user input.