From 02d0a042d5c4595067fa89c57d77326889b3d7a9 Mon Sep 17 00:00:00 2001 From: chan770 Date: Fri, 28 Aug 2026 22:02:55 -0700 Subject: [PATCH] Do not crash on a JWT with a non-base64url signature (fixes #6101) JWT_REGEX accepts a signature segment of any length and parseJWT only validates the header/payload, so a value that matches the JWT pattern but carries a malformed/truncated signature (e.g. 41 base64url characters, which is length % 4 == 1 and thus impossible base64) reached crackHMAC. There decodeBase64(signature) let binascii.Error propagate, aborting the whole run with an unhandled exception during checkJWT(). Guard the signature decode: a signature that is not valid base64url cannot be an HMAC we could verify, so crackHMAC now treats it as not crackable (returns None) instead of raising. The other auditJWT findings are unaffected. Added a regression doctest. --- lib/utils/jwt.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/utils/jwt.py b/lib/utils/jwt.py index 88b5490c92c..c000d839b05 100644 --- a/lib/utils/jwt.py +++ b/lib/utils/jwt.py @@ -89,6 +89,8 @@ def crackHMAC(token, secrets, limit=None): 's3cr3t' >>> crackHMAC(token, ["admin", "letmein"]) is None True + >>> crackHMAC("eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.x", ["secret"]) is None + True """ data = parseJWT(token) @@ -97,7 +99,13 @@ def crackHMAC(token, secrets, limit=None): fn = HMAC_ALGORITHMS[data["header"]["alg"].upper()] signingInput = getBytes(data["signingInput"]) - target = decodeBase64(data["signature"], binary=True) + + try: + target = decodeBase64(data["signature"], binary=True) + except Exception: + # a signature segment that is not valid base64url (e.g. a truncated/malformed token that still + # matches the JWT pattern) cannot be an HMAC we could verify, so it is simply not crackable + return None for index, secret in enumerate(secrets): if limit is not None and index >= limit: