I hoped this would never happen, but here we go. I have problems with managing to deliver an article for Wednesday not because I have so little time, but because I have no idea for an article. I’ve fooled around a little bit with network recon labs on Pentester Academy, I’ve fooled around python imports, but nothing really inspired me enough. So today only some very basic article on JWT tokens and known issues connected to them.
Disclaimer: this article is 90% based on this paper by Sekurak, but I’m really out of ideas and want to keep the flow, because otherwise I am going to stop writing at all. Sorry for that.
What is JWT?
JWT stands for JSON Web Token. The idea behind it is to store all details about user in token that they own and that is not stored in database. At this point a big advantage comes up: in case of database compromise no session ids can leak and if we have properly secured passwords (compare with part 1 and part 2 of my article on the subject) and therefore the risk of taking over users’ accounts is significantly lower.
The question that should come up right now: how do we ensure that we can verify a token that is not stored on our side?
How is JWT built?
JWT consists of 3 parts: header, body and signature. They are all generally JSONS coded into JWS or JWE. I’ll be dealing with more popular JWS. I am not going to go into much details, if you are interested look at the additional resources.
So, normally in header we will have the type of the claim and algorithm used to sign the token, any claims in body, though there are some predefined ones like expiration time or token id. This is handy when it comes to preventing access in actions like logging out user since we do not have any session on our side that could be deleted from database and all we can do is to blacklist a token (or user pointed at by the token). All these parts are encoded (encoded, not encrypted) with Base64Url algorithm.
If you want to play around with it a little bit, you can look at Flask-JWT library that has an example of usage or go to jwt.io where you can try to manually check what happens when you change certain contents of the token.
Now, the extremely important-the signature. It can be thought of as hash digest of header, payload and some secret that is only known by the server. The server is responsible for building the token and verifying it. If someone changes the body and does not know the content of the secret, they can’t obtain correct hash value and therefore the check will fail. At least it should be.
Known issues with JWT
One may think: “if I am responsible for pointing which hashing algorithm I want to use, what happens when I use none?” That’s correct, the RFC says that only HMAC SHA-256 and “none” must be implemented as hashing methods. If the server doesn’t whitelist what to use, it may be prone to bypassing the signature checking.
A similar issue that actually has historically happened was with a java’s library. If someone skipped the signature part, the check was simply passed.
Another problem, that I’ve mentioned earlier is connected to logging out users. Since we do not have any entries in the database that we can delete, we have to somehow blacklist their tokens, check expiration times etc. This can get complicated and I myself have seen such an issue in one of the projects that I’m developing and it was… Embarrassing.
Sometimes signatures can leak in a silly place like error message (yes, really).
At the end I’d like to say thank you to Sekurak team for writing something that finally allowed me to write this article and learn something more.
- Sekurak’s paper on the topic (Polish)
- RFC 7519 on JWT
- Flask-JWT library with example of usage
- auth0 blog article