
phpMyAdmin - High 8.1 - Authenticated POST /index.php - 2FA Bypass
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · PMA-5.2.3-WebAuthn-2FA
phpMyAdmin 5.2.3 - phpMyAdmin
Default tarball. web-auth/webauthn-lib is suggest / require-dev. composer install --no-dev does not install it. createServer therefore uses CustomServer. That class parses response.signature (the field must exist and decode) and never COSE-verifies it. It never uses the stored credentialPublicKey. The product's own CustomServerTest accepts an assertion with no public key.
After the MySQL password, a garbage WebAuthn signature that still matches challenge, origin, rpIdHash, and user-present is a full phpMyAdmin session. That is the database.
| ID | no CVE yet |
| CWE | CWE-347, CWE-287 |
| CVSS | High: 8.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
| Product | phpMyAdmin |
| Affected | 5.2.3 default tarball (web-auth/webauthn-lib absent) |
| Auth | MySQL password, then WebAuthn skipped |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
They still need the MySQL password (cookie login). After that password, they POST webauthn_request_response with matching challenge, origin host, rpIdHash, user-present, enrolled credential id, and a garbage signature. They land in the normal UI. 2FA is gone. That is RCE on the data, not a PHP shell on the host.
If you actually installed web-auth/webauthn-lib, you are on WebauthnLibServer and not this bug.
I read createServer, then parseAndValidateAssertionResponse, then the unit test that does not pass a public key. Structural checks are real: credential id in allowCredentials, clientData.type == webauthn.get, challenge matches session, origin host, rpIdHash, user-present. Signature is parsed and ignored.
The first client I pointed at this was polite. It POSTed a WebAuthn JSON without a prior password session. Login page. It POSTed route=/ with a signature that did not even satisfy the structural checks. You stay on 2FA. webauthn_request_response is still in the HTML.
Wrong turns already recorded: Webauthn\Server present (wrong server class); challenge not the one from this session; origin host mismatch; user-present flag clear; credential id not in allowCredentials; unauthenticated, no MySQL password; a reverse shell. Theatre. The tell is the 2FA field gone and a phpMyAdmin navigation page.
Client bugs look like product bugs. A helper that shadows http.client never sends a packet. .recv() on an HTTPResponse is not .read(). I mention that once because it cost time and looked, for a minute, like phpMyAdmin was fine.
Then: POST username/password/token. Pull the session challenge from the 2FA page. POST the forged assertion. still_2fa=False landed=True. Lab Dockerfile copies 5.2.3, composer install --no-dev, then deletes vendor/web-auth so CustomServer is the one in play. The last php -r exits 1 if Webauthn\Server exists. The image must fail the build if the lib is still there. init.sql enrolls WebAuthn 2FA for appuser without a hardware key.
cd lab
./run.sh
Target only http://127.0.0.1:18080. Place phpMyAdmin 5.2.3 as src/ next to the Dockerfile first.
password-login status=200 webauthn=True
2fa-post status=200 still_2fa=False landed=True
SUCCESS PMA-WEBAUTHN-BYPASS WebAuthn 2FA accepted with garbage signature
Install web-auth/webauthn-lib so createServer uses the lib, or actually COSE-verify response.signature against the stored public key in CustomServer. Until then, disable WebAuthn 2FA or do not expose this phpMyAdmin.
CustomServer::parseAndValidateAssertionResponse · WebAuthn::createServer · CustomServerTestGNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.