Tech16 min read

phpMyAdmin 5.2.3 WebAuthn 2FA bypass tested against official releases

IkesanContents

Update (2026-10-29): Verified a separate bug where raw AAGUID byte persistence breaks security key registration with TypeError → phpMyAdmin 5.2 WebAuthn Key Registration Fails with TypeError Due to Raw AAGUID Bytes

TL;DR

Impact Authentication bypass in phpMyAdmin’s WebAuthn two-factor authentication when the fallback CustomServer implementation is active. An attacker who already knows a user’s MySQL password can pass two-factor authentication without a security key because CustomServer does not verify the cryptographic signature.

Conditions Only environments lacking the web-auth/webauthn-lib library. The official 5.2.3 tarball, zip packages, and official Docker images bundle this library, so CustomServer is not used. Environments cloned from Git development branches with --no-dev or Debian system packages can be affected.

Status As of October 29, 2026, no CVE ID or phpMyAdmin security advisory (PMASA) has been published. Signature verification code is also not yet present in the development branch.

Verification Tested on October 29, 2026, in a loopback-only (127.0.0.1) Docker environment. The CustomServer setup succeeded with the PoC, while the official tarball setup under TLS rejected forged signatures with Invalid signature.

Check Verify whether vendor/web-auth/webauthn-lib/src/Server.php exists under the phpMyAdmin root directory to determine which implementation is loaded.


In late October 2026, Abraxas Labs published a proof-of-concept (PoC) repository on GitHub claiming that two-factor authentication in phpMyAdmin 5.2.3 could be bypassed to gain full database access. The repository author assessed the vulnerability at CVSS 8.1 (High).
The PoC claimed that default 5.2.3 release packages were vulnerable.

phpMyAdmin is a web interface commonly used to manage MySQL and MariaDB databases. Because it often runs on production servers, we checked the claims against both the source code and official releases.
The underlying flaw—the total absence of cryptographic signature verification—exists in the code of the custom fallback server.
However, extracting the official tarball from phpmyadmin.net showed that the dependencies differed from the PoC’s core assumption.
Because reading code alone does not confirm runtime behavior, we built a loopback-only Docker lab on October 29, 2026, and ran the PoC against the test configurations.

How phpMyAdmin implements WebAuthn 2FA

phpMyAdmin has supported two-factor authentication since version 4.8.0.
Enabling it requires configuring the phpMyAdmin configuration storage database, after which individual users can enable two-factor authentication in their user settings.
Two methods are supported: one-time passwords via an authenticator app (TOTP) and hardware security keys.
In 5.2.3, the latter appears in the settings menu as “Hardware Security Key (WebAuthn/FIDO2)”.

WebAuthn allows users to authenticate using security keys like YubiKeys through the browser.
During registration, the authenticator generates a key pair, and the server stores only the public key.
During authentication, the server generates a random challenge, which the authenticator signs using its private key along with contextual data.
The server then verifies that signature against the stored public key.
Because the private key never leaves the authenticator, a valid signature proves the authentic possession and interaction with that specific key.
The W3C WebAuthn specification explicitly mandates signature verification using the stored public key.

phpMyAdmin contains two distinct server implementations for this verification: WebauthnLibServer, which relies on the external web-auth/webauthn-lib library, and CustomServer, a custom implementation written directly in phpMyAdmin.
Which class is instantiated is determined by a single line in libraries/classes/Plugins/TwoFactor/WebAuthn.php:

private function createServer(): Server
{
    return class_exists(WebauthnServer::class) ? new WebauthnLibServer($this->twofactor) : new CustomServer();
}

If the library class Webauthn\Server can be autoloaded, phpMyAdmin uses the library. Otherwise, it falls back to CustomServer.
In composer.json, web-auth/webauthn-lib is listed only under suggest and require-dev, not in the main require section.

What CustomServer actually validates

The vulnerable logic is in CustomServer::parseAndValidateAssertionResponse.
When receiving an assertion response from the browser during login, it performs the following checks:

ItemValidation
Authenticator IDChecks if the credential ID in the response matches any credential registered for that user
TypeChecks if clientDataJSON.type equals webauthn.get
ChallengeChecks if clientDataJSON.challenge matches the challenge stored in the session
OriginChecks if the hostname in clientDataJSON.origin matches the target server hostname
rpIdHashChecks if the first 32 bytes of authenticatorData match the SHA-256 hash of the hostname
User Presence (UP) flagChecks if the User Presence bit in authenticatorData flags is set

It also checks structural JSON integrity, verifies that id matches rawId, and checks that fields are valid Base64.
For the signature field, it only verifies that the field exists, is not empty, and can be decoded as Base64.
It never checks the signature against the registered public key.
All six items in the table can be forged by an attacker: clientDataJSON is standard JSON encoded in Base64, and authenticatorData is a fixed byte sequence containing the origin hash, flags, and counter, none of which require a private key to construct.

phpMyAdmin’s own unit test, CustomServerTest, calls this function without supplying any public key and expects it to succeed.
The PoC author cited this test as evidence of missing signature verification.

The identical function exists in the 5.2.1 and 5.2.2 release tags.
Although the PoC targeted 5.2.3 specifically, any version from 5.2.1 onward running CustomServer has this flaw.

Attack flow

The attack demonstrated by the PoC proceeds as follows.
As a prerequisite, the attacker must already know the user’s MySQL username and password.

flowchart TD
  A["Log in with valid MySQL credentials"] --> B["Two-factor authentication prompt displayed"]
  B --> C["Extract challenge and credential ID<br/>from page HTML"]
  C --> D["Construct clientDataJSON and<br/>authenticatorData payloads"]
  D --> E["Submit arbitrary garbage string<br/>in the signature field"]
  E --> F["CustomServer validates format and headers<br/>without checking the signature"]
  F --> G["Authentication succeeds;<br/>access granted to phpMyAdmin"]

The 2FA page HTML contains the server challenge and the list of registered credential IDs (allowCredentials).
The attacker extracts the challenge, constructs matching client data with proper flags, and submits the payload as webauthn_request_response.
In the PoC lab, the credential ID was pre-seeded into the database (32 bytes of 0xA1), and the signature field was filled with the Base64 encoding of the literal string not-a-real-signature-PMA-WEBAUTHN-BYPASS.
The server accepted this response and granted access.

Once authenticated, the attacker can only execute actions permitted by that MySQL user’s privileges.
As the PoC documentation notes, this does not grant OS-level code execution.
However, because 2FA exists to protect against compromised passwords, bypassing it leaves only the password protecting the database.

Official release packages bundle the library

The PoC README and the vulnerability disclosure draft (VENDOR-EMAIL.md) claimed that default tarball releases are vulnerable because running composer install --no-dev skips web-auth/webauthn-lib.
The PoC’s Dockerfile comment also stated that the official tarball lacks the library.

We downloaded phpMyAdmin-5.2.3-all-languages.tar.gz from the official site and inspected its contents.
The package contained vendor/web-auth/webauthn-lib/, including src/Server.php defining Webauthn\Server.
According to vendor/composer/installed.json, the bundled version was web-auth/webauthn-lib v3.3.12.
The English-only zip package (phpMyAdmin-5.2.3-english.zip) contained the same files.

The reason lies in the release packaging script, scripts/create-release.sh.
After running composer update --no-dev, the script explicitly adds suggested packages using composer require, including web-auth/webauthn-lib.
The script also includes an assertion check: if vendor/web-auth/webauthn-lib/src/Server.php is missing, the build terminates with the error “Webauthn-lib should be installed”.

In official release archives, class_exists(WebauthnServer::class) evaluates to true, so createServer() instantiates WebauthnLibServer.
The PoC README acknowledges that environments with webauthn-lib installed use WebauthnLibServer and are not vulnerable.
Instead of testing the official tarball, the PoC Dockerfile takes the 5.2.3 Git source, runs composer install --no-dev, and then explicitly executes rm -rf vendor/web-auth.
That removal is necessary because the 5.2.3 Git tag includes a composer.lock where webauthn-lib was committed as a production dependency during release packaging.
Running composer install --no-dev against the release tag installs the library from that lockfile.
Even the PoC Dockerfile comments acknowledged that “the Git lockfile still contains webauthn-lib”.

Having verified the packaged files and code logic, we next tested the configurations in a Docker lab.

Testing configurations in a Docker lab

The verification was conducted in Docker containers bound strictly to 127.0.0.1.
Both MySQL and phpMyAdmin ran inside the container network, with test credentials (appuser / apppass) and registered credentials initialized by local SQL scripts.
No external network connections were made.

We evaluated three configurations.
To determine whether CustomServer or WebauthnLibServer was active, we executed php -r "var_dump(class_exists('Webauthn\\Server'));" inside each container.

ConfigurationDistribution sourceDependency handlingActive implementation
AGit tag RELEASE_5_2_3composer install --no-dev followed by rm -rf vendor/web-auth (PoC setup)CustomServer
BOfficial tarball phpMyAdmin-5.2.3-all-languages.tar.gzExtracted cleanly (vendor untouched)WebauthnLibServer
COfficial tarball (with TLS)Same as B, plus Apache self-signed TLS and bcmath/gmp PHP extensionsWebauthnLibServer

Configuration A: PoC succeeded as reported

We cloned the PoC repository, placed the 5.2.3 tag in lab/src/, and executed ./run.sh.
The output matched the PoC author’s findings:

IOC password-login status=200 webauthn=True
IOC challenge=PtcxeEGeDNgeIebwFs4/6aXY…
IOC 2fa-post status=200 still_2fa=False landed=True
SUCCESS PMA-WEBAUTHN-BYPASS WebAuthn 2FA accepted with garbage signature

Here, still_2fa=False indicates that the 2FA prompt was dismissed, and landed=True confirms entry into the main phpMyAdmin dashboard.
The submitted signature was only the string not-a-real-signature-PMA-WEBAUTHN-BYPASS, without any cryptographic key operations.

Configuration B: Blocked by HTTP scheme check

Directing the same PoC script against the official tarball configuration failed to pass two-factor authentication, but not due to signature verification:

IOC 2fa-post status=200 still_2fa=True landed=True
Two-factor authentication failed: Invalid scheme. HTTPS required.

In webauthn-lib v3.3.12, AuthenticatorAssertionResponseValidator checks whether clientDataJSON.origin uses https: two steps prior to signature verification.
The only exceptions are origins listed in securedRelyingPartyId, which defaults to an empty list.
phpMyAdmin’s WebauthnLibServer never calls setSecuredRelyingPartyId().
Consequently, over plain HTTP, requests from localhost or 127.0.0.1 fail at this preliminary scheme check.

Because this error masked whether signature verification would succeed or fail, we built Configuration C with TLS enabled.

Configuration C: Testing three distinct signature payloads

In Configuration C, we generated an ECDSA P-256 key pair to simulate a physical security key, exported the public key in COSE format, and registered it in the database.
The private key remained on the client side to generate signatures.
The target URL was updated to https://localhost:18443.

All three test modes used identical clientDataJSON and authenticatorData structures, differing solely in the signature payload:

ModeSignature payloadResultphpMyAdmin response
garbage41-byte ASCII string (not-a-real-signature-PMA-WEBAUTHN-BYPASS)RejectedInvalid data. Should start with a sequence.
wrongkeyValid 64-byte ES256 signature generated by an unassociated P-256 keyRejectedInvalid signature.
validGenuine ES256 signature generated by the registered private keyAccepted-
IOC pma-says: Two-factor authentication failed: Invalid data. Should start with a sequence.
CONTROL-GARBAGE-REJECTED forged signature rejected
IOC pma-says: Two-factor authentication failed: Invalid signature.
CONTROL-WRONGKEY-REJECTED forged signature rejected
IOC 2fa-post status=200 still_2fa=False landed=True
CONTROL-VALID-ACCEPTED genuine signature accepted => harness is correct

Acceptance of the valid signature confirmed that the challenge, origin, rpIdHash, credential ID, and key registration were configured correctly.
Rejection of wrongkey with Invalid signature. confirmed that the server actively verified the signature against the registered public key.
That error originates in step 7.2.16 of AuthenticatorAssertionResponseValidator, where $algorithm->verify() evaluates the signature.

The garbage payload produced a different error message because CoseSignatureFixer::fix() runs immediately before signature verification to normalize raw signatures.
While 64-byte ES256 signatures are parsed directly, other lengths are parsed as ASN.1 DER sequences.
Because the 41-byte string cannot be parsed as DER, processing halts with “Should start with a sequence”, and never reaches verify().
Thus, against an official tarball, the PoC payload cannot reach the signature verification phase.

Pitfalls encountered during verification

Three technical hurdles arose during testing:

First, Configuration B initially crashed before rendering the 2FA form:

Internal error: Assert\InvalidArgumentException: The parameter "aaguid" is missing

PublicKeyCredentialSource::createFromArray() in webauthn-lib requires all class properties to exist in the stored credential record.
The PoC’s initialization SQL omitted aaguid (the 16-byte authenticator model identifier).
Because CustomServer never references aaguid, it worked without it.
For Configuration C, populating aaguid with a 36-character UUID string resolved the exception.

Second, a missing extension error surfaced: Two-factor authentication failed: Requires GMP or bcmath extension.
This exception originates from fgrosse/phpasn1, which webauthn-lib uses for elliptic curve arithmetic.
Neither extension is enabled by default in standard php:8.2-apache images, nor are they listed under suggest in phpMyAdmin’s composer.json.
Installing both extensions was required for Configuration C.

Third, signature counter state management: when a valid assertion succeeds, the library updates the stored counter with the counter value from authenticatorData.
Because the validator requires subsequent assertions to have a strictly greater counter value, replaying the same valid signature fails.
The test harness executed valid last and refreshed database state between runs.
Because signature verification occurs before the counter check, counter values did not affect the garbage or wrongkey rejection results.

Breakdown of installation methods

Installation methodwebauthn-lib statusActive implementationVerified
Official tarball / zip (5.2.3)Bundled (v3.3.12)WebauthnLibServer (Unaffected)Tested (Config B & C)
Official Docker image (5.2.3)Bundled (extracts official tarball)WebauthnLibServer (Unaffected)Verified via tarball
Git release tag (RELEASE_5_2_3) with composer install --no-devBundled via composer.lock (v3.3.12)WebauthnLibServer (Unaffected)Tested
Git branch (QA_5_2) with composer update then --no-devNot installedCustomServer (Vulnerable)Tested
Git branch (master) with composer update then --no-devNot installedCustomServer (Vulnerable)Tested
Debian system package (phpmyadmin)No dependency declaredExpected CustomServerUnverified

Installing from Git is documented in phpMyAdmin’s setup guide (doc/html/_sources/setup.rst.txt), which suggests running composer update followed by composer update --no-dev for production deployments.

The three Git entries in the table reflect testing inside a php:8.2-cli container on October 29, 2026.
The QA_5_2 branch (commit 6b04ffd) lacks a composer.lock, while master (commit 6016908) lists webauthn-lib 5.3.9 only under require-dev.
In both branches, composer update installed vendor/web-auth, but the subsequent composer update --no-dev removed it.
Conversely, on the release tag RELEASE_5_2_3, running composer install --no-dev preserved vendor/web-auth/webauthn-lib/src/Server.php, so class_exists remained true.

