Imagine for a moment, the most exciting day for a product launch, main features developed, hearts palpitating, shoulders have been cried upon, many nights have been rushed working, product managers are exhausted, everybody is heavy, tears of joy rushing from their eyes…three, two, one and….Launch!
The product is live as a SaaS service… A sigh has been communally shared, everything works, the customers can login, they see the shiny menu that your new UX designer has worked so hard to create. A day passes, two, a week…and then something weird happens: Some customers complain that the data displayed on their dashboard is incorrect, some users have been added to their organization or some data is missing, but what could be the culprit?
In the case that the product uses JWT (JSON Web Tokens) for authentication, there could potentially be multiple issues if not implemented correctly. In this article we will try to address JWT and its implementation in the following sections.
JSON Web Tokens (Shortened JWT) are a method of transferring small structured parts of information between parties in a trusted or secured manner. Trusted meaning signed by a verified public key versus Secured meaning encrypted by a known secret key.
These JWT tokens are most commonly used as either authorization mechanisms or encryption methods, we will focus on the authorization part in this document as this is the scenario that is most interesting to us (and is actually the most prevalent).
Let’s assume for the sake of our discussion that we have Jimmy, he is a user of our SaaS application; Jimmy starts his day by logging in to the application, his credentials are being verified by either a local users repository or a third party provider, the authentication succeeded and now Jimmy receives a JWT Token which looks like this:
Looks fun doesn’t it? Oh, wait, it’s encoded, let’s break it down for a second, in decoded form it looks like this:
The token is split into three parts:
- The header: defines what type of token it is and what algorithm is used to sign the validity of the token
- The payload: which will contain information about who the token was signed for, the role or the authorization of the user, the identity of the user, the timestamp of the token, etc.
- The verify signature part: this will include the signature of the token, and the secret if needed.
This token is passed from the application to the user and is usually submitted back in the Authorization header, this way the application verifies the authenticity of the token and checks the user role / identity that will have access to the application.
The server signs the token with its own key, that way it can verify that the contents are not modified and that the token was indeed issued by the server.
The application flow will look something like this:
Now that we know what the regular mechanism of JWT looks like, let’s try to see what can (and a lot of times can) go wrong, there are multiple ways to attack the application that uses JWT tokens, let’s assume that our token looks like this (after the decoding that is):
The attacker will want to access the application, maybe change the permissions or impersonate a different user, our only method of checking the validity of the token is the strength and the security of the signing key. For example if the attacker will modify the Role field to Admin in the application and the application doesn’t re-check the role, the user can access Administrative functions, as long as the signature is valid, at least we can be sure that the token is valid and was not manipulated before sending back to the application.
Some attack vectors in this scenario will try to address the header first:
- Modifying or downgrading the Algorithm of the token: setting the Alg field to none, if the application allows it, will mean that there is no signature, thus allowing the attacker to change every field of the token and impersonate anyone, attacker can also try to downgrade the algorithm to a weak algorithm will allow the attacker to Brute force the key to the token and sign his own tokens instead.
Modifying header parameters: changing fields like JWK, JKU or KID, allow the attacker to either refer to a different signing key that will be used for token verification or to a blank key, for example can use his own key to sign the token and embed it into the JWK header or refer the server to the URL of the key used to sign using the JKU and submit self signed tokens to the application.
Everything but the kitchen sink
If an attacker can create or sign false tokens to the application, this effectively means that the authorization mechanism is basically broken, he can impersonate any user or role within the application thus gaining access and performing any damage that he wants with the impersonated user’s identity.
So, what can be done about it? Let’s put it into the Do’s and Don’ts of using JWT tokens:
- Rotate signing keys periodically, so that if the key is exposed the tokens that are signed will be exposed for a short time, minimizing the threat.
- Secure your keys, don’t store them in the application, better to store them in a cloud KeyVault / KMS.
- Verify the role signed in the token vs the user repo.
- Verify the Signing key and URL, don’t allow URLs from locations that you don’t control in the JKU
- Set the token expiration to the shortest time required, don’t issue tokens that have long expiration times
- Trust external JKU locations
- Allow KID (Key Identifiers) to be set for inexistent keys
- Try to write your own frameworks to decrypt keys
- Try to use homegrown Encryption algorithms
- Use weak keys or allow to downgrade the Hashing algorithms