# PyJWT BOM Bypass

PyJWT 2.13.0's JWK guard used bytes.lstrip(), so a UTF-8 BOM-prefixed RSA public JWK slipped past detection and was accepted as an HS256 secret, enabling token forgery.

- Payout: Research
- Severity: high
- Program: PyJWT
- Found by: wnsgurd90-keke
- Disclosed: 2026-09-29
- CVE: CVE-2026-102272
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-r6x4-923q-g947
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-r6x4-923q-g947
- Tags: area:oss domain:oss impact:ato kind:research platform:ghsa sev:high tech:jwt tech:python technique:incomplete-fix technique:parser-differential vuln:auth-bypass vuln:crypto vuln:signature-replay

**Target:** PyJWT HMACAlgorithm.prepare_key() raw-JWK validation guard
**Root cause:** The CVE-2026-48526 fix detected asymmetric JWKs via `key_bytes.lstrip().startswith(b"{")`, but bytes.lstrip() strips only ASCII whitespace, so a UTF-8 BOM prefix defeats the check and RSA public key bytes are accepted as an HMAC secret.
**Steps:**
1. Attacker obtains the target's RSA public key JWK (e.g. from `/jwks.json`).
2. Prepend a UTF-8 BOM (`\xef\xbb\xbf`) before the JSON opening brace of the JWK bytes.
3. Sign a JWT with HS256 using the BOM-prefixed JWK bytes as the HMAC secret, setting arbitrary claims (role, sub).
4. Submit the forged token to a PyJWT 2.13.0 verifier configured with `algorithms=["HS256", "RS256"]` holding the same BOM-prefixed key bytes; the JWK detection block is skipped and the token verifies.
**How found:** Patch-bypass review of the CVE-2026-48526 guard: noticed the JWK detection relied on `bytes.lstrip()` + `startswith(b"{")`, and reasoned that lstrip() only removes ASCII whitespace, so any non-whitespace prefix such as a BOM would prevent the `{` match and skip the asymmetric-key rejection.
**Exploitation:** Unauthenticated attacker forges HS256 tokens with arbitrary claims against a verifier using a mixed algorithm set and a raw BOM-prefixed RSA public JWK as key, achieving full auth/authz bypass (C:H/I:H). Requires AC:H preconditions: known public key plus mixed-algorithm config.
**Insight:** When auditing fixes that sniff key/input type by byte prefix, enumerate every encoding variant the downstream parser accepts (BOM, UTF-16/32, leading non-whitespace) — a prefix check that disagrees with the actual JSON parser is a bypass; prefer parsing and inspecting `kty` over byte matching.