If PHP’s ext-zip extension is missing, dependency resolution fails because php-webdriver/webdriver in require-dev requires it.
Even with --no-dev, Composer resolves require-dev rules before deciding what to exclude.
Installing ext-zip allowed Composer to complete successfully.

Code on active Git branches is under active development and differs from the released 5.2.3 codebase.

Debian packages in both trixie (5.2.2) and sid list no WebAuthn dependencies in Depends, Recommends, or Suggests.
Debian packages map PHP dependencies to separate system packages, and Debian’s package search yields no web-auth package.
Under these conditions, Webauthn\Server cannot be autoloaded, which would route authentication to CustomServer, though this was not verified on a Debian host.

Regardless of installation method, exploiting this flaw requires three prerequisites:

  • The phpMyAdmin instance runs in an environment using CustomServer
  • The attacker already possesses the target user’s MySQL username and password
  • The target user has configured WebAuthn (Hardware Security Key) as their two-factor authentication method

Users relying on authenticator apps (TOTP) do not route through this verification path.

Upstream status

As of October 29, 2026, the latest advisory on phpMyAdmin’s security page remains PMASA-2025-3 (January 2025).
No security advisory or CVE identifier has been published on GitHub.
While the PoC repository includes a draft notification to security@phpmyadmin.net (VENDOR-EMAIL.md), it contains no dispatch dates or response history.

In the 5.2 development branch (QA_5_2), pull request #20499 (“Fix WebAuthn\CustomServer credential save”) was merged on September 29, 2026 (commit).
That change updates credential lookups and re-saves signature counters upon login.
While authentication now fails if the credential ID cannot be found, cryptographic signature verification using the stored public key was not added.
Inspection of libraries/classes/WebAuthn/CustomServer.php on QA_5_2 (commit 6b04ffd) and src/WebAuthn/CustomServer.php on master (commit 6016908) revealed no signature verification logic.
Whether PR #20499 was prompted by this vulnerability report cannot be determined from the commit log.

How to check and immediate mitigations

Whether an instance is affected depends entirely on whether Webauthn\Server can be loaded.
For standard Composer layouts, checking for the existence of this file indicates which implementation is active:

ls vendor/web-auth/webauthn-lib/src/Server.php

If this file exists, phpMyAdmin uses WebauthnLibServer, and the bypass does not apply.
If it is absent, CustomServer is active.
In custom autoloader setups, running php -r "var_dump(class_exists('Webauthn\\Server'));" under the phpMyAdmin execution environment confirms the loaded implementation.

For environments running CustomServer, administrators can either install web-auth/webauthn-lib to enable library validation, or restrict phpMyAdmin access to trusted internal networks (such as behind a VPN or IP whitelist) rather than relying on WebAuthn 2FA until an upstream patch is released.
For Git-based installations, adding the version specified in that branch’s require-dev (^3.3.2 for QA_5_2, ^5.3 for master) via composer require enables the library.
For package-managed environments like Debian where modifying vendor directories is impractical, network-level access restrictions are the cleanest mitigation.

Considerations when migrating to the library

Switching to web-auth/webauthn-lib involves two prerequisites discovered during our testing:

First, the PHP environment must provide either the gmp or bcmath extension. Without one of them, elliptic curve signature verification fails with Requires GMP or bcmath extension.

Second, existing credential records created under CustomServer store aaguid as a raw 16-byte binary string. In contrast, webauthn-lib v3.3.12 expects a 36-character UUID string or Base64 encoding.
Testing PublicKeyCredentialSource::createFromArray() directly in PHP showed that records where aaguid is 16 null bytes (0x00) fail with Safe\Exceptions\UrlException.
Records containing bytes with the high bit set (>= 0x80) fail earlier during storage because json_encode() rejects invalid UTF-8 sequences.
If users encounter 2FA errors after enabling webauthn-lib, deleting and re-registering the hardware security key resolves the format incompatibility.