# REPRO-2026-00139: libjwt: JWT algorithm-confusion authentication bypass via RSA JWK without alg ## Summary Status: published Severity: critical Type: security Confidence: Unknown ## Identifiers REPRO ID: REPRO-2026-00139 GHSA: GHSA-q843-6q5f-w55g CVE: CVE-2026-44699 ## Package Name: libjwt Ecosystem: c Affected: >= 3.0.0, <= 3.3.2 Fixed: 3.3.3 ## Root Cause # Root Cause Analysis: CVE-2026-44699 / GHSA-q843-6q5f-w55g ## Summary `libjwt` versions 3.0.0 through 3.3.2 suffer from an algorithm confusion attack that allows JWT forgery using only a public RSA JWK. When an RSA JWK lacking an `alg` parameter is supplied as the verification key for an HMAC-signed token (`HS256`/`HS384`/`HS512`), the library incorrectly accepts the RSA key for HMAC verification. Because the internal `jwk_item_t` union shares storage between `provider_data` (an RSA `EVP_PKEY*`) and `oct.{key,len}` (HMAC bytes/length), the HMAC path reads a zero-length key and produces a successful verification for any token signed as `HMAC("", header.payload)`. An attacker who knows only the published JWKS can forge valid tokens and bypass authentication. ## Impact - **Package**: `libjwt` (C library for JWT processing) - **Affected versions**: 3.0.0 through 3.3.2 (inclusive) - **Fixed version**: 3.3.3 - **Risk level**: Critical (CVSS 3.1 base 9.1) - **Consequences**: Full authentication bypass. Any attacker with access to the public JWKS (which is public by definition) can forge HMAC-signed JWTs that the vulnerable library accepts as valid. ## Root Cause The root cause is a failure to validate that a JWK's actual key type (`kty`) is compatible with the JWT algorithm being used for verification. The library previously relied on the optional `alg` parameter in the JWK to determine compatibility. When `alg` was absent (`JWT_ALG_NONE`), `__setkey_check()` in `jwt-common.c` allowed the key to be used with any caller-specified algorithm. Because `jwk_item_t` is implemented as a union, setting an RSA key populates `provider_data` while leaving the HMAC `oct` fields uninitialized (effectively zero-length). When the HMAC verification backend later runs, it reads `oct.key` and `oct.len`, producing an HMAC with an empty key. Since `HMAC("", data)` is deterministic, an attacker can pre-compute the signature and forge a valid token. ### Fix commit - Commit `49c730a` in the libjwt repository: "Fix algorithm confusion that allows JWT forgery via RSA JWK as HMAC key" - The fix introduces `jwt_alg_required_kty()` which maps each JWA algorithm to its required JWK key type (e.g., `HS256` requires `kty=oct`). - `__setkey_check()` now rejects any setkey/verify callback that pairs a non-none algorithm with an incompatible `kty`. - `__verify_config_post()` adds a defensive re-check once `jwt->alg` is bound from the token header. - `__check_hmac()` adds a final backstop refusing any non-oct key for HMAC algorithms. ## Reproduction Steps 1. Execute `repro/reproduction_steps.sh` 2. The script builds two versions of `libjwt`: - `v3.3.2` (vulnerable) - `v3.3.3` (fixed) 3. It compiles a small C harness against each version. 4. The harness: - parses an RSA public JWK **without** an `alg` field, - calls `jwt_checker_setkey(checker, JWT_ALG_HS256, rsa_jwk)`, - verifies a forged `HS256` token whose signature is `HMAC-SHA256("", header.payload)`. 5. On the vulnerable build, the forged token verifies successfully (harness exits `0`). 6. On the fixed build, `setkey` is rejected immediately with "Key type does not match algorithm" (harness exits `2`). ## Evidence ### Log locations - `logs/vuln_out.txt` — output from harness linked against `v3.3.2` - `logs/fix_out.txt` — output from harness linked against `v3.3.3` - `logs/results.txt` — combined summary - `repro/validation_verdict.json` — structured verdict ### Key excerpts **Vulnerable build (v3.3.2)** ``` Exit code: 0 VERIFY SUCCEEDED (VULNERABLE) ``` **Fixed build (v3.3.3)** ``` Exit code: 2 setkey rejected: Key type does not match algorithm ``` These outputs confirm the bug and its remediation: the vulnerable library silently accepts an RSA JWK for HMAC verification and validates the forged token, while the fixed library rejects the key/algorithm mismatch at `setkey` time. ### Environment - OS: Ubuntu Noble (24.04) - Compiler: GCC 13.3.0 - CMake: 3.28.3 - OpenSSL: 3.0.13 - Jansson: 2.14 - libjwt built from source at `external/libjwt` ## Recommendations / Next Steps 1. **Upgrade immediately** to `libjwt >= 3.3.3`. 2. **Review JWKS handling** in applications that use `libjwt`. Ensure that verification callbacks do not blindly trust the `alg` field from an attacker-controlled JWT header without also checking the JWK's `kty`. 3. **Regression tests**: The fix commit includes a comprehensive test suite in `tests/jwt_security.c` covering RSA, EC, and OKP keys mis-targeted at HMAC algorithms, as well as malformed JWKs where `alg` disagrees with `kty`. Maintainers should ensure these tests are run in CI. 4. **Defense in depth**: Even after upgrading, applications should enforce algorithm whitelisting (e.g., only accept `RS256` if the JWKS contains RSA keys) at the application layer to reduce the impact of any future confusion bugs. ## Additional Notes - **Idempotency**: The reproduction script was run twice consecutively with identical results, confirming the build and test process is deterministic. - **Edge cases**: The advisory notes the bug specifically requires an RSA JWK *without* an `alg` parameter. An RSA JWK that explicitly declares `"alg": "RS256"` would also be rejected for HMAC after the fix, because the fix binds algorithm acceptance to the JWK's `kty`, not its optional `alg` hint. - **Limitations**: The reproduction script focuses on `HS256` with a 2048-bit RSA JWK. The fix commit's tests confirm the same issue exists for `HS384` and `HS512`, as well as for EC and OKP keys misused as HMAC keys. ## Reproduction Details Reproduced: 2026-05-22T10:07:50.777Z Duration: 807 seconds Tool calls: 106 Turns: 87 Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00139 pruva-verify GHSA-q843-6q5f-w55g pruva-verify CVE-2026-44699 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00139&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00139/artifacts/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - GitHub Advisory: https://github.com/advisories/GHSA-q843-6q5f-w55g - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-44699 ## Artifacts - repro/rca_report.md (analysis, 5566 bytes) - repro/reproduction_steps.sh (reproduction_script, 3125 bytes) - vuln_variant/rca_report.md (analysis, 7537 bytes) - vuln_variant/reproduction_steps.sh (reproduction_script, 2474 bytes) - bundle/context.json (other, 2815 bytes) - bundle/metadata.json (other, 709 bytes) - bundle/ticket.md (ticket, 3244 bytes) - repro/harness_vuln (other, 16544 bytes) - repro/harness_fix (other, 16544 bytes) - repro/validation_verdict.json (other, 311 bytes) - repro/harness.c (other, 2351 bytes) - vuln_variant/variant_harness.c (other, 4978 bytes) - vuln_variant/harness_vuln (other, 16768 bytes) - vuln_variant/patch_analysis.md (documentation, 6002 bytes) - vuln_variant/variant_manifest.json (other, 2579 bytes) - vuln_variant/harness_fix (other, 16768 bytes) - vuln_variant/validation_verdict.json (other, 1525 bytes) - logs/results.txt (other, 189 bytes) - logs/fix_out.txt (other, 51 bytes) - logs/vuln_variant/results.txt (other, 70 bytes) - logs/vuln_variant/fix_out.txt (other, 620 bytes) - logs/vuln_variant/vuln_out.txt (other, 581 bytes) - logs/vuln_out.txt (other, 30 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00139 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00139/artifacts/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00139 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev