p PatchFirst

GHSA-jc38-x7x8-2xc8

GHSA-jc38-x7x8-2xc8 · PHP JWT Framework: JWSVerifier uses algorithm from unprotected header, enabling algorithm confusion attacks

This page contains facts from public vulnerability sources. Check the original advisory before updating.

## Summary `JWSVerifier::getAlgorithm()` in `src/Library/Signature/JWSVerifier.php` (line 144) merges protected and unprotected headers using PHP's spread operator: ```php $completeHeader = [...$signature->getProtectedHeader(), ...$signature->getHeader()]; ``` In PHP, when spreading arrays with duplicate string keys, the **last array's values take precedence**. Since the unprotected header (`getHeader()`) is spread second, an attacker can override the integrity-protected `alg` parameter by placing a different value in the unprotected header. This creates a Time-of-Check/Time-of-Use (TOCTOU) vulnerability: 1. `HeaderCheckerManager` validates `alg` from the **protected** header 2. `JWSVerifier` uses `alg` from the **unprotected** header for actual verification The same issue exists in `JWEDecrypter.php` (lines 120-124) where `array_merge()` exhibits the same last-wins behavior for `alg` and `enc`. ## Affected Code **JWSVerifier.php line 144** — Spread operator merge order allows unprotected header to override `alg`: ```php $completeHeader = [...$signature->getProtectedHeader(), ...$signature->getHeader()]; ``` **JWEDecrypter.php lines 120-124** — `array_merge()` with same last-wins behavior: ```php $completeHeader = array_merge( $jwe->getSharedProtectedHeader(), $jwe->getSharedHeader(), $recipient->getHeader() ); ``` ## Attack Vectors ### Vector A — Mixed key sets (HIGH probability) If the application uses a JWKSet containing keys of different types (common in multi-tenant or federation scenarios), the JWSVerifier iterates all keys (line 86). An attacker can force a different algorithm that matches a different key in the set. ### Vector B — alg ONLY in unprotected header (HIGH probability) If `alg` is placed EXCLUSIVELY in the unprotected header (not in the protected header at all), `HeaderCheckerManager::checkDuplicatedHeaderParameters()` does NOT trigger. The JSON Flattened/General serializers allow tokens with no protected header or a protected header without `alg`. RFC 7515 Section 4.1.1 states `alg` MUST be integrity-protected, but the library does not enforce this. ### Vector C — Direct JWSVerifier usage (HIGH probability) `JWSLoader` takes `?HeaderCheckerManager` (nullable). If developers use `JWSVerifier` directly or create `JWSLoader` without a `HeaderCheckerManager`, the duplicate header check never runs. ## Contrast with JWSBuilder (safe) `JWSBuilder::findSignatureAlgorithm()` (line 196) uses `[...$header, ...$protectedHeader]` where protected wins. It also has `checkDuplicatedHeaderParameters()` (line 218). The JWSVerifier has **neither** safeguard. ## Proof of Concept ```php <?php // Demonstrate algorithm override via unprotected header $protected = ["alg" => "RS256", "typ" => "JWT"]; $unprotected = ["alg" => "HS256"]; $merged = [...$protected, ...$unprotected]; // $merged["alg"] === "HS256" — unprotected wins! // JSON Flattened JWS with algorithm override: $maliciousJws = json_encode([ 'payload' => base64url_encode($payload), 'protected' => base64url_encode('{"alg":"RS256"}'), 'header' => ['alg' => 'HS256'], // OVERRIDE 'signature' => base64url_encode($sig), ]); // HeaderCheckerManager validates RS256 from protected header -> PASS // JWSVerifier uses HS256 from unprotected header -> attacker's algorithm choice ``` A full working PoC demonstrating HS512-to-HS256 downgrade with mixed keysets is available upon request. ## Suggested Fix In `JWSVerifier::getAlgorithm()`, read `alg` exclusively from the protected header: ```php private function getAlgorithm(Signature $signature): Algorithm { $protectedHeader = $signature->getProtectedHeader(); if (! isset($protectedHeader['alg'])) { throw new InvalidArgumentException('The "alg" parameter must be in the protected header.'); } return $this->signatureAlgorithmManager->get($protectedHeader['alg']); } ``` For `JWEDecrypter`, reverse the merge order so protected header wins, or extract `alg`/`enc` exclusively from the protected header. ## Résolution Un correctif a été préparé sur une branche dédiée basée sur `3.4.x`, avec des tests anti-régression dédiés (fork privé temporaire de cette advisory, PR #1). **JWS algorithm confusion** — `JWSVerifier` lit le paramètre `alg` exclusivement dans le header protégé en intégrité (RFC 7515 §4.1.1) ; un `alg` placé dans le header non protégé ne peut plus surcharger l'algorithme signé. **Validation :** `php -l` OK, PHPUnit vert, aucune nouvelle erreur PHPStan introduite (différentiel nul vs `3.4.x`), aucun commentaire ajouté dans le code source. Après merge, cascade prévue `3.4.x → 4.0.x → 4.1.x`.

CVSS 9.1 · Critical · Fix available

Published
Updated

Stack: php

Affected products and versions

  • web-token/jwt-library Packagist

    Versions
    • 3.3.0
    • 3.3.1
    • 3.3.2
    • 3.3.3
    • 3.3.4
    • 3.3.5
    • 3.4.0
    • 3.4.1
    • 3.4.2
    • 3.4.3
    • 3.4.4
    • 3.4.5
    • 3.4.6
    • 3.4.7
    • 3.4.8
    • 3.4.9
    Affected ranges
    • ECOSYSTEM: < 3.4.10

    Update to 3.4.10

  • web-token/jwt-library Packagist

    Versions
    • 4.0.0
    • 4.0.1
    • 4.0.2
    • 4.0.3
    • 4.0.4
    • 4.0.5
    • 4.0.6
    Affected ranges
    • ECOSYSTEM: ≥ 4.0.0, < 4.0.7

    Update to 4.0.7

  • web-token/jwt-library Packagist

    Versions
    • 4.1.0
    • 4.1.1
    • 4.1.2
    • 4.1.3
    • 4.1.4
    • 4.1.5
    • 4.1.6
    Affected ranges
    • ECOSYSTEM: ≥ 4.1.0, < 4.1.7

    Update to 4.1.7

  • web-token/jwt-bundle Packagist

    Versions
    • 3.3.0
    • 3.3.1
    • 3.3.2
    • 3.3.3
    • 3.3.4
    • 3.3.5
    • 3.4.0
    • 3.4.1
    • 3.4.2
    • 3.4.3
    • 3.4.4
    • 3.4.5
    • 3.4.6
    • 3.4.7
    • 3.4.8
    • 3.4.9
    • v0.0.0
    • v0.0.1
    • v0.0.2
    • v0.1.0
    • v0.2.0
    • v0.2.1
    • v0.2.2
    • v0.2.3
    • v0.3.0
    • v0.3.1
    • v0.3.2
    • v0.4.0
    • v0.4.1
    • v0.5.0
    • v0.5.1
    • v0.5.2
    • v1.0.0
    • v1.0.1
    • v1.0.2
    • v1.0.3
    • v1.0.4
    • v1.0.5
    • v1.0.6
    • v1.1.0
    • v1.1.0-beta1
    • v1.1.0-beta2
    • v1.1.0-beta3
    • v1.1.0-beta4
    • v1.1.1
    • v1.1.2
    • v1.1.3
    • v1.1.4
    • v1.2.0
    • v1.2.1
    • v1.2.2
    • v1.2.3
    • v1.2.4
    • v1.3.0
    • v1.3.1
    • v1.3.10
    • v1.3.2
    • v1.3.3
    • v1.3.4
    • v1.3.5
    • v1.3.6
    • v1.3.7
    • v1.3.8
    • v1.3.9
    • v2.0.0
    • v2.0.0-alpha1
    • v2.0.0-alpha2
    • v2.0.0-alpha3
    • v2.0.1
    • v2.0.10
    • v2.0.11
    • v2.0.12
    • v2.0.13
    • v2.0.2
    • v2.0.3
    • v2.0.4
    • v2.0.5
    • v2.0.6
    • v2.0.7
    • v2.0.8
    • v2.0.9
    • v2.1.0
    • v2.1.1
    • v2.1.2
    • v2.1.3
    • v2.1.4
    • v2.1.5
    • v2.1.6
    • v2.1.7
    • v2.1.8
    • v2.1.9
    • v2.2.0
    • v2.2.1
    • v2.2.10
    • v2.2.11
    • v2.2.2
    • v2.2.3
    • v2.2.4
    • v2.2.5
    • v2.2.6
    • v2.2.7
    • v2.2.8
    • v2.2.9
    • v3.0.1
    • v3.0.2
    • v3.0.3
    • v3.0.4
    • v3.0.5
    • v3.0.6
    • v3.0.7
    Affected ranges
    • ECOSYSTEM: < 3.4.10

    Update to 3.4.10

  • web-token/jwt-bundle Packagist

    Versions
    • 4.0.0
    • 4.0.1
    • 4.0.2
    • 4.0.3
    • 4.0.4
    • 4.0.5
    • 4.0.6
    Affected ranges
    • ECOSYSTEM: ≥ 4.0.0, < 4.0.7

    Update to 4.0.7

  • web-token/jwt-bundle Packagist

    Versions
    • 4.1.0
    • 4.1.1
    • 4.1.2
    • 4.1.3
    • 4.1.4
    • 4.1.5
    • 4.1.6
    Affected ranges
    • ECOSYSTEM: ≥ 4.1.0, < 4.1.7

    Update to 4.1.7

  • web-token/jwt-experimental Packagist

    Versions
    • 3.3.0
    • 3.3.1
    • 3.3.2
    • 3.3.3
    • 3.3.4
    • 3.3.5
    • 3.4.0
    • 3.4.1
    • 3.4.2
    • 3.4.3
    • 3.4.4
    • 3.4.5
    • 3.4.6
    • 3.4.7
    • 3.4.8
    • 3.4.9
    Affected ranges
    • ECOSYSTEM: < 3.4.10

    Update to 3.4.10

  • web-token/jwt-experimental Packagist

    Versions
    • 4.0.0
    • 4.0.1
    • 4.0.2
    • 4.0.3
    • 4.0.4
    • 4.0.5
    • 4.0.6
    Affected ranges
    • ECOSYSTEM: ≥ 4.0.0, < 4.0.7

    Update to 4.0.7

  • web-token/jwt-experimental Packagist

    Versions
    • 4.1.0
    • 4.1.1
    • 4.1.2
    • 4.1.3
    • 4.1.4
    • 4.1.5
    • 4.1.6
    Affected ranges
    • ECOSYSTEM: ≥ 4.1.0, < 4.1.7

    Update to 4.1.7

  • web-token/jwt-framework Packagist

    Versions
    • 3.1.0
    • 3.1.1
    • 3.1.2
    • 3.1.3
    • 3.1.4
    • 3.1.5
    • 3.1.6
    • 3.1.7
    • 3.2.0
    • 3.2.1
    • 3.2.10
    • 3.2.2
    • 3.2.3
    • 3.2.4
    • 3.2.5
    • 3.2.6
    • 3.2.7
    • 3.2.8
    • 3.2.9
    • 3.3.0
    • 3.3.0-alpha1
    • 3.3.0-alpha2
    • 3.3.0-alpha3
    • 3.3.1
    • 3.3.2
    • 3.3.3
    • 3.3.4
    • 3.3.5
    • 3.4.0
    • 3.4.1
    • 3.4.2
    • 3.4.3
    • 3.4.4
    • 3.4.5
    • 3.4.6
    • 3.4.7
    • 3.4.8
    • 3.4.9
    • v0.0.0
    • v0.0.1
    • v0.0.2
    • v0.1.0
    • v0.2.0
    • v0.2.1
    • v0.2.2
    • v0.2.3
    • v0.3.0
    • v0.3.1
    • v0.3.2
    • v0.4.0
    • v0.4.1
    • v0.5.0
    • v0.5.1
    • v0.5.2
    • v1.0.0
    • v1.0.1
    • v1.0.2
    • v1.0.3
    • v1.0.4
    • v1.0.5
    • v1.0.6
    • v1.1.0
    • v1.1.0-beta1
    • v1.1.0-beta2
    • v1.1.0-beta3
    • v1.1.0-beta4
    • v1.1.1
    • v1.1.2
    • v1.1.3
    • v1.1.4
    • v1.2.0
    • v1.2.1
    • v1.2.2
    • v1.2.3
    • v1.2.4
    • v1.3.0
    • v1.3.1
    • v1.3.10
    • v1.3.2
    • v1.3.3
    • v1.3.4
    • v1.3.5
    • v1.3.6
    • v1.3.7
    • v1.3.8
    • v1.3.9
    • v2.0.0
    • v2.0.0-alpha1
    • v2.0.0-alpha2
    • v2.0.0-alpha3
    • v2.0.1
    • v2.0.11
    • v2.0.12
    • v2.0.13
    • v2.0.2
    • v2.0.3
    • v2.0.4
    • v2.0.5
    • v2.0.6
    • v2.0.7
    • v2.0.8
    • v2.0.9
    • v2.1.0
    • v2.1.1
    • v2.1.2
    • v2.1.3
    • v2.1.4
    • v2.1.5
    • v2.1.6
    • v2.1.7
    • v2.1.8
    • v2.1.9
    • v2.2.0
    • v2.2.1
    • v2.2.10
    • v2.2.11
    • v2.2.2
    • v2.2.3
    • v2.2.4
    • v2.2.5
    • v2.2.6
    • v2.2.7
    • v2.2.8
    • v2.2.9
    • v3.0.1
    • v3.0.2
    • v3.0.3
    • v3.0.4
    • v3.0.5
    • v3.0.6
    • v3.0.7
    Affected ranges
    • ECOSYSTEM: < 3.4.10

    Update to 3.4.10

  • web-token/jwt-framework Packagist

    Versions
    • 4.0.0
    • 4.0.1
    • 4.0.2
    • 4.0.3
    • 4.0.4
    • 4.0.5
    • 4.0.6
    Affected ranges
    • ECOSYSTEM: ≥ 4.0.0, < 4.0.7

    Update to 4.0.7

  • web-token/jwt-framework Packagist

    Versions
    • 4.1.0
    • 4.1.1
    • 4.1.2
    • 4.1.3
    • 4.1.4
    • 4.1.5
    • 4.1.6
    Affected ranges
    • ECOSYSTEM: ≥ 4.1.0, < 4.1.7

    Update to 4.1.7

Original sources

Check your server →