What are JWT Tokens – and how to ‘hack’ them
JSON Web Tokens – or JWTs – are a common method of providing authentication and authorisation to a web application.
While they may seem complex, it is possible to look closely and break down the structure of these three-part tokens, to understand the different ways we can attack them during a penetration test.
What are JWTs
A JSON Web Token (JWT) is a string of data that has been created in a standardised format in a way to send data between two systems.
Used by websites, a typical use-case for JWTs is to control authorisation to access different functionality, based on a user’s role or privilege level.
These tokens differ from traditional cookies as they are stored on the client device and hold all the appropriate data for a server to understand authentication and authorisation. This means that the server does not need to manage a list of live session tokens and the permissions associated with each account.
The format of a JWT token:
JWTs are made up of three parts each separated by a full stop.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjY2OWI4MjBiMmZlMjQyMDAxMjc3NzBkOCIsInVzZXIiOiJkYW4iLCJyb2xlIjpbInVzZXIiXSwiaWF0IjoxNzIxNzYzMTEyfQ.8ub0JOEMx-jpuKFgvz5exwxei43NKfOIv6FJR7-pyTY.
Header: contains metadata about the JSON Web Token
Payload: contains application specific data that is used by the site
Signature: contains a signature that is created by hashing the value of the header, payload and a secret key.
How can we understand JWTs?
The power of a JWT is provided by the signature that is used to confirm that data has not been modified or tampered with between the client and server.
As such, the header and payload are actually easy to understand. Both can be simply base64 decoded.
Header
Header value: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Decoded data: {“typ”:”JWT”,”alg”:”HS256″}
Type (typ): This shows the type of token being used. For JWTs it will obviously have the value JWT.
Algorithm (alg): This shows the algorithm that is used to sign the token and generate the signature. HS256 (HMAC-SHA256 is a common algorithm for JWTs to use)
Payload
Payload value:eyJpZCI6IjY2OWI4MjBiMmZlMjQyMDAxMjc3NzBkOCIsInVzZXIiOiJkYW4iLCJyb2xlIjpbInVzZXIiXSwiaWF0IjoxNzIxNzYzMTEyfQ
Decoded data: {“id”:”669b820b2fe24200127770d8″,”user”:”dan”,”role”:[“user”],”iat”:1721763112}
The JWT payload data will be different for every application, depending how it is being used – some applications hold a lot of data in their payload, some hold a small amount.
Within our example token, we can see that the most valuable data is ‘user’ holding a username and ‘role’ holding a privilege level.
Signature
Signature value: 8ub0JOEMx-jpuKFgvz5exwxei43NKfOIv6FJR7-pyTY
The signature of a JWT cannot be simply decoded. That is because it is a hash value that is created by the following formula
signature = hash(secret, base64(header), base64(payload))
So anyone can read my JWT data?
The short answer is yes.
We can see that it is possible to understand the contents of any JWT, which is why it is recommended that sensitive data is not held within them.
Note that within our example token, a username and privilege level are held, but password information is not.
What makes JWTs secure is the fact that the signature stops people decoding and modifying the data, as the signature will no longer match and the server will reject the token.
How can we attack JWTs in a penetration test?
JWT vulnerabilities are typically caused by a lack of security consideration when implementing them.
Application developers may introduce vulnerabilities that lead to pentesters being able to either completely disregard the token’s signature, or rewrite payload data and forge a valid signature.
JWT ‘none’ signature
As seen, JWT headers contain information about the type of hashing algorithm that is used to generate a signature
{“typ”:”JWT”,”alg”:”HS256″}
Because the token itself declares how the signature has been created, and the server has no option but to trust it, attackers can attempt to change the algorithm used to generate a signature.
If we change the signing algorithm to ‘none’ we can create a new JWT that does not benefit from any kind of validation.
Our token
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjY2OWI4MjBiMmZlMjQyMDAxMjc3NzBkOCIsInVzZXIiOiJkYW4iLCJyb2xlIjpbInVzZXIiXSwiaWF0IjoxNzIxNzYzMTEyfQ.8ub0JOEMx-jpuKFgvz5exwxei43NKfOIv6FJR7-pyTY
Will change to
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0K.eyJpZCI6IjY2OWI4MjBiMmZlMjQyMDAxMjc3NzBkOCIsInVzZXIiOiJkYW4iLCJyb2xlIjpbInVzZXIiXSwiaWF0IjoxNzIxNzYzMTEyfQ.
Take note that the full stops remain. This new header, when decoded is:
{“typ”:”JWT”,”alg”:”none”}
This means that there is no need for a signature anymore but the server still needs to understand where the payload ends.
Forging signature
If it is not possible to completely remove the signature through use of the ‘none’ algorithm, we can attempt to crack the secret key (the only part of the hashing process that we don’t know).
This can be done with both John the Ripper and Hashcat with the following commands:
Hashcat -a 0 -m 16500 <JWT> <Wordlist>
John <JWT> –wordlist=<Wordlist> –format=HMAC-SHA256
Both of these tools will sign the header and payload, using each word of the wordlist as the secret key. If the signature matches, we have identified the key and can use this to forge new signatures.
The key used to sign our example JWT is 133777.
Armed with this information, we are now able to change the payload data, and create the correct signature, so that the server accepts our modified token.
Here are two simple ways to achieve this:
JWT.io – a website that can be used to decode token information and rewrite it.
Note the use of the secret key in the verify signature section. This allows me to change the user role to admin and create a correct signature
BurpSuite plugin JWT Editor – this can be found in the BApp store and installed on your instance of Burp.
Within the JWT Editor tab, we need to add a New Symmetric Key.
Once this has been added to Burp, we can intercept the requests that include the JWT token, change the user to an admin, and sign the new value.
This creates a valid JWT that has the modified payload data but has been signed with a forged signature that will be accepted by the server.
So, what are the pros and cons of JWTs?
Pros
- Secure: JWTs are signed to stop tampering. That means that as long as strong hashing algorithms are supported, and strong secret keys are used, it should not be possible to modify a token and have the server accept it.
- Stored locally: JWTs are stored on the client system, this means that the server does not need to use space holding session information.
Cons
- Non-revokable: because JWTs are stored on the client system and self-contained (they hold all the data they need about themselves), it can be difficult to revoke a JWT before it naturally expires. This is because it is not as simple as deleting a session token from a server’s database
- Dependent on secret key: the header of a JWT tells anyone who reads it what hashing algorithm is used, this means an attacker has all the information they need to try and crack the secret key. If a weak key is used, attackers are then able to forge signatures that will be accepted by the server.
- Signing algorithms: attackers can attempt to change the signing algorithm to a value like ‘none’ that negates the security.
Conclusion
So there you have it. JWT tokens can be used to provide security to applications, but they are far from infallible.
Misconfigurations that allow weak signing algorithms and bad secret keys can lead to attackers gaining access to areas of an application that should be restricted.
It is important to always check functionality that is used for access control to ensure the security of sensitive data and maintain integrity of an application.
Finally, to see some of this in action, take a look at our YouTube shorts on JWTs.
You might also want to check out the OWASP Top 10, for more information about the most critical risks to web applications.