loading...

Reflected XSS attack on localStorage

tappyy profile image Andy ・3 min read

We've all seen the debates about localStorage vs. cookies when it comes to handling client-side JWTs. You may choose to store your JWTs in one or the other depending on which article you read. But what does an XSS attack actually look like?

XSS Overview

The Open Web Application Security Project (OWASP) defines XSS as:

Cross-Site Scripting (XSS) attacks are a type of injection, in which malicious scripts are injected into otherwise benign and trusted websites.

In other words, attackers can use the features of your site to inject malicious Javascript. It's important to note that any client-side Javascript has access to localStorage, sessionStorage and cookies (non-HttpOnly).

Example

I'm going to use a simple error page that users are redirected to if they encounter an general error. I've seen this used many times (hopefully a little better than what I'm about to show!)

Note: Let's assume that our site authenticates users via JWT and stores them in localStorage.

Here's our beautiful error page:

Error page example

It accepts code and message parameters to display in the page like so:

.../error.html?code=500&message=Something%20went%20wrong

The code that handles displaying the message looks like:

const params = new URL(document.location).searchParams
const errorCode = parseInt(params.get("code"))
const errorMessage = params.get("message")

document.getElementById("error-code").innerHTML ="Error code: " + errorCode
document.getElementById("error-message").innerHTML = errorMessage

Can you spot the mistake? 😏

We are getting the error message from the URL and placing it into our document HTML... 🤔

What would happen if an attacker was to try to inject some Javascript instead of a message?

XSS Success

Uh-oh! This confirms to the attacker that this page is vulnerable to an attack called Reflected XSS.

With some creativity, it's not a huge leap to get the contents of your local storage (which includes your JWT) and send it off to the attacker... bye bye token!

bye bye token

Once the attacker has your token, it's trivial to reveal all information stored in that token. They are just base64 encoded objects.

Solution

The main issue with our code is that we are getting the message string from the URL and inserting it directly into our document HTML. Instead, we should:

  1. Sanitise anything that could come from the user (including URL parameters).
  2. Use .textContent instead.

A good tip is Don't store anything in the JWT you wouldn't already consider public. This way, even if your site happens to be vulnerable to XSS, the attacker isn't gaining any private information.

Conclusion

There is nothing wrong with storing JWTs in localStorage. The issue is with poor coding practices that have the potential to expose your site and users to attack.

Granted, this was a simple (and contrived) example of reflected XSS, but there are other DOM-based attacks your app may be vulnerable to.

It's fun to break things you're working on and see if you can patch any vulnerabilities before they make it out!

Here are some good places to learn more:

Have fun! Thanks for reading! 😃

Posted on by:

tappyy profile

Andy

@tappyy

Your friendly neighbourhood software engineer.

Discussion

markdown guide