JWT
JSON Web Token
Last updated
JSON Web Token
Last updated
Read this article first:
JWT is similar to a session token, but they work differently:
A session token contains only the session ID. The client sents this session token to the server and server looks up this ID in its database. This backend database stores all the information regarding the user's session, for example, username, authorization, and etc.
A JWT contains the entire session object and it is stored on the client side. JWT consists of a signature so the server can verify its integrity.
To summarize, with session ID cookies, sessions live on the server, but with JWTs, sessions live on the client.
decode()
|| verify()
Many JWT libraries provide one method to decode the token and another to verify it:
decode()
: Only decodes the token from base64url encoding without verifying the signature.
verify()
: Decodes the token and verifies the signature.
Here decode()
does NOT verify the signature at all. Sometimes developers might mix up these methods.
If the none
algorithm is accepted by the server, then the signature won't be verified at all. That is, anyone can forge a malicious JWT and the server will accept it blindly. This is a dumb vulnerability, just disable the none
algorithm, please.
With symmetric encryption, a cryptographic signature is only as strong as the secret used.
If an application uses a weak secret, the attacker can simply brute-force it by trying different secret values until the original signature matches the forged one. Having discovered the secret, the attacker can use it to generate valid signatures for malicious tokens. To avoid this vulnerability, strong secrets must always be used with symmetric encryption.
JWT accepts both symmetric and asymmetric encryption algorithms. Depending on the encryption type, you need to use either a shared secret or a public-private key pair:
Algorithm | Key used to sign | Key used to verify |
Asymmetric (RSA) | Private key | Public key |
Symmetric (HMAC) | Shared secret | Shared secret |
When an application uses asymmetric encryption, it can openly publish its public key and keep the private key secret. This allows the application to sign tokens using its private key and anyone can verify this token using its public key. The algorithm confusion vulnerability arises when an application does not check whether the algorithm of the received token matches the expected algorithm.
In many JWT libraries, the method to verify the signature is something like verify()
which takes two arguments depending on user-specified algorithm:
verify(token, secret)
– if the user-specified algorithm is HS256
verify(token, public_key)
– if the user-specified algorithm is RS256
Unfortunately, in some libraries, verify()
does NOT check whether the received token is signed using the application's expected algorithm. Suppose the server uses RS256. If the public key is accessible within the application, an attacker can forge malicious tokens by:
Changing the algorithm of the token to HS256
Tampering with the payload to get the desired outcome
Signing the malicious token with the public key found in the application
Sending the JWT back to the application
The application expects RSA encryption, so when an attacker supplies HMAC instead, the verify()
method will treat the public key as an HMAC shared secret and use symmetric rather than asymmetric encryption. This means that the token will be signed using the application’s non-secret public key and then verified using the same public key.
To avoid this vulnerability, applications must check if the algorithm of the received token is the expected one before they pass the token to the verify()
method.
In RSA || HMAC Confusion, the attacker needs the public key to exploit the vulnerability. What if the attacker does not have the public key? Bad luck, it is still vulnerable. Read this article to learn more: