Feb 25, 2026·6 min read·2 visits
Pterodactyl Panel < 1.12.0 treated TOTP codes as reusable passwords within their 30-second validity window. Attackers could capture a used token and replay it to gain access.
Pterodactyl Panel, the go-to management solution for game server hosting, suffered from a classic logic flaw in its Two-Factor Authentication implementation. While the system correctly validated that a Time-based One-Time Password (TOTP) was mathematically correct for the current time window, it failed to record that the token had been used. This stateless verification meant the 'One-Time' part of TOTP was merely a suggestion. An attacker with valid credentials and a captured token could replay the authentication request multiple times within the validity window (typically 30-60 seconds), effectively bypassing the intended security controls of 2FA.
Pterodactyl Panel is the heavyweight champion of open-source game server management. It manages Docker containers, handles billing integrations, and generally keeps the Minecraft and Rust ecosystems running. Naturally, it guards these gates with Two-Factor Authentication (2FA), specifically TOTP (Time-based One-Time Passwords). You know the drill: scan the QR code, get the 6-digit code from Authy or Google Authenticator, and you're in.
But here is the dirty little secret about TOTP libraries: most of them are just fancy calculators. They take a secret key and the current time, do some HMAC-SHA1 voodoo, and spit out a 6-digit number. Crucially, the library often doesn't know if that number was used five seconds ago by the legitimate user. It just knows the number is technically correct for the current time slice.
CVE-2025-69197 is the story of what happens when a developer trusts the calculator without checking the receipt. For versions of Pterodactyl prior to 1.12.0, the system verified the math but ignored the history. This created a race condition where a token was valid for its entire 30-60 second lifespan, regardless of how many times it was submitted. It wasn't a One-Time Password; it was a "Valid-For-A-Minute Password."
To understand the bug, you have to understand the laziness inherent in stateless authentication. In a proper TOTP implementation, the server must store the timestamp (or the counter index) of the last successful login. When a new code comes in, the server checks two things:
Pterodactyl missed step 2.
The LoginCheckpointController utilized the PragmaRX\Google2FA library's verifyKey method. This method is stateless. It simply returns true or false based on the hash generation. Because Pterodactyl didn't update the user's record to say "Hey, we just used the token for time slice X," the server had no memory of the event.
This is akin to a movie theater ticket taker who looks at the date on your ticket but forgets to tear the stub. You can walk in, hand the ticket to your friend, and they can walk in too—as long as you both do it before the movie starts. In crypto terms, this is a replay attack enabled by a lack of replay protection.
Let's look at the smoking gun. The fix, applied in commit 032bf076d92bb2f929fa69c1bac1b89f26b8badf, explicitly changes the verification logic from a simple check to a stateful comparison.
The Vulnerable Logic: Previously, the controller likely did something resembling this (pseudocode):
// The "I verified the math" approach
$valid = $google2fa->verifyKey($user->totp_secret, $input_code);
if ($valid) {
Auth::login($user);
}The Fixed Logic:
The patch introduces a new column to the database, totp_authenticated_at. This stores the timestamp of the last successful TOTP exchange. The code switches to verifyKeyNewer, which enforces time progression.
// The "I verified the math AND the timeline" approach
$timestamp = $google2fa->verifyKeyNewer(
$user->totp_secret,
$input_code,
$user->totp_authenticated_at // The memory of the last login
);
if ($timestamp !== false) {
// Update the memory so this token can't be used again
$user->update(['totp_authenticated_at' => $timestamp]);
Auth::login($user);
}By passing $user->totp_authenticated_at as the third argument, the library checks if the token provided belongs to a time window after the stored timestamp. If you try to replay a token from the same 30-second window, the library sees that the window index hasn't increased and rejects it. Simple, elegant, and previously missing.
How does a hacker weaponize this? You still need the username and password, so this isn't a magic key. However, it completely neuters the protection of 2FA in specific, common scenarios.
Scenario: The Streamer / Shoulder Surfer Imagine a server admin streaming their desktop on Discord or Twitch, or perhaps screen-sharing with a "support" agent (social engineering).
123456.123456 code.Because of the vulnerability, the server accepts the attacker's request as long as it falls within the verification window (usually configured to allow for some clock drift, often +/- 1 window, meaning up to 60-90 seconds).
So, the attacker is in. Now what? Pterodactyl isn't just a dashboard; it's a command center for Docker containers.
If the compromised account has Administrator privileges, the impact is effectively Remote Code Execution (RCE). An admin can:
Even for non-admin users, this allows an attacker to hijack game servers, delete backups, or modify configurations to grief players. The vulnerability transforms a "compromised password" scenario—which 2FA is specifically designed to mitigate—back into a full account takeover.
The remediation is straightforward: Update to version 1.12.0 immediately. This version includes the database migration and code changes required to enforce stateful TOTP verification.
For those who enjoy living dangerously and cannot update, you could technically tighten the auth.2fa.window setting in config/pterodactyl.php to 0. This reduces the drift window to strict time matching, making the replay window smaller, but it doesn't fix the underlying logic flaw and might lock out users with slightly desynchronized clocks.
Realistically, just patch the panel. This is logic, not magic. If you don't track state, you don't have security.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Pterodactyl Panel Pterodactyl | < 1.12.0 | 1.12.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-294 |
| Attack Vector | Network |
| CVSS | 6.5 (Medium) |
| Exploit Maturity | PoC / Theoretical |
| Requires Creds | Yes |
| Automated Exploit | Unlikely |
Authentication Bypass by Capture-replay