GHSA-jc38-x7x8-2xc8: PHP JWT Framework JWSVerifier Uses Algorithm From Unprotected Vulnerability
HIGHGHSA-jc38-x7x8-2xc8 is a HIGH jwsverifier uses algorithm from unprotected vulnerability (CWE-345: Insufficient Verification of Data Authenticity) affecting PHP JWT Framework libraries in the Packagist ecosystem. The issue involves jwsverifier uses algorithm from unprotected header, enabling algorithm confusion attacks. Remediation is available by upgrading affected packages to version 4.1.7, 4.0.7, 3.4.10.
GHSA-jc38-x7x8-2xc8 at a Glance
How to Fix GHSA-jc38-x7x8-2xc8
To remediate this vulnerability, upgrade all affected PHP JWT Framework dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade web-token/jwt-framework to 4.1.7
Upgrade web-token/jwt-framework to 4.0.7
Upgrade web-token/jwt-library to 3.4.10
Upgrade web-token/jwt-library to 4.0.7
Upgrade web-token/jwt-library to 4.1.7
Upgrade web-token/jwt-bundle to 3.4.10
Upgrade web-token/jwt-bundle to 4.0.7
Upgrade web-token/jwt-bundle to 4.1.7
Upgrade web-token/jwt-experimental to 3.4.10
Upgrade web-token/jwt-experimental to 4.0.7
Upgrade web-token/jwt-experimental to 4.1.7
Upgrade web-token/jwt-framework to 3.4.10
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for web-token/jwt-framework
How to Check if You Are Affected
To determine whether your application is affected by GHSA-jc38-x7x8-2xc8, inspect your project dependencies for PHP JWT Framework:
inspect dependencies for web-token/jwt-framework
Analyze the terminal output to locate any version of PHP JWT Framework in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.1.7), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-345: Insufficient Verification of Data Authenticity, where the product does not sufficiently verify the origin or authenticity of data, in a way that causes it to accept invalid data. In PHP JWT Framework, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
GHSA-jc38-x7x8-2xc8 Affected PHP JWT Framework Versions
12 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
web-token/jwt-framework
|
Packagist |
≥ 4.1.0
< 4.1.7
|
4.1.7
|
|
web-token/jwt-framework
|
Packagist |
≥ 4.0.0
< 4.0.7
|
4.0.7
|
|
web-token/jwt-library
|
Packagist |
< 3.4.10
|
3.4.10
|
|
web-token/jwt-library
|
Packagist |
≥ 4.0.0
< 4.0.7
|
4.0.7
|
|
web-token/jwt-library
|
Packagist |
≥ 4.1.0
< 4.1.7
|
4.1.7
|
|
web-token/jwt-bundle
|
Packagist |
< 3.4.10
|
3.4.10
|
|
web-token/jwt-bundle
|
Packagist |
≥ 4.0.0
< 4.0.7
|
4.0.7
|
|
web-token/jwt-bundle
|
Packagist |
≥ 4.1.0
< 4.1.7
|
4.1.7
|
|
web-token/jwt-experimental
|
Packagist |
< 3.4.10
|
3.4.10
|
|
web-token/jwt-experimental
|
Packagist |
≥ 4.0.0
< 4.0.7
|
4.0.7
|
|
web-token/jwt-experimental
|
Packagist |
≥ 4.1.0
< 4.1.7
|
4.1.7
|
|
web-token/jwt-framework
|
Packagist |
< 3.4.10
|
3.4.10
|
References
Frequently Asked Questions
What is GHSA-jc38-x7x8-2xc8?
Which PHP JWT Framework versions are affected?
What version fixes GHSA-jc38-x7x8-2xc8?
Is web-token/jwt-framework affected?
Is web-token/jwt-library affected?
What CWE is associated with GHSA-jc38-x7x8-2xc8?
How can I check Packagist dependencies for GHSA-jc38-x7x8-2xc8?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-jc38-x7x8-2xc8",
"modified" : "2026-07-06T16:29:56Z",
"published" : "2026-06-18T21:09:17Z",
"aliases" : [ ],
"summary" : "PHP JWT Framework: JWSVerifier uses algorithm from unprotected header, enabling algorithm confusion attacks",
"details" : "## Summary\n\n`JWSVerifier::getAlgorithm()` in `src/Library/Signature/JWSVerifier.php` (line 144) merges protected and unprotected headers using PHP's spread operator:\n\n```php\n$completeHeader = [...$signature->getProtectedHeader(), ...$signature->getHeader()];\n```\n\nIn 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.\n\nThis creates a Time-of-Check/Time-of-Use (TOCTOU) vulnerability:\n1. `HeaderCheckerManager` validates `alg` from the **protected** header\n2. `JWSVerifier` uses `alg` from the **unprotected** header for actual verification\n\nThe same issue exists in `JWEDecrypter.php` (lines 120-124) where `array_merge()` exhibits the same last-wins behavior for `alg` and `enc`.\n\n## Affected Code\n\n**JWSVerifier.php line 144** — Spread operator merge order allows unprotected header to override `alg`:\n```php\n$completeHeader = [...$signature->getProtectedHeader(), ...$signature->getHeader()];\n```\n\n**JWEDecrypter.php lines 120-124** — `array_merge()` with same last-wins behavior:\n```php\n$completeHeader = array_merge(\n $jwe->getSharedProtectedHeader(),\n $jwe->getSharedHeader(),\n $recipient->getHeader()\n);\n```\n\n## Attack Vectors\n\n### Vector A — Mixed key sets (HIGH probability)\nIf 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.\n\n### Vector B — alg ONLY in unprotected header (HIGH probability)\nIf `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.\n\n### Vector C — Direct JWSVerifier usage (HIGH probability)\n`JWSLoader` takes `?HeaderCheckerManager` (nullable). If developers use `JWSVerifier` directly or create `JWSLoader` without a `HeaderCheckerManager`, the duplicate header check never runs.\n\n## Contrast with JWSBuilder (safe)\n\n`JWSBuilder::findSignatureAlgorithm()` (line 196) uses `[...$header, ...$protectedHeader]` where protected wins. It also has `checkDuplicatedHeaderParameters()` (line 218). The JWSVerifier has **neither** safeguard.\n\n## Proof of Concept\n\n```php\n<?php\n// Demonstrate algorithm override via unprotected header\n$protected = [\"alg\" => \"RS256\", \"typ\" => \"JWT\"];\n$unprotected = [\"alg\" => \"HS256\"];\n$merged = [...$protected, ...$unprotected];\n// $merged[\"alg\"] === \"HS256\" — unprotected wins!\n\n// JSON Flattened JWS with algorithm override:\n$maliciousJws = json_encode([\n 'payload' => base64url_encode($payload),\n 'protected' => base64url_encode('{\"alg\":\"RS256\"}'),\n 'header' => ['alg' => 'HS256'], // OVERRIDE\n 'signature' => base64url_encode($sig),\n]);\n// HeaderCheckerManager validates RS256 from protected header -> PASS\n// JWSVerifier uses HS256 from unprotected header -> attacker's algorithm choice\n```\n\nA full working PoC demonstrating HS512-to-HS256 downgrade with mixed keysets is available upon request.\n\n## Suggested Fix\n\nIn `JWSVerifier::getAlgorithm()`, read `alg` exclusively from the protected header:\n\n```php\nprivate function getAlgorithm(Signature $signature): Algorithm\n{\n $protectedHeader = $signature->getProtectedHeader();\n if (! isset($protectedHeader['alg'])) {\n throw new InvalidArgumentException('The \"alg\" parameter must be in the protected header.');\n }\n return $this->signatureAlgorithmManager->get($protectedHeader['alg']);\n}\n```\n\nFor `JWEDecrypter`, reverse the merge order so protected header wins, or extract `alg`/`enc` exclusively from the protected header.\n\n## Résolution\n\nUn 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).\n\n**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é.\n\n**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`.",
"severity" : [ {
"type" : "CVSS_V4",
"score" : "CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:P"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Packagist",
"name" : "web-token/jwt-library"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "3.4.10"
} ]
} ]
}, {
"package" : {
"ecosystem" : "Packagist",
"name" : "web-token/jwt-library"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "4.0.0"
}, {
"fixed" : "4.0.7"
} ]
} ]
}, {
"package" : {
"ecosystem" : "Packagist",
"name" : "web-token/jwt-library"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "4.1.0"
}, {
"fixed" : "4.1.7"
} ]
} ]
}, {
"package" : {
"ecosystem" : "Packagist",
"name" : "web-token/jwt-bundle"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "3.4.10"
} ]
} ]
}, {
"package" : {
"ecosystem" : "Packagist",
"name" : "web-token/jwt-bundle"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "4.0.0"
}, {
"fixed" : "4.0.7"
} ]
} ]
}, {
"package" : {
"ecosystem" : "Packagist",
"name" : "web-token/jwt-bundle"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "4.1.0"
}, {
"fixed" : "4.1.7"
} ]
} ]
}, {
"package" : {
"ecosystem" : "Packagist",
"name" : "web-token/jwt-experimental"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "3.4.10"
} ]
} ]
}, {
"package" : {
"ecosystem" : "Packagist",
"name" : "web-token/jwt-experimental"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "4.0.0"
}, {
"fixed" : "4.0.7"
} ]
} ]
}, {
"package" : {
"ecosystem" : "Packagist",
"name" : "web-token/jwt-experimental"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "4.1.0"
}, {
"fixed" : "4.1.7"
} ]
} ]
}, {
"package" : {
"ecosystem" : "Packagist",
"name" : "web-token/jwt-framework"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "3.4.10"
} ]
} ]
}, {
"package" : {
"ecosystem" : "Packagist",
"name" : "web-token/jwt-framework"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "4.0.0"
}, {
"fixed" : "4.0.7"
} ]
} ]
}, {
"package" : {
"ecosystem" : "Packagist",
"name" : "web-token/jwt-framework"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "4.1.0"
}, {
"fixed" : "4.1.7"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/web-token/jwt-framework/security/advisories/GHSA-jc38-x7x8-2xc8"
}, {
"type" : "WEB",
"url" : "https://github.com/FriendsOfPHP/security-advisories/blob/master/web-token/jwt-library/GHSA-jc38-x7x8-2xc8.yaml"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/web-token/jwt-framework"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-345" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-06-18T21:09:17Z",
"nvd_published_at" : null
}
}