CVE-2026-78655: Punk::Plugin::TOTP versions before 0.05 for Perl allow the second-factor attempt limit to be reset by replaying an earlier session cookie because the challenge route counts failures in the session
Punk::Plugin::TOTP versions before 0.05 for Perl allow the second-factor attempt limit to be reset by replaying an earlier session cookie because the challenge route counts failures in the session.
The POST handler on challengepath keeps the failure count as tries inside the totppending record in the session, raising it on each rejected code and deleting the pending record once it reaches attempts, five by default. Punk::Session carries the session in a signed cookie unless the application declares a store, and keeps no server-side record, so an earlier value of the same session stays valid until the expiry stamped inside it. A client that saves the cookie before its failed attempts and presents it again gets the pending record back with its counter, and the limit never fires. The replayed record is accepted while its own expiry, pendingttl seconds from the challenge and 300 by default, has not passed.
Sessions declared with a store are not affected: the pending record and its counter then live server-side.
The attempt limit does not bound guessing of the second factor, which is left to the per-address rate limit the plugin registers on the same path, 30 requests per 60 seconds.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
Punk::Plugin::TOTPto a version that resolves this vulnerability.Fixed in 0.05 - Configuration
Add the two columns where the TOTP failure count is kept (default: totp_failed and totp_failed_at, named by fields) to the user table.
Punk-TOTP user table fields for TOTP attempt tracking = totp_failed and totp_failed_at
Event History
Frequently Asked Questions
Which deployments are affected by the attempt-limit bypass?
Deployments using versions before 0.05 are affected when Punk::Session uses its default signed-cookie session handling, meaning no session store has been declared. Deployments with a declared session store are not affected because the pending TOTP record and failure counter are kept server-side.
What does an attacker need to bypass the second-factor attempt limit?
The attacker must save a session cookie containing a pending TOTP challenge before exhausting failed attempts, then replay that earlier cookie. The replay works only until the pending record expires, which is pending_ttl seconds from the challenge and defaults to 300 seconds.
Does this remove all limits on TOTP guessing?
It bypasses the per-session second-factor attempt limit, which defaults to five attempts. The plugin's per-address rate limit still applies on the challenge path at 30 requests per 60 seconds.
What can be done if an upgrade is not immediately possible?
Declare a session store so that pending TOTP state and its failure counter are maintained server-side rather than in a replayable signed cookie. The existing per-address rate limit remains relevant but does not restore the session-based attempt limit.