Hackers can exploit your website to attack users if you’re not careful. They do it through a method called cross-site scripting (XSS).
XSS basically means the attacker gets your website to run their JavaScript. When they run their JavaScript, they can do a lot of harm. They can even use your site to steal your users’ usernames and passwords.
To prevent XSS attacks, you need to prevent other people from running JavaScript in your website. To prevent people from running JavaScript in your website, you need to sanitize your output.
How people can run JavaScript on your website
Attackers can run JavaScript on your website when you accidentally insert their JavaScript into the DOM.
They can send you JavaScript without your knowledge through two main ways.
Through the URL
Through a text field
Let’s go through an example and see how it works. Say you have a form with a text field and a submit button.
When the user submits the form, you reproduce what they wrote to the DOM.
const form = document.querySelector('form')
const output = document.querySelector('.output')
form.addEventListener('submit', event => {
event.preventDefault()
const input = event.target.querySelector('input')
const value = input.value.trim()
output.innerHTML = value
})
When an attacker has access to a text field, they can submit JavaScript through an attribute. Here’s one example:
<img src="x" onerror=alert("HACKED!")>
If you add this HTML into the text box, you’ll immediately see an alert that says “Hacked!”.
What happens is:
JavaScript adds the <img> tag into the DOM.
The <img> tag tries to load the image from “x”. This fails because x doesn’t exist.
When the loading fails, an error event will occur.
The onerror attribute handles the error event. This runs the attacker’s JavaScript code.
Sanitizing your output
Doctors and nurses wash their hands with hand sanitizers after touching a patient. They do this to prevent themselves (and other patients) from getting infected.
Loosely translated, sanitizing something means to disinfect it.
When we sanitize JavaScript, we remove ways that allow people to run their JavaScript on our site.
There are two ways to sanitize our output:
Use textContent
Use a sanitizer
Using textContent
textContent can only output text. If you use textContent, you won’t generate any HTML. If you don’t generate HTML, there’s no way to insert JavaScript.
form.addEventListener('submit', event => {
// ...
output.textContent = value
})
If you need to write HTML, you’d want to use a sanitizer instead.
Using a sanitizer
There are many sanitizer libraries on the internet. One example is DOMPurify.
Before you use DOMPurify, you need to include the library before your JavaScript file. The process looks like this:
<!-- Link to DOMPurify -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/1.0.7/purify.min.js"></script>
<!-- Your main JavaScript comes next -->
<script src="js/main.js"></script>
Once you have installed DOMPurify, you will be able to use a variable called DOMPurify. To sanitize your code, you use a sanitize method.
If you try adding <img src="x" onerror=alert("HACKED!")> to the input box, nothing will show up.
But if you look in the DOM, you’ll see an <img> tag with an src attribute that points to x. The <img> tag is present, but the onerror attribute is missing. DOMPurify removed it.
Exercise
Sanitize the following strings. What are their sanitized outputs?
<img src=x onerror=alert(1)>
<svg><g/onload=alert(2)></svg>
<iframe src=jAva	script:alert(3)><iframe>
Note: You can learn more about Cross-site scripting here.