Skip to content

Commit 28b2b0e

Browse files
1 parent e4aa2dc commit 28b2b0e

4 files changed

Lines changed: 409 additions & 0 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-2jx3-65f3-xr8r",
4+
"modified": "2026-06-18T21:07:41Z",
5+
"published": "2026-06-18T21:07:41Z",
6+
"aliases": [],
7+
"summary": "spomky-labs/otphp: Mass-assignment in Factory::loadFromProvisioningUri lets a hostile provisioning URI corrupt OTP state or leak an uncaught TypeError",
8+
"details": "## Summary\n\n`OTPHP\\Factory::loadFromProvisioningUri()` parses an attacker-supplied `otpauth://` URI and forwards **every** query key to `OTP::setParameter($key, $value)`. `setParameter()` resolves the name with `property_exists($this, $parameter)` and performs a dynamic write `$this->{$parameter} = $value` (`src/OTP.php:196-197`). Because the query keys are entirely controlled by whoever produced the URI, a URI can target the internal properties of the OTP object that are not meant to be set from a URI: `parameters`, `issuer`, `label`, `issuer_included_as_parameter`, and (on TOTP) the readonly `clock`. This is an instance of object property mass-assignment (CWE-915).\n\n## Impact\n\nThe `Factory` is documented as the entry point for third-party provisioning URIs (e.g. QR codes from Microsoft 365 / Google Authenticator). An application that loads such a URI is exposed to:\n\n- **State corruption.** A URI such as `otpauth://totp/Alice?secret=JBSWY3DPEHPK3PXP&parameters[foo]=bar` overwrites the whole internal `$parameters` array that `createFromSecret()` primed (`period`, `algorithm`, `digits`, `epoch`). The resulting object is silently unusable: `getProvisioningUri()`, `getDigits()`, `at()`, `verify()` then throw `ParameterNotFoundException`.\n- **Uncaught TypeError escaping the documented exception type.** A URI such as `otpauth://totp/Alice?secret=JBSWY3DPEHPK3PXP&issuer_included_as_parameter=notabool` assigns a string to a typed `bool` property and raises a `TypeError`. The `try/catch` in `loadFromProvisioningUri()` only wraps `Url::fromString()`; `createOTP()` and `populateOTP()` run outside it, so the `TypeError` (and `Error` on the readonly `clock`) escapes past the documented `InvalidProvisioningUriException`, breaking callers that catch only the documented type.\n- **Label/issuer validation bypass.** `parameters[label]=hijacked` stores a label into the parameters array without running the `label` validation callback (keyed on `label`, not `parameters`). `getLabel()` and `getParameter('label')` then disagree — a confused-deputy risk.\n\n## Affected component\n\n- `src/OTP.php:187-201` — `setParameter()` dynamic property write\n- `src/Factory.php:50-55` — `populateParameters()` forwarding all query keys\n\n## Proof of concept\n\n```php\nuse OTPHP\\Factory;\n\n// State corruption\n$otp = Factory::loadFromProvisioningUri(\n 'otpauth://totp/Alice?secret=JBSWY3DPEHPK3PXP&parameters[foo]=bar',\n $clock\n);\n$otp->getProvisioningUri(); // ParameterNotFoundException: Parameter \"period\" does not exist\n\n// Uncaught TypeError\nFactory::loadFromProvisioningUri(\n 'otpauth://totp/Alice?secret=JBSWY3DPEHPK3PXP&issuer_included_as_parameter=notabool',\n $clock\n); // TypeError escapes InvalidProvisioningUriException\n```\n\n## Remediation\n\nRestrict the keys accepted from a provisioning URI to a known allow-list of public OTP parameters, and never let a URI key resolve to an internal object property via `property_exists`. Route all URI-sourced values through the validated parameter map only.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V4",
12+
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "Packagist",
19+
"name": "spomky-labs/otphp"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "11.4.3"
30+
}
31+
]
32+
}
33+
]
34+
}
35+
],
36+
"references": [
37+
{
38+
"type": "WEB",
39+
"url": "https://github.com/Spomky-Labs/otphp/security/advisories/GHSA-2jx3-65f3-xr8r"
40+
},
41+
{
42+
"type": "WEB",
43+
"url": "https://github.com/FriendsOfPHP/security-advisories/blob/master/spomky-labs/otphp/GHSA-2jx3-65f3-xr8r.yaml"
44+
},
45+
{
46+
"type": "PACKAGE",
47+
"url": "https://github.com/Spomky-Labs/otphp"
48+
}
49+
],
50+
"database_specific": {
51+
"cwe_ids": [
52+
"CWE-915"
53+
],
54+
"severity": "MODERATE",
55+
"github_reviewed": true,
56+
"github_reviewed_at": "2026-06-18T21:07:41Z",
57+
"nvd_published_at": null
58+
}
59+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-3prj-6hqw-cm82",
4+
"modified": "2026-06-18T21:09:01Z",
5+
"published": "2026-06-18T21:09:01Z",
6+
"aliases": [],
7+
"summary": "PHP JWT Library: PBES2-HS*+A*KW unwrap accepts an unbounded p2c iteration count, enabling CPU-amplification denial of service",
8+
"details": "### Impact\n\nWhen a JWE uses a password-based key-encryption algorithm (`PBES2-HS256+A128KW`, `PBES2-HS384+A192KW`, `PBES2-HS512+A256KW`), `PBES2AESKW::unwrapKey()` reads the `p2c` (PBKDF2 iteration count) parameter directly from the attacker-controlled JOSE header and passes it to `hash_pbkdf2()` with **no upper bound**. The only validation performed (`checkHeaderAdditionalParameters()`) was `is_int($p2c) && $p2c > 0`.\n\nAn unauthenticated attacker can craft a single JWE whose protected header sets a very large `p2c` (e.g. `100_000_000` ≈ 87 s of CPU, or `PHP_INT_MAX`), forcing a worker to spend an arbitrary amount of CPU inside PBKDF2 **before** the key unwrap can even fail. The decrypter swallows the eventual exception, so the attacker pays almost nothing while the server burns CPU. JSON General serialization (multiple recipients) and multi-key JWKSets multiply the cost. This is a classic uncontrolled-resource-consumption (CWE-400) denial of service.\n\n### Affected configurations\n\nApplications that register any `PBES2-HS*+A*KW` algorithm in their decryption `AlgorithmManager`.\n\n### Patches\n\n`PBES2AESKW` now enforces a configurable maximum iteration count (`DEFAULT_MAX_COUNT = 1_000_000`, well above realistic legitimate values which are a few thousand) in `checkHeaderAdditionalParameters()`, before any PBKDF2 computation. The bound is exposed as a constructor argument so operators can tune it.\n\n### Workarounds\n\nBefore upgrading: validate/limit the `p2c` header with a custom header checker, or do not enable PBES2 algorithms for untrusted tokens.\n\n### References\n\n- RFC 7518 §4.8 (PBES2)\n- CWE-400: Uncontrolled Resource Consumption\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**PBES2** — `PBES2AESKW::unwrapKey()` borne désormais le paramètre `p2c` (constante `DEFAULT_MAX_COUNT = 1_000_000`, configurable via le constructeur) avant tout appel à `hash_pbkdf2()`, empêchant l'amplification CPU (DoS).\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`.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V4",
12+
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "Packagist",
19+
"name": "web-token/jwt-library"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "3.4.10"
30+
}
31+
]
32+
}
33+
]
34+
},
35+
{
36+
"package": {
37+
"ecosystem": "Packagist",
38+
"name": "web-token/jwt-framework"
39+
},
40+
"ranges": [
41+
{
42+
"type": "ECOSYSTEM",
43+
"events": [
44+
{
45+
"introduced": "0"
46+
},
47+
{
48+
"last_affected": "4.1.6"
49+
}
50+
]
51+
}
52+
]
53+
},
54+
{
55+
"package": {
56+
"ecosystem": "Packagist",
57+
"name": "web-token/jwt-library"
58+
},
59+
"ranges": [
60+
{
61+
"type": "ECOSYSTEM",
62+
"events": [
63+
{
64+
"introduced": "4.0.0"
65+
},
66+
{
67+
"fixed": "4.0.7"
68+
}
69+
]
70+
}
71+
]
72+
},
73+
{
74+
"package": {
75+
"ecosystem": "Packagist",
76+
"name": "web-token/jwt-library"
77+
},
78+
"ranges": [
79+
{
80+
"type": "ECOSYSTEM",
81+
"events": [
82+
{
83+
"introduced": "4.1.0"
84+
},
85+
{
86+
"fixed": "4.1.7"
87+
}
88+
]
89+
}
90+
]
91+
}
92+
],
93+
"references": [
94+
{
95+
"type": "WEB",
96+
"url": "https://github.com/web-token/jwt-framework/security/advisories/GHSA-3prj-6hqw-cm82"
97+
},
98+
{
99+
"type": "WEB",
100+
"url": "https://github.com/FriendsOfPHP/security-advisories/blob/master/web-token/jwt-library/GHSA-3prj-6hqw-cm82.yaml"
101+
},
102+
{
103+
"type": "PACKAGE",
104+
"url": "https://github.com/web-token/jwt-framework"
105+
}
106+
],
107+
"database_specific": {
108+
"cwe_ids": [
109+
"CWE-400",
110+
"CWE-770"
111+
],
112+
"severity": "HIGH",
113+
"github_reviewed": true,
114+
"github_reviewed_at": "2026-06-18T21:09:01Z",
115+
"nvd_published_at": null
116+
}
117+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-6vvh-pxr4-25r7",
4+
"modified": "2026-06-18T21:08:15Z",
5+
"published": "2026-06-18T21:08:15Z",
6+
"aliases": [],
7+
"summary": "PHP JWT Framework: Chacha20Poly1305 key-encryption algorithm discards the Poly1305 authentication tag, performing no authentication on decryption",
8+
"details": "### Impact\n\nThe experimental `Chacha20Poly1305` key-encryption algorithm generates the 16-byte Poly1305 authentication tag during `encryptKey()` but **discards it**: the tag is never written to the header and therefore never reaches the wire. On the receiving side, `decryptKey()` calls `openssl_decrypt('chacha20-poly1305', ...)` **without the tag argument**, which makes OpenSSL skip authentication entirely.\n\nAs a result the AEAD construction is silently degraded to unauthenticated ChaCha20: a tampered encrypted CEK is accepted, and because ChaCha20 is a stream cipher, a single-byte change in the ciphertext propagates as a single-byte change in the recovered CEK with no integrity check (CWE-353 / CWE-347). An attacker on the token path can manipulate the wrapped key without detection.\n\n### Affected configurations\n\nApplications that register `Jose\\Experimental\\KeyEncryption\\Chacha20Poly1305` (package `web-token/jwt-experimental`) as a JWE `alg`.\n\n### Patches\n\n`encryptKey()` now publishes the Poly1305 tag as the base64url `tag` header parameter (and asserts it is 16 bytes). `decryptKey()` requires the `tag` header, validates its length, and passes it to `openssl_decrypt()` so the tag is actually verified, in line with RFC 7539 / RFC 8439. Tampering now results in a decryption failure.\n\n> Note: this changes the wire format of tokens produced with this experimental algorithm (a `tag` header is now emitted and required).\n\n### Workarounds\n\nDo not use the experimental `Chacha20Poly1305` key-encryption algorithm for untrusted input until upgraded.\n\n### References\n\n- RFC 7539 / RFC 8439 (ChaCha20-Poly1305 AEAD)\n- CWE-353: Missing Support for Integrity Check\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**ChaCha20-Poly1305** — le tag d'authentification Poly1305 est désormais publié dans le header au chiffrement et vérifié au déchiffrement (RFC 7539), rétablissant l'intégrité AEAD.\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`.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V4",
12+
"score": "CVSS:4.0/AV:A/AC:H/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "Packagist",
19+
"name": "web-token/jwt-experimental"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"last_affected": "4.1.6"
30+
}
31+
]
32+
}
33+
]
34+
},
35+
{
36+
"package": {
37+
"ecosystem": "Packagist",
38+
"name": "web-token/jwt-library"
39+
},
40+
"ranges": [
41+
{
42+
"type": "ECOSYSTEM",
43+
"events": [
44+
{
45+
"introduced": "0"
46+
},
47+
{
48+
"fixed": "3.4.10"
49+
}
50+
]
51+
}
52+
]
53+
},
54+
{
55+
"package": {
56+
"ecosystem": "Packagist",
57+
"name": "web-token/jwt-library"
58+
},
59+
"ranges": [
60+
{
61+
"type": "ECOSYSTEM",
62+
"events": [
63+
{
64+
"introduced": "4.0.0"
65+
},
66+
{
67+
"fixed": "4.0.7"
68+
}
69+
]
70+
}
71+
]
72+
},
73+
{
74+
"package": {
75+
"ecosystem": "Packagist",
76+
"name": "web-token/jwt-library"
77+
},
78+
"ranges": [
79+
{
80+
"type": "ECOSYSTEM",
81+
"events": [
82+
{
83+
"introduced": "4.1.0"
84+
},
85+
{
86+
"fixed": "4.1.7"
87+
}
88+
]
89+
}
90+
]
91+
}
92+
],
93+
"references": [
94+
{
95+
"type": "WEB",
96+
"url": "https://github.com/web-token/jwt-framework/security/advisories/GHSA-6vvh-pxr4-25r7"
97+
},
98+
{
99+
"type": "WEB",
100+
"url": "https://github.com/FriendsOfPHP/security-advisories/blob/master/web-token/jwt-library/GHSA-6vvh-pxr4-25r7.yaml"
101+
},
102+
{
103+
"type": "PACKAGE",
104+
"url": "https://github.com/web-token/jwt-framework"
105+
}
106+
],
107+
"database_specific": {
108+
"cwe_ids": [
109+
"CWE-347",
110+
"CWE-353"
111+
],
112+
"severity": "MODERATE",
113+
"github_reviewed": true,
114+
"github_reviewed_at": "2026-06-18T21:08:15Z",
115+
"nvd_published_at": null
116+
}
117+
}

0 commit comments

Comments
 (0)