Preventing Reflected XSS in Your React Application

Preventing Reflected XSS in Your React Application

Learn some basics about reflected XSS (cross site scripting) and how to prevent this common attack as a developer.

Cas Spicer · 2 minute read

What is a cross site scripting (XSS) attack?

An XSS attack is when a bad actor injects malicious code from the client side of your website or application that is designed to run on other user's browsers. It can be used to get session ID's or cookies (used for logging into secure applications), usernames, passwords, and more.

What is the difference between Reflected XSS and Stored XSS?

If you think of the browser as a mirror, reflected XSS is malicious code that the hacker 'reflects' off of one browser to one or more other browsers. Stored XSS is malicious code that's literally stored in the database, which will later run on a unknowing victim's browser.

Why does using the javascript InnerHTML property make my site vulnerable?

When building a website or application with vanilla javascript, I'd recommend avoiding using the innerHTML property at all costs. Use textContent instead, which converts data to a string. InnerHTML sets or gets HTML on your site or app. This means that if there is a <script></script> tag somewhere in there, some code might be executed, and that's a vulnerability.

React makes it easy to set text through JSX syntax, which offers automatic character escaping. Any time we put data within brackets (example: {someData}), React will automatically escape the characters in that data. An attacker can't so easily inject code there, as the special characters in the code will automatically be changed to strings of escaped (different) characters.

If you really need to use innerHTML, React will warn you about this data being stored in the DangerouslySetInnerHTML for that component. This indicates a potential attack vector for XSS. However, we can sanitize the data being saved in that attribute using npm's DOMPurify.

An Example of Reflected XSS

I added a text input field where the user can enter a 'test chat' on the handy hackable site I built for my SQL injection demo, the spongebob hackable chat. This input field uses innerHTML to render the users' text content to the screen!

XSS1

async function postMsg() {
    try {
        var search = await axios.post('api/postMsg', {
            text: inputMsg.value
        })

        const newMessage = search.data[1];
        console.log(newMessage)
        const newMsg = document.createElement('p');
        newMsg.innerHTML = inputMsg.value;
        newChatArea.append(newMsg);

        return search
    }
    catch(err) {
        console.log(err)
    }
}

If the user tries entering <script>alert('hacked!')</script> it will inject the code - I can see it's logging to the console after it comes back to the client from the server. However, it isn't running the code.

XSS3

However, we can inject <img src onerror="alert('hacked!')"> here which does run the code, as the blank src image element throws an error:

XSS2

As you can imagine, a bad actor won't usually run a simple 'alert' function on the victim's browser - they will likely inject malicious code here, designed to steal the end user's personal or login information.

I would encourage you to play around with this in your own projects to gain a little hands-on understanding of how this works. As always, use your code for good, folks!