# Authlib: empty signatures validation in JWS JSON deserialization

Authlib's JsonWebSignature.deserialize_json() accepts a JWS JSON object with an empty signatures array as verified, letting unauthenticated attackers forge arbitrary payloads without any key.

- Payout: Research
- Severity: critical
- Program: Authlib
- Found by: zer0d4y5, uziii2208, nguyenminhtuan28
- Disclosed: 2026-10-27
- CVE: CVE-2026-96760
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-6v38-4h7w-rj9h
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-6v38-4h7w-rj9h
- Tags: area:oss domain:oss impact:ato impact:integrity kind:research platform:ghsa sev:critical tech:jwt tech:python technique:source-review vuln:auth-bypass vuln:crypto

**Target:** JsonWebSignature.deserialize_json() JWS JSON Serialization parser
**Root cause:** `is_valid` is initialized to True and only set False inside the loop over `obj["signatures"]`; an empty signatures array iterates zero times, so the payload is returned as verified without consulting any key.
**Steps:**
1. Read `authlib/jose/rfc7515/jws.py` and note `is_valid = True` precedes the `for header_obj in obj["signatures"]` loop.
2. Craft a general JWS JSON object with `payload` set to base64url-encoded attacker claims and `signatures: []`.
3. Call `jws.deserialize_json(forged, key=None)` (or with a real key) and observe the payload returned as verified.
4. Control: genuine 2-signature object with correct key is accepted; corrupting a signature raises BadSignatureError; omitting the signatures member raises DecodeError — confirming the empty-array case specifically.
**How found:** Code reading of the installed 1.8.0 artifact: the verifier flag is initialized True before the signature loop, so the empty-array path never runs validation; confirmed with a differential test matrix (attack vs. positive/negative controls) and cross-checked against joserfc 1.7.5, which rejects the same object.
**Exploitation:** Any relying party calling deserialize_json() on untrusted input accepts an attacker-authored payload as signed; where the payload carries identity or authorization claims, an unauthenticated remote caller achieves full authentication bypass with no key material.
**Insight:** When auditing JOSE/JWT verifiers, look for a validity flag initialized to a permissive default and only flipped inside a loop over signatures/keys — an empty or zero-length collection then skips verification entirely; test with `signatures: []` and with the member absent, since they often take different code paths.

## Proof of concept (copied exactly from the original report)

**Forged JWS JSON with empty signatures array**

```
import base64
from authlib.jose import JsonWebSignature

def b64(b):
    return base64.urlsafe_b64encode(b).rstrip(b"=").decode()

jws = JsonWebSignature()
payload = b'{"sub":"attacker","role":"admin"}'
forged = {"payload": b64(payload), "signatures": []}

print(jws.deserialize_json(forged, key=None))
```

