Jun 15, 2026·8 min read·4 visits
Unauthenticated attackers can send JWTs with randomized KIDs to force connection errors on the target's upstream JWKS endpoint. A flaw in the error cleanup sequence then writes None to the cache, evicting all legitimate signing keys and preventing legitimate users from authenticating.
A logic flaw in PyJWT's PyJWKClient class allows remote unauthenticated attackers to trigger a complete authentication outage. By transmitting a volume of JWTs containing randomized, non-existent Key ID (kid) values, attackers force synchronous outbound JWKS resolution queries. When these queries fail or time out, a defect in the error cleanup code overwrites the local cache of valid signing keys with None, causing a denial of service.
The pyjwt library provides a widely adopted interface for encoding and decoding JSON Web Tokens (JWT) in Python. When deploying applications with asymmetric cryptography (such as RS256, ES256, or PS256), the verifying party requires access to the corresponding public key. The standard mechanism for dynamic key distribution is a JSON Web Key Set (JWKS), typically served over HTTPS by an Identity Provider (IdP) such as Auth0, Keycloak, or Okta. To optimize performance and reduce latency, the PyJWKClient class includes an in-memory caching mechanism (JWKSetCache) that stores retrieved keys, mitigating the need to query the remote JWKS endpoint on every token validation attempt.
A critical design choice in many JWT libraries, including legacy versions of pyjwt, is the parsing of the JWT header prior to cryptographic signature verification. The header contains metadata such as the Key ID (kid), which indicates which specific key from the JWK Set should be used to verify the signature. Because the kid is extracted from the unverified header, an attacker can manipulate this value arbitrarily. This exposes a significant attack surface: any incoming request containing a syntactically valid JWT, regardless of its signature validity, is processed by the verification pipeline up to the point of key retrieval.
CVE-2026-48524 describes a logical vulnerability in the handling of network errors during the retrieval of JWK Sets. When an unauthenticated remote attacker floods the target application with JWTs containing randomized, non-existent kid headers, the application attempts to resolve these keys by querying the remote JWKS endpoint. If these synchronous outbound queries fail—either due to rate-limiting by the IdP or socket timeouts—the cache-clearing logic in PyJWKClient is triggered. This behavior transforms a transient network error or rate-limiting event into a complete, application-wide authentication outage.
The root cause of CVE-2026-48524 lies in the exception-handling structure of the fetch_data method inside jwt/jwks_client.py. In Python, the finally block in a try-except-else-finally statement is guaranteed to execute regardless of whether an exception is raised, caught, or propagated. Prior to version 2.13.0, PyJWKClient.fetch_data utilized this construct to populate its local cache. The local variable jwk_set was initialized to None at the entry of the method.
During normal operations, a successful HTTP request populates jwk_set with the parsed dictionary containing the keys, which is then written to the cache via self.jwk_set_cache.put(jwk_set). However, if the outbound connection fails—due to connection reset, DNS failure, HTTP 429 rate limits, or read timeout—the interpreter raises an exception inside the try block. This exception is caught in the except block, which raises a PyJWKClientConnectionError. Before the exception propagates up the call stack to the caller, control is transferred to the finally block.
At this execution state, the jwk_set variable remains bound to its initial value of None. The conditional check if self.jwk_set_cache is not None: evaluates to true. Consequently, the interpreter executes self.jwk_set_cache.put(None). The cache backend implements a simple key-value replacement strategy. Writing None to the cache overwrites and effectively evicts the previously stored, valid JWK Set. This represents an instance of CWE-460 (Improper Cleanup on Thrown Exception) working in conjunction with CWE-755 (Improper Handling of Exceptional Conditions).
To understand the mechanics of the vulnerability and its remediation, we examine the logical transition between the vulnerable implementation and the patch introduced in version 2.13.0. The fundamental flaw was the placement of state-altering cache writes within the non-conditional cleanup path of the finally block.
# VULNERABLE CODE PATH (jwt/jwks_client.py)
def fetch_data(self) -> Any:
jwk_set: Any = None
try:
# Outbound network IO is initiated here
r = urllib.request.Request(url=self.uri, headers=self.headers)
with urllib.request.urlopen(
r, timeout=self.timeout, context=self.ssl_context
) as response:
jwk_set = json.loads(response.read().decode("utf-8"))
except Exception as e:
# Any network failure or timeout redirects here
raise PyJWKClientConnectionError(
f'Fail to fetch data from the url, err: "{e}"'
) from e
else:
return jwk_set
finally:
# EXPLOITATION POINT: This block always runs
# If an exception was raised, jwk_set remains None
if self.jwk_set_cache is not None:
self.jwk_set_cache.put(jwk_set) # Cache is overwritten with NoneThe remediation in version 2.13.0 completely refactors the control flow to isolate the cache-write operation from the exception handling sequence. By removing the finally block entirely, the library guarantees that the cache state is mutated only upon successful completion of both the network request and the JSON parsing routine.
# PATCHED CODE PATH (jwt/jwks_client.py)
def fetch_data(self) -> Any:
try:
r = urllib.request.Request(url=self.uri, headers=self.headers)
with urllib.request.urlopen(
r, timeout=self.timeout, context=self.ssl_context
) as response:
jwk_set = json.loads(response.read().decode("utf-8"))
except Exception as e:
raise PyJWKClientConnectionError(
f'Fail to fetch data from the url, err: "{e}"'
) from e
# REMEDIATION: Cache update occurs only after successful retrieval.
# If fetch fails, the exception is raised, and this block is bypassed.
if self.jwk_set_cache is not None:
self.jwk_set_cache.put(jwk_set)
return jwk_setWhile this patch completely resolves the cache eviction flaw (CWE-460), the design of PyJWKClient remains susceptible to network amplification. An attacker sending requests with random kid values can still force the server to initiate synchronous outbound connections. Applications utilizing PyJWKClient must configure appropriate rate limiting or implement caching strategies that penalize missing keys to prevent outbound connection exhaustion.
An exploitation scenario targeting CVE-2026-48524 does not require cryptographic material or valid user credentials. The attack leverages the target application's willingness to parse JWT headers from unauthenticated requests. The attacker must target an endpoint that relies on dynamic JWKS resolution using PyJWKClient.
The execution flow begins with the generation of automated requests containing JWTs with unique, randomized kid headers. The target application parses each header, notes that the kid is missing from the local cache, and issues a synchronous fetch request to the remote JWKS URI. This process is repeated across a high volume of concurrent threads or connections.
As the rate of outbound queries increases, the Identity Provider (e.g., Auth0 or Keycloak) detects the spike and applies rate-limiting policies, responding with an HTTP 429 Too Many Requests status code. Alternatively, the target application may exhaust its own outbound socket pool, resulting in network connection timeouts. In either case, an exception is thrown in the try block. The finally block executes immediately, writing None to the cache and wiping out the legitimate keys. Subsequent verification attempts for all users fail until a successful outbound request can be completed.
The impact of CVE-2026-48524 is classified as a Denial of Service (DoS) affecting the availability of the authentication layer. Because the vulnerability results in the eviction of legitimate public keys from the cache, any application thread trying to validate a standard user token is forced to perform a synchronous outbound request. If the upstream provider is rate-limiting the application, all incoming valid tokens will fail verification, causing a complete lockout of authorized users.
The vulnerability receives a CVSS v3.1 score of 3.7. The High attack complexity reflects the dependency on external environmental conditions, specifically the requirement that the upstream JWKS endpoint fails, rate-limits, or times out. An attacker cannot guarantee the precise timing of this failure, as it depends on network latency, connection pooling, and the rate-limiting thresholds of the specific Identity Provider.
From a business perspective, the consequences can be significant. If a critical service experiences a cache wipe, the authentication layer cannot recover until the upstream provider stops rate-limiting the application server. This creates a feedback loop: as long as the attacker continues to transmit invalid kid headers, the application will continue to trigger rate limits, extending the duration of the service outage indefinitely.
The primary remediation path is upgrading the pyjwt library to version 2.13.0 or higher. This version removes the vulnerable finally block and ensures that the cache is only updated when a network transaction is completed successfully. This prevents temporary network failures from corrupting the in-memory cache state.
If upgrading is not immediately feasible, organizations can implement several mitigation strategies. First, deploy a Web Application Firewall (WAF) or ingress controller rule to rate-limit requests to endpoints that parse JWTs. Second, implement an application-level middleware to validate the format of the kid header, discarding requests that do not match expected patterns (e.g., specific alphanumeric lengths or UUID formats used by the IdP).
To detect potential exploitation attempts, security operations teams should monitor application logging for high rates of PyJWKClientConnectionError exceptions. Additionally, network monitoring tools should track outbound connections to the JWKS endpoint. A sharp increase in outbound DNS requests or HTTP queries to the authentication provider's domain is a strong indicator of active exploitation or misconfiguration.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
pyjwt Jose Padilla | < 2.13.0 | 2.13.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-460 |
| Attack Vector | Network |
| CVSS v3.1 | 3.7 |
| EPSS Score | 0.00205 |
| Impact | Denial of Service (DoS) |
| Exploit Status | none |
| KEV Status | Not Listed |
The software does not clean up its state or resources when an exception is thrown, leading to inconsistent states (specifically, storing None in the JWKS cache when a network exception is thrown inside fetch_data()).
PyJWT versions 2.8.0 through 2.12.1 are vulnerable to an unauthenticated Denial of Service (DoS) attack. When verifying detached JSON Web Signatures (JWS) using the unencoded-payload option (RFC 7797, b64=false), the library eagerly decodes the payload segment before verifying the header configuration or the cryptographic signature. This behavior enables a remote, unauthenticated attacker to inject an arbitrarily large payload segment, triggering excessive CPU and memory resource consumption prior to signature validation.
Nodemailer prior to version 8.0.9 contains a security control bypass vulnerability. Transport-level configuration parameters designed to restrict local file system access and remote URL requests are not propagated to all content-resolution execution paths. This failure allows unauthorized local file inclusion and server-side request forgery when the application utilizes specific transports or processing flags.
GHSA-268h-hp4c-crq3 is a Carriage Return Line Feed (CRLF) injection vulnerability in the Nodemailer npm package affecting versions up to and including 8.0.8. The library allows arbitrary email header injection when parsing user-controlled comments within list headers (such as List-Unsubscribe or List-ID). This occurs because list headers bypass standard validation by utilizing an internal 'prepared' flag, causing unsanitized newlines to be emitted directly into the outgoing RFC822 mail stream. This exploit allows remote attackers to inject custom, unauthorized mail headers, disrupting signature checks, bypassing filters, or spoofing parameters.
A high-severity type-confusion path traversal vulnerability (CVE-2026-49982 / GHSA-7c78-jf6q-g5cm) exists in the node-tmp package version 0.2.6. The vulnerability allows remote attackers to bypass path validation checks by passing non-string data types such as Arrays or duck-typed Objects into options like prefix, postfix, or template. Because the library relies on the .includes() method without verifying the input type, standard array checks evaluate differently than string checks. Downstream string coercion subsequently restores the traversal sequence, allowing files and directories to be created outside the designated temporary directory root. This can result in arbitrary file writes and potential local file execution depending on application context.
CVE-2026-47347 is an open redirect vulnerability affecting multiple TYPO3 CMS versions. The issue resides in GeneralUtility::sanitizeLocalUrl, where an insufficient blocklist validation implementation fails to prevent browsers from normalizing malformed relative paths into external protocol-relative redirections. Attackers can exploit this to conduct phishing, session hijacking, or credential harvesting campaigns.
An authenticated backend user with access to the Recycler module in TYPO3 CMS can bypass write restrictions and restore soft-deleted records on pages or database tables they are not authorized to modify. This vulnerability resides in the core DataHandler class due to missing permission checks during 'undelete' operations.