{"id":"GHSA-2jx3-65f3-xr8r","title":"spomky-labs/otphp: Mass-assignment in Factory::loadFromProvisioningUri lets a hostile provisioning URI corrupt OTP state or leak an uncaught TypeError","summary":"spomky-labs/otphp: Mass-assignment in Factory::loadFromProvisioningUri lets a hostile provisioning URI corrupt OTP state or leak an uncaught TypeError","severity":"medium","cwe":["CWE-915"],"vendor":"spomky-labs","product":"spomky-labs/otphp","affected":["spomky-labs/otphp < 11.4.3"],"patched":["spomky-labs/otphp 11.4.3"],"published":"2026-06-18","updated":"2026-06-18","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-2jx3-65f3-xr8r","references":[{"url":"https://github.com/Spomky-Labs/otphp/security/advisories/GHSA-2jx3-65f3-xr8r"},{"url":"https://github.com/FriendsOfPHP/security-advisories/blob/master/spomky-labs/otphp/GHSA-2jx3-65f3-xr8r.yaml"},{"url":"https://github.com/advisories/GHSA-2jx3-65f3-xr8r"}],"tags":["ghsa","composer"],"ingestedAt":"2026-06-19T03:39:00.767Z","ecosystem":"composer","slug":"GHSA-2jx3-65f3-xr8r","body":"## Overview\n\n## 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.\n\n## Affected packages\n\n- `spomky-labs/otphp < 11.4.3`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `spomky-labs/otphp 11.4.3`","depth":"sunlit","depthScore":28,"depthScoreParts":{"impact":27.5,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}