Every CVESearch/Get API key

GHSA-g7m4-839x-ch6v

spomky-labs/otphp: Unbounded digits parameter in a provisioning URI triggers an uncaught DivisionByZeroError in OTP generation

8.7 HighFix available

Summary

The digits parameter parsed from a provisioning URI is validated only with a lower bound ($value > 0) and has no upper bound (src/OTP.php:353-357). OTP generation computes $code % (10 ** $this->getDigits()) (src/OTP.php:283). When digits is large enough that 10 ** digits overflows PHP's integer range and the (int) cast yields 0 (around digits >= 40 on 64-bit PHP 8.x), the modulo operand becomes 0 and PHP raises a DivisionByZeroError.

Impact

OTPHP\Factory::loadFromProvisioningUri() forwards the attacker-controlled digits query value to setParameter('digits', $value), so a hostile URI such as otpauth://totp/Alice?secret=JBSWY3DPEHPK3PXP&digits=50 produces an OTP object whose at(), now(), and verify() all throw DivisionByZeroError. Because DivisionByZeroError extends Error (not Exception), callers that guard OTP generation with a catch (\Exception) do not catch it, turning a malformed URI into an unhandled fatal error (denial of service of the verification path).

Measured threshold on PHP 8.3: digits = 30 works, digits >= 40 throws DivisionByZeroError: Modulo by zero.

Affected component

  • src/OTP.php:353-357 — digits parameter callback (no upper bound)
  • src/OTP.php:283 — $code % (10 ** $this->getDigits())

Proof of concept

use OTPHP\Factory;
use OTPHP\InternalClock;

$otp = Factory::loadFromProvisioningUri(
    'otpauth://totp/Alice?secret=JBSWY3DPEHPK3PXP&digits=50',
    new InternalClock()
);
$otp->at(0); // DivisionByZeroError: Modulo by zero (escapes catch (\Exception))

Remediation

Enforce a sane upper bound on digits in the parameter validation callback (e.g. reject values above 8–10, the practical range for OTPs) so that an out-of-range value is rejected with a documented exception instead of producing an object that fails later with an uncatchable Error.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
None
User interaction
None

What an attacker can affect

Confidentiality
None
Integrity
None
Availability
High

What each source reports

GitHub Advisories Score used8.7

spomky-labs/otphp: Unbounded digits parameter in a provisioning URI triggers an uncaught DivisionByZeroError in OTP generation

Summary

The digits parameter parsed from a provisioning URI is validated only with a lower bound ($value > 0) and has no upper bound (src/OTP.php:353-357). OTP generation computes $code % (10 ** $this->getDigits()) (src/OTP.php:283). When digits is large enough that 10 ** digits overflows PHP's integer range and the (int) cast yields 0 (around digits >= 40 on 64-bit PHP 8.x), the modulo operand becomes 0 and PHP raises a DivisionByZeroError.

Impact

OTPHP\Factory::loadFromProvisioningUri() forwards the attacker-controlled digits query value to setParameter('digits', $value), so a hostile URI such as otpauth://totp/Alice?secret=JBSWY3DPEHPK3PXP&digits=50 produces an OTP object whose at(), now(), and verify() all throw DivisionByZeroError. Because DivisionByZeroError extends Error (not Exception), callers that guard OTP generation with a catch (\Exception) do not catch it, turning a malformed URI into an unhandled fatal error (denial of service of the verification path).

Measured threshold on PHP 8.3: digits = 30 works, digits >= 40 throws DivisionByZeroError: Modulo by zero.

Affected component

  • src/OTP.php:353-357 — digits parameter callback (no upper bound)
  • src/OTP.php:283 — $code % (10 ** $this->getDigits())

Proof of concept

use OTPHP\Factory;
use OTPHP\InternalClock;

$otp = Factory::loadFromProvisioningUri(
    'otpauth://totp/Alice?secret=JBSWY3DPEHPK3PXP&digits=50',
    new InternalClock()
);
$otp->at(0); // DivisionByZeroError: Modulo by zero (escapes catch (\Exception))

Remediation

Enforce a sane upper bound on digits in the parameter validation callback (e.g. reject values above 8–10, the practical range for OTPs) so that an out-of-range value is rejected with a documented exception instead of producing an object that fails later with an uncatchable Error.

Record
GHSA-g7m4-839x-ch6v
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
Weakness
CWE-369, CWE-1284
Updated
2026-11-02
OSV8.7

spomky-labs/otphp: Unbounded digits parameter in a provisioning URI triggers an uncaught DivisionByZeroError in OTP generation

Summary

The digits parameter parsed from a provisioning URI is validated only with a lower bound ($value > 0) and has no upper bound (src/OTP.php:353-357). OTP generation computes $code % (10 ** $this->getDigits()) (src/OTP.php:283). When digits is large enough that 10 ** digits overflows PHP's integer range and the (int) cast yields 0 (around digits >= 40 on 64-bit PHP 8.x), the modulo operand becomes 0 and PHP raises a DivisionByZeroError.

Impact

OTPHP\Factory::loadFromProvisioningUri() forwards the attacker-controlled digits query value to setParameter('digits', $value), so a hostile URI such as otpauth://totp/Alice?secret=JBSWY3DPEHPK3PXP&digits=50 produces an OTP object whose at(), now(), and verify() all throw DivisionByZeroError. Because DivisionByZeroError extends Error (not Exception), callers that guard OTP generation with a catch (\Exception) do not catch it, turning a malformed URI into an unhandled fatal error (denial of service of the verification path).

Measured threshold on PHP 8.3: digits = 30 works, digits >= 40 throws DivisionByZeroError: Modulo by zero.

Affected component

  • src/OTP.php:353-357 — digits parameter callback (no upper bound)
  • src/OTP.php:283 — $code % (10 ** $this->getDigits())

Proof of concept

use OTPHP\Factory;
use OTPHP\InternalClock;

$otp = Factory::loadFromProvisioningUri(
    'otpauth://totp/Alice?secret=JBSWY3DPEHPK3PXP&digits=50',
    new InternalClock()
);
$otp->at(0); // DivisionByZeroError: Modulo by zero (escapes catch (\Exception))

Remediation

Enforce a sane upper bound on digits in the parameter validation callback (e.g. reject values above 8–10, the practical range for OTPs) so that an out-of-range value is rejected with a documented exception instead of producing an object that fails later with an uncatchable Error.

Record
GHSA-g7m4-839x-ch6v
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
Weakness
CWE-1284, CWE-369
Updated
2026-11-02

Affected packages

Affected packages
PackageAffectedFixed
spomky-labs/otphp
Packagist
before 11.4.3
11.4.3
spomky-labs/otphp
Packagist
before 11.4.3, plus listed versions
1.0.1-stable11.0.311.1.011.1.111.2.0
Show 45 moreShow less
11.2.111.2.211.3.011.4.011.4.111.4.25.0.0v1.0.0-stablev10.0.0v10.0.1v10.0.2v10.0.3v11.0.0v11.0.1v11.0.2v2.0.0-stablev2.0.1-stablev2.0.2-stablev3.0.0-stablev3.0.1-stablev3.1.0v3.1.1v4.0.0v4.0.1v4.0.2v4.0.3v4.0.4v5.0.1v6.0.0v6.0.1v6.0.2v6.0.3v6.0.4v7.0.0v7.0.1v7.0.2v7.0.3v7.0.4v8.0.0v8.1.0v8.2.0v8.3.0v8.3.1v8.3.2v8.3.3
11.4.3

History

History
RecordedChangedSources
first indexedGitHub Advisories, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

GHSA-g7m4-839x-ch6v is a high vulnerability (CVSS 8.7) in spomky-labs/otphp, published June 18, 2026. No source lists public exploit code. Fixed in spomky-labs/otphp 11.4.3.

References

This page doesn't list everything

Left out here: 10 versions. The JSON record has them.

MoveEnter Open