Jun 19, 2026·7 min read·3 visits
Unauthenticated remote attackers can deliver crafted OTP provisioning URIs to overwrite internal properties of the `otphp` library, causing denial of service, validation bypasses, or immediate application crashes.
A critical mass-assignment (property injection) vulnerability exists in the PHP One-Time Password (OTP) library spomky-labs/otphp within the Factory::loadFromProvisioningUri method. When an application loads an OTP provisioning URI (such as a QR code configuration link), a hostile URI can inject query parameters that dynamically overwrite internal, private, or read-only object properties of the OTP instance. This behavior leads to application state corruption, validation bypasses, or uncaught TypeErrors that crash the executing application process.
The PHP One-Time Password (OTP) library spomky-labs/otphp is a widely utilized package in the PHP ecosystem for generating and validating Google Authenticator, Microsoft Authenticator, and standard TOTP/HOTP values. The core attack surface is exposed via the Factory::loadFromProvisioningUri() interface. This method is routinely used by applications to programmatically import multi-factor authentication (MFA) settings from QR codes or configuration URLs provided directly by users or third-party authentication managers.
Historically, this method accepted any syntactically valid otpauth:// URI and parsed its query parameters into an instance of the OTP object. However, versions of the package prior to 11.4.3 contain an improper input validation vulnerability classified under CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes. By supplying query parameter keys that map to internal class properties, an attacker can hijack the deserialization process and execute a target-state injection attack.
This dynamic injection leads to multiple severe conditions including absolute internal state corruption, unexpected TypeErrors that crash the PHP thread, and security validation bypasses. Because many application workflows consume these provisioning URIs unauthenticated during user registration or recovery stages, this vulnerability presents an accessible vector for denial of service (DoS) and application state manipulation. No specialized privileges or complex user actions are required to trigger these states.
To understand the mechanics of the property injection, we must examine the parameter processing logic in src/OTP.php. When a provisioning URI is loaded, query parameters are parsed into name-value pairs and passed to OTP::setParameter($parameter, $value). The legacy implementation checked whether the parameter matched a defined class property using PHP's native property_exists() function.
If property_exists($this, $parameter) returned true, the library executed a dynamic property assignment: $this->{$parameter} = $value. Under standard object-oriented programming paradigms, private or protected variables are isolated from external direct manipulation. However, the use of property_exists against a user-controlled string bypassed these scoping rules, allowing an attacker to overwrite internal object fields that were never meant to be publicly mutable.
Two principal injection vectors arise from this dynamic assignment. First, an attacker can specify parameters[foo]=bar. Since the class contains a protected $parameters array, the code executes $this->parameters = ['foo' => 'bar']. This completely obliterates the existing state map that stores critical variables such as period, digits, digest, and secret. Subsequent calls to verify credentials or retrieve parameters trigger ParameterNotFoundException errors, resulting in a denial-of-service condition.
Second, an attacker can inject typed properties, such as issuer_included_as_parameter=notabool. Since the internal property $issuer_included_as_parameter is explicitly typed as a boolean in PHP, assigning a string to it triggers a native PHP TypeError. In vulnerable versions, this assignment occurs outside the limited exception-handling try-catch block in Factory, meaning the fatal error escapes, aborts execution, and immediately terminates the server thread.
The security vulnerability was addressed in version 11.4.3 through three primary modifications. The first and most critical fix restricts which properties can be dynamically assigned from URI query parameters. Rather than executing a generic property_exists() check, the patched src/OTP.php now enforces a strict whitelist containing only the public label and issuer properties.
// Legacy vulnerable dynamic assignment in src/OTP.php
if (property_exists($this, $parameter)) {
$this->{$parameter} = $value;
} else {
$this->parameters[$parameter] = $value;
}// Patched dynamic assignment in src/OTP.php (v11.4.3)
if (in_array($parameter, ['label', 'issuer'], true)) {
$this->{$parameter} = $value;
} else {
$this->parameters[$parameter] = $value;
}Additionally, the patch modifies src/Factory.php to handle any unexpected anomalies that occur during object creation or population. The loadFromProvisioningUri method now traps all exceptions implementing the PHP global Throwable interface, packaging them into a uniform, safely handled InvalidProvisioningUriException. This prevents any TypeErrors from leaking out to crash the parent process.
// Patched Factory.php exception handling mechanism
try {
$otp = self::createOTP($parsed_url, $clock);
self::populateOTP($otp, $parsed_url);
} catch (InvalidProvisioningUriException $exception) {
throw $exception;
} catch (Throwable $throwable) {
throw new InvalidProvisioningUriException(
'Not a valid OTP provisioning URI',
$throwable->getCode(),
$throwable
);
}Finally, the patch introduces static verification for the maximum allowed OTP digits. In src/OTPInterface.php, a constant is declared: public const MAX_DIGITS = 10. Any input value exceeding this threshold (such as digits=50) is rejected. This prevents standard dynamic truncation integer overflows and division-by-zero errors when calculating 10 ** digits during validation operations.
An attacker can exploit this vulnerability by submitting a crafted provisioning URI to any endpoint that consumes user-provided configuration strings. This scenario typically occurs when a user is setting up multi-factor authentication (MFA) and is given the option to paste a configuration link or upload a backup QR code containing an otpauth:// URI.
To execute a State Corruption attack, the attacker generates a URI containing a nested array parameter matching the protected property $parameters. When the factory processes the URI otpauth://totp/Alice?secret=JBSWY3DPEHPK3PXP¶meters[foo]=bar, the execution of $this->parameters = ['foo' => 'bar'] strips the object of its initialization configuration. If the application subsequently invokes $otp->getDigits(), the execution fails with an unhandled exception because the required digits property was deleted from the state.
To execute a Thread Crash attack, the attacker injects mismatched types into typed properties, such as issuer_included_as_parameter=notabool. Since this assignment bypasses standard type validation in PHP and is not caught by the factory, a fatal TypeError is thrown. The parent web server or application worker thread terminates immediately upon handling this request.
The security impact of GHSA-2JX3-65F3-XR8R is classified as a medium-severity Denial of Service (DoS) and application integrity corruption vulnerability, with a vendor-assigned CVSS v4 score of 5.3. While this vulnerability does not allow remote code execution (RCE) or direct database exfiltration, it breaks the core security assertions of multi-factor authentication systems.
First, the capacity to crash execution threads via uncaught TypeError exceptions allows unauthenticated attackers to systematically disable login endpoints. If an application allows users to register MFA profiles via an open endpoint, sending multiple requests with type-mismatched query variables will exhaust system resources or crash background queue workers processing the imports.
Second, bypassing validation arrays via property injection introduces significant risk of authentication bypass or spoofing. By injecting alternate configurations, attackers can manipulate internal metadata while evading callback-driven parameter checks, leading to discrepancies in identity verification pipelines. This compromises the reliable validation of multi-factor tokens and introduces potential logic flaws in calling applications.
Remediation of this vulnerability requires upgrading spomky-labs/otphp to version 11.4.3 or greater. Upgrading the package completely replaces the vulnerable property check with the strict whitelist constraint and introduces secure error-handling boundaries. This is the only vendor-supported and complete resolution to the flaw.
If an immediate upgrade is impossible, developers must implement an input-sanitization wrapper to intercept and clean the URI before it is processed by the library. This wrapper must parse the URI query parameters and explicitly strip blacklisted keys that map to private or protected object properties, such as parameters, clock, digits, and issuer_included_as_parameter.
function sanitizeProvisioningUri(string $uri): string {
$parsed = parse_url($uri);
if (!isset($parsed['query'])) {
return $uri;
}
parse_str($parsed['query'], $queryData);
$blacklist = ['parameters', 'clock', 'issuer_included_as_parameter', 'digits'];
foreach ($blacklist as $key) {
unset($queryData[$key]);
}
$parsed['query'] = http_build_query($queryData);
return ($parsed['scheme'] ?? 'otpauth') . '://' .
($parsed['host'] ?? '') .
($parsed['path'] ?? '') . '?' .
$parsed['query'];
}In addition to sanitization, software developers should treat all external parser libraries as untrusted boundaries. All interactions with external deserializers, factory loaders, or parser classes should be wrapped inside universal try-catch blocks that intercept PHP Throwable instances. This defensive architectural pattern ensures that logic errors, TypeErrors, or structural exceptions generated deep within imported packages cannot propagate upward and terminate the execution of critical system processes.
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| Product | Affected Versions | Fixed Version |
|---|---|---|
spomky-labs/otphp Spomky-Labs | < 11.4.3 | 11.4.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-915 |
| Attack Vector | Network |
| CVSS v4 Score | 5.3 (Medium) |
| Exploit Status | Proof of Concept |
| Affected Component | Factory::loadFromProvisioningUri |
| Vulnerability Class | Improperly Controlled Modification of Dynamically-Determined Object Attributes |
The product receives input from an upstream component containing name-value pairs, which are dynamically mapped to attributes of an object, allowing an attacker to modify properties that are not intended to be exposed.
CVE-2026-0755 is a critical vulnerability in gemini-mcp-tool (<= 1.1.5) that allows unauthenticated remote code execution on Windows installations and arbitrary local file exfiltration across all supported operating systems. The flaws exist within the execAsync command runner and the input handling logic of the Model Context Protocol (MCP) server, which fails to securely escape arguments passed to Node.js child processes and does not validate local file references in user-supplied prompt strings.
The spomky-labs/otphp library prior to version 11.4.3 is vulnerable to an unhandled DivisionByZeroError crash when parsing provisioning URIs containing a digits parameter value equal to or greater than 40. This allows unauthenticated remote attackers to trigger a Denial of Service by supplying a crafted URI, which causes float-to-integer cast overflow and subsequent division-by-zero fatal error in modern PHP runtimes.
An implementation flaw in the experimental Chacha20Poly1305 key-encryption algorithm within the PHP JWT Framework (web-token/jwt-framework) discards the Poly1305 authentication tag during key wrapping and omits it during decryption. This degrades the Authenticated Encryption with Associated Data (AEAD) protection to unauthenticated ChaCha20, allowing an attacker to manipulate the encrypted Content Encryption Key (CEK) without detection.
An uncontrolled resource consumption vulnerability in the PBES2-HS* key wrapping algorithms of the web-token JWT library allows remote, unauthenticated attackers to cause a denial of service (DoS) by sending JWE tokens with unbounded iteration counts.
An algorithm confusion vulnerability exists in the PHP JWT Framework (web-token/jwt-library) where the JWSVerifier and JWEDecrypter components merge integrity-protected and unprotected headers using insecure methods. Under specific conditions, duplicate parameters defined in unprotected headers override those in integrity-protected headers, allowing an attacker to bypass cryptographic signature verification.
An observable timing discrepancy vulnerability in the web-token/jwt-framework library allows unauthenticated remote attackers to perform a Bleichenbacher / Marvin padding oracle attack against JWE tokens using the RSAES-PKCS1-v1_5 algorithm. By failing to perform constant-time implicit rejection on PKCS#1 v1.5 padding failures, the decryption process leaks structural validation errors via exceptions and early returns, exposing the wrapped Content Encryption Key (CEK) to cryptographic recovery.