
phpMyAdmin 5.2.3 - High - Remote - CSRF - token strip on /sql
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · phpmyadmin-csrf-sql
Class: CSRF Reach: Remote
phpMyAdmin 5.2.3 (tag RELEASE_5_2_3, commit 962857e). Common.php builds the PSR-7 request from globals before checkTokenRequestParam. On mismatch, Sanitize::removeRequestVars clears $_POST and $_GET but not that snapshot. $sql_query is then copied from $request->getParsedBodyParam('sql_query'). AJAX POSTs abort on token mismatch. Non-AJAX POSTs continue. SqlController prefers stripped $_POST['sql_query'] and otherwise keeps the global.
A victim with a phpMyAdmin session cookie is enough. POST index.php?route=/sql with sql_query and no valid token, and without ajax_request. The SELECT runs as that MySQL user. Default CookieSameSite=Strict in 5.2.3 blocks many cross-site browsers. Same-site, Lax, None, or HTTP-auth still hit it. This is a broken CSRF control either way.
| ID | no CVE yet |
| Class | CSRF |
| Reach | Remote. Default CookieSameSite=Strict blocks many cross-site browsers. Same-site / Lax / None / HTTP-auth remain. |
| CWE | CWE-352 |
| CVSS | High: 8.1 CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N |
| Product | phpMyAdmin |
| Affected | 5.2.3 tag RELEASE_5_2_3 (962857e) |
| Auth | Victim phpMyAdmin session cookie (or HTTP auth). No valid CSRF token. |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1:19240 only |
Anyone who can cause the victim browser to POST to phpMyAdmin without ajax_request can run SQL as that MySQL user.
| Privilege | Method / path | Lab-proven or inferred |
|---|---|---|
| Run attacker SQL as the victim MySQL account (integrity + recon of that SQL box) | POST index.php?route=/sql sql_query=... db=... no token, no ajax_request | Lab-proven. Results body contained PMA-CSRF-SQL-WITNESS. General log showed the SELECT as appuser |
| Abort the same handler on AJAX | Same POST with ajax_request=1 | Lab-proven. Token mismatch. INSERT marker row absent. General log did not run that INSERT |
Skip handlers that only read stripped $_POST | POST /database/structure/drop-table without a token | Lab-proven. keepme still present |
Same gap on export (SaveDir), partition DROP/TRUNCATE, account lock, /config/set, pmadb create | Any handler that reads the PSR-7 body after the strip | Inferred. Not exercised |
Closed on this lab: unauthenticated PMA (cookie login required first), AJAX routes (they exit on mismatch), drop-table without a token, OS shell. Default Strict SameSite means a classic cross-site form often will not send the cookie; the leftover is still a failed CSRF check on same-site and on Lax/None deploys.
Eight parallel source hunts of RELEASE_5_2_3. Common.php creates $request = createFromGlobals() before checkTokenRequestParam(). On mismatch, Sanitize::removeRequestVars() clears $_POST/$_GET only. Non-AJAX continues. SqlController uses global $sql_query if $_POST is empty. Route GET|POST /sql.
Wrong turns: treating AJAX /sql as the leftover (it aborts Token mismatch); treating drop-table as the leftover (that handler only reads stripped $_POST); treating default Strict SameSite as "CSRF is closed" (the control is still broken; same-site / Lax / None remain).
python3 phpmyadmin-csrf-sql-Abraxas-Labs.py
or
cd lab
./run.sh
Loopback only: http://127.0.0.1:19240. Images phpmyadmin:5.2.3 and mysql:8.0. Compose project phpmyadmin-csrf-sql. The PoC is a same-site loopback POST (Strict SameSite still sends the cookie). Witness PMA-CSRF-SQL-WITNESS.
SUCCESS phpmyadmin-csrf-sql sql-ran=yes ajax-abort=yes drop-held=yes PMA-CSRF-SQL-WITNESS
On mismatch, replace the request with an empty body, or refuse all POSTs, not only AJAX. Do not copy sql_query from a stale request. Fail closed like logout.
962857e)libraries/classes/Common.php request snapshot before token stripSanitize::removeRequestVars clears $_POST/$_GET onlySqlController global $sql_queryGNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.