Sep 30, 2026·5 min read·5 visits
An unauthenticated remote attacker can crash Python web servers using PyJWT by transmitting a token with thousands of nested JSON brackets, triggering an uncaught RecursionError and resulting in a Denial of Service (DoS).
A Denial of Service (DoS) vulnerability exists in the PyJWT library when parsing unverified token payloads containing deeply nested JSON structures. Because PyJWT fails to catch RecursionError during payload parsing, an unauthenticated remote attacker can crash the application thread or worker by sending a specially crafted token.
PyJWT is a Python implementation of the JSON Web Token (JWT) standards. The vulnerability resides in the pre-verification payload parsing routines of PyJWT. Specifically, when applications perform dynamic signing key lookups (such as using PyJWKClient.get_signing_key_from_jwt) or decode tokens with signature verification explicitly disabled (verify_signature=False), PyJWT decodes and parses the token's payload prior to validating its cryptographic signature.
This architecture creates an unauthenticated attack surface. An attacker can submit an arbitrary payload that forces parsing operations before cryptographic validation is performed. This issue is classified under CWE-248 (Uncaught Exception) and CWE-400 (Uncontrolled Resource Consumption), allowing an unauthenticated remote attacker to trigger a Denial of Service (DoS) against applications utilizing vulnerable versions of PyJWT.
The root cause of this vulnerability lies in PyJWT's failure to catch the native RecursionError raised by CPython's standard library JSON parser during payload deserialization. The default parser, json.loads, uses a recursive-descent parsing algorithm to process nested structures. Each layer of nested arrays ([ or ]) or objects ({ or }) increases the depth of the interpreter's call stack.
CPython enforces a hard execution call stack depth limit, typically set to 1,000 frames. When the standard JSON parser encounters structures with nesting that exceeds this threshold, the interpreter halts execution and raises a native RecursionError to prevent process-level stack overflow. PyJWT wrapped the parsing statement json.loads(decoded["payload"]) in a try-except block configured to catch only ValueError. Because RecursionError inherits from RuntimeError rather than ValueError, the exception bubbles up through the application execution context and crashes the thread or process handling the request.
The vulnerability exists in jwt/api_jwt.py within the helper function _decode_payload. Below is a code block illustrating the vulnerable implementation alongside the subsequent patch implemented in version 2.15.0.
# Vulnerable Code in PyJWT < 2.15.0
def _decode_payload(self, decoded: dict[str, Any]) -> dict[str, Any]:
try:
# Highly nested payload structures raise a RecursionError here
payload: dict[str, Any] = json.loads(decoded["payload"])
except ValueError as e:
# Only ValueError (and its subclass JSONDecodeError) is intercepted
raise DecodeError(f"Invalid payload string: {e}") from e
if not isinstance(payload, dict):
raise DecodeError("Invalid payload string: must be a json object")The fix updates the exception target to handle both ValueError and RecursionError. This converts interpreter-level stack crashes into structured, catchable application-level exceptions.
# Patched Code in PyJWT >= 2.15.0
def _decode_payload(self, decoded: dict[str, Any]) -> dict[str, Any]:
try:
payload: dict[str, Any] = json.loads(decoded["payload"])
except (ValueError, RecursionError) as e:
# RecursionError is now safely caught and wrapped
raise DecodeError(f"Invalid payload string: {e}") from e
if not isinstance(payload, dict):
raise DecodeError("Invalid payload string: must be a json object")This adjustment ensures that payload parsing errors do not cause the entire process or execution worker to terminate.
To exploit this vulnerability, a remote attacker generates a JWT with a payload containing deeply nested JSON arrays or objects. Because payload parsing occurs before signature verification, the token's cryptographic signature block can be left empty or contain dummy bytes. No secret keys or credentials are required.
The typical attack workflow is described below:
By repeatedly transmitting this payload to endpoints that perform dynamic key lookup or unverified decodes, an attacker can consume and crash all available application worker threads, leading to complete service exhaustion.
The primary impact of this vulnerability is service degradation or complete Denial of Service. In synchronous web frameworks (such as Django, Flask, or WSGI deployments), crashing a worker thread with an unhandled exception interrupts active client connections and drops active tasks. In asynchronous runtimes (such as FastAPI or Sanic), unhandled errors in async paths can disrupt the entire event loop.
The exploit is highly asymmetric. A payload containing 20,000 open brackets occupies roughly 40 kilobytes of space, yet it is sufficient to exhaust the execution stack on a default CPython environment. This allows an attacker with minimal network resource constraints to impair the availability of identity management and API gateway systems.
The primary remediation step is upgrading PyJWT to version 2.15.0 or higher, which catches the stack exhaustion exception and raises a standard DecodeError instead.
In environments where upgrading the library is not immediately possible, security teams should implement pre-verification middleware to restrict payload depth. The following filter validates token string structure prior to parsing:
import base64
def validate_jwt_nesting_depth(raw_token: str, max_depth: int = 50) -> bool:
parts = raw_token.split(".")
if len(parts) < 2:
return False
try:
# Safely pad and decode base64 payload bytes
payload_bytes = base64.urlsafe_b64decode(parts[1] + "==")
depth = 0
for byte in payload_bytes:
if byte in (91, 123): # '[' or '{'
depth += 1
if depth > max_depth:
return False
elif byte in (93, 125): # ']' or '}'
depth = max(0, depth - 1)
return True
except Exception:
return FalseEnforcing rate limiting on public-facing endpoints and establishing restrictive HTTP request timeout parameters will also mitigate exploitation attempts.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
pyjwt jpadilla | >= 2.0.0a1, < 2.15.0 | 2.15.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-248 / CWE-400 |
| Attack Vector | Network (AV:N) |
| CVSS Score | 5.3 (Medium) |
| EPSS Score | 0.00291 (Percentile: 19.51%) |
| Impact | Denial of Service (DoS) |
| Exploit Status | Proof-of-Concept |
| KEV Status | Not Listed |
The software does not explicitly handle or catch an exception that can be thrown within its execution block, resulting in sudden termination of the program or thread.
A security vulnerability in serialize-javascript v7.1.1 allows Cross-Site Scripting (XSS) due to an overly greedy regular expression (SCRIPT_CLOSE_REGEXP) used during function serialization. Two secondary defects involving a spoofed toString() validation bypass and a stateful native code validator are also addressed in the fixed version v7.1.2.
An improper output encoding and escaping vulnerability (CWE-116) in Vercel Satori allows unauthenticated remote attackers to perform markup injection in dynamic Open Graph images generated via Next.js's ImageResponse. Unsanitized parameter interpolation into SVG elements breaks XML structural boundaries. This exposes downstream parsing, rasterization, and rendering pipelines to Server-Side Request Forgery (SSRF), Local File Read, and Remote Code Execution (RCE).
An uncontrolled recursion vulnerability exists in PyJWT from version 2.13.0 to 2.14.0. The vulnerability allows remote, unauthenticated attackers to cause a Denial of Service (DoS) via crafted JWT headers that trigger stack exhaustion during JSON decoding.
A signature verification bypass vulnerability in PyJWT allows unauthenticated remote attackers to forge JSON Web Tokens when processing JSON Web Key Sets containing an empty symmetric key.
A Regular Expression Denial of Service (ReDoS) vulnerability exists in Nodemailer's addressparser fallback engine before version 10.0.6. Under specific malformed inputs with excessive word boundaries, the parser exhibits quadratic backtracking, leading to high CPU utilization and event loop blockage.
Nodemailer versions prior to 10.0.9 are vulnerable to a parser differential bug. When processing a quoted local-part followed by an RFC 5322 comment and trailing characters, the internal addressparser module fails to order its normalization routine correctly. This error results in the generation of malformed envelope recipient addresses containing injected whitespace and secondary domains, allowing attackers to bypass routing restrictions and exfiltrate sensitive emails.