Jun 15, 2026·8 min read·15 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()).
An authentication bypass in the SiYuan personal knowledge management system before version 3.7.0 exposes a dynamic icon rendering endpoint. This endpoint processes client-supplied Go template directives. By submitting a crafted request, an unauthenticated remote attacker can leverage registered database template functions to execute arbitrary read-only SQL queries and exfiltrate workspace contents.
CVE-2026-54069 is a critical authentication bypass vulnerability in the SiYuan Note personal knowledge management system. The flaw is located in the HTTP server's middleware handling API authorization, which unconditionally trusts requests carrying a 'chrome-extension://' scheme in the Origin HTTP header, granting administrative access without validating API tokens.
CVE-2026-54089 is a critical authentication bypass vulnerability in File Browser affecting instances configured with proxy-based authentication. An unauthenticated remote attacker with direct network access can impersonate arbitrary users or register new accounts by spoofing configured HTTP headers.
The malicious Cargo package 'exploration' was uploaded to the crates.io registry. During compilation or package import, the crate executes code designed to establish an outbound TCP/HTTP connection, download an external second-stage binary, and execute the binary locally on the host machine. This creates an unauthenticated remote code execution vector impacting developer environments and continuous integration pipelines.
CVE-2026-54088 is a critical command injection vulnerability in File Browser prior to version 2.63.6. When Hook Authentication is enabled, the application interpolates unsanitized credentials into a shell command, allowing unauthenticated remote code execution.
An authenticated remote code execution vulnerability exists in NotrinosERP (versions up to and including 1.0.0) within the Human Resource Management (HRM) module. Users with employee management permissions can upload arbitrary file types, including PHP scripts, which are written directly to a web-accessible directory. This allows for arbitrary code execution in the context of the web-server user.