Adminer DSN/ODBC injection, SQLite RCE, and admin-panel CSRF boundaries¶
Source: GitHub Security Advisories unreviewed feed, 2026-08-25 (all first published 2026-08-25): GHSA-34q8-53jm-qc2r (unauthenticated PDO DSN/ODBC TraceFile RCE, critical 9.8), GHSA-j4fh-9p52-f987 (authenticated SQLite VACUUM INTO RCE, high 7.2), GHSA-vfp8-hfj6-qmr2 (SQLite database-list arbitrary file deletion, high 8.1), GHSA-wjcm-7h7g-36vh (CSRF token XOR + low-entropy + loose compare, medium 6.8), GHSA-gx46-wch5-wp5p (rogue-MySQL version-string script-tag break-out, medium 6.1), and GHSA-fh9x-8h7v-rwhv (X-Forwarded-Prefix trust confusion, medium 4.7). All affect Adminer before 5.4.3 (the CSRF/session items also note a 5.5.0 line for the prefix item).
Adminer is a single-file, frequently internet-facing database admin panel, which makes it a recurring recon target. This wave is durable because it chains several reusable admin-panel boundaries: an unauthenticated server field crossing into a PDO/ODBC DSN, a query-language feature (VACUUM INTO) not being blocked despite a sibling restriction, a file-delete sink trusting a caller-supplied path, and a client-supplied X-Forwarded-Prefix being trusted for redirects and cookie paths. The DSN-to-trace-file primitive and the "restricted one operation but not its sibling" pattern generalize to any DB admin tool or DB-backed application that builds driver strings from user input.
Canaries only
Run these checks in a disposable Adminer instance with fake credentials and a scratch web root. Use marker files and a denied execution/file-write sink. Never write a real PHP file to a live web root, delete real files, exfiltrate real database contents, or run arbitrary SQL against production data.
Boundary map¶
| Surface | Caller-controlled value | Privileged transition | Safe positive |
|---|---|---|---|
DB connect server field | ODBC params via ; (TraceFile, TraceOn) | unauthenticated DSN injection writes a trace file | DSN is sanitized; no TraceFile parameter reaches the driver |
| SQLite query | VACUUM INTO '<path>' | authenticated query writes a file to an arbitrary path | VACUUM INTO is blocked the way ATTACH is |
| SQLite db list drop | db[] relative path with .. | drop action deletes an arbitrary writable file | extension/path validated to the media/data root |
| CSRF token | observed mask:masked token | one XOR recovers the session secret; loose == allows type juggling | token not split-publishable; hash_equals compare; high-entropy rand |
| DB server version | crafted MySQL version string | inserted into a CSP-nonce <script>, breaks out of JS context | version string escaped/validated before embedding |
X-Forwarded-Prefix | absolute URL | prepended to REQUEST_URI for redirects + cookie path | trusted-proxy check; no client prefix honored |
Unauthenticated DSN/ODBC TraceFile injection¶
The highest-impact item: Adminer builds a PDO DSN from the server field without sanitizing, and ODBC-flavored DSNs accept ;-separated driver parameters. An attacker can inject TraceFile=<web-root>/canary.php and TraceOn=1, so the driver writes a trace (including PHP) to a web-accessible path.
- Stand up a disposable Adminer instance pointed at a fake ODBC/MySQL backend in a lab, with a scratch, world-writable web root.
- Submit a connect form whose
serverfield carries;TraceFile=<marker>.txt;TraceOn=1(use a non-executable extension first to prove the write without claiming execution). - Record whether the marker file appears in the web root and what the DSN string looked like.
| Input class | Expected secure result |
|---|---|
| ordinary host:port | clean DSN, no extra driver params |
;TraceFile=...;TraceOn=1 | rejected or parameters stripped before the driver |
A bounded positive is the marker file being created in the scratch web root from an unauthenticated connect. Report the DSN-injection-to-trace-write primitive. Do not write an executable .php to a live host or execute it; the write primitive is the finding, execution is inferred only if separately authorized and proven.
SQLite VACUUM INTO vs ATTACH (restricted-sibling gap)¶
Adminer blocks ATTACH but not VACUUM INTO, which also writes a file. This is the classic "one sibling operation restricted, its twin not" gap.
- In a disposable Adminer + SQLite setup, run an ordinary
VACUUMto confirm the baseline. - Run
VACUUM INTO '<scratch-root>/canary.db'and record whether the write succeeds to a path the operator chose. - Compare against
ATTACHto show the asymmetric restriction.
A positive is the file being written to the attacker-chosen path under an authenticated session. Report the missing block with the exact query; prove only with a marker inside the scratch root.
SQLite database-list file deletion¶
The drop action for a SQLite database trusts the db[] path. Supply a relative path with ../ segments and record whether unlink() removes a file outside the data root. Use a sibling marker file in the scratch root; never target real config or data. A positive is the marker being deleted from outside the intended directory.
Admin-panel CSRF: token split-publish + weak compare¶
Three compounding weaknesses make the CSRF control effectively forgeable:
- The token is sent as
rand XOR secret : rand; observing one token (sniff, log,Referrer, XSS) lets an attacker XOR once to recover the secret and mint unlimited valid tokens. rand(1, 1e6)is ~20 bits — brute-forable blind.- Verification uses loose
==, enabling PHP type juggling.
Check each edge in a lab: capture a single token, recover the secret, and forge; test == with a juggling candidate. Report the token-design flaw and the weak compare; do not forge a real admin action against a live deployment.
Rogue-MySQL version-string script break-out¶
Adminer inserts the database server's version string into a CSP-nonce <script>. A rogue MySQL server (an attacker-controlled backend) can return a version string that breaks out of the JS context. Test with a fake backend returning a crafted version string and record whether the embedded string escapes the script context; do not load real payloads on a live panel.
X-Forwarded-Prefix trust confusion¶
Adminer prepends the client-supplied X-Forwarded-Prefix to REQUEST_URI with no trusted-proxy check. Supply an absolute URL and record its flow into Location, Set-Cookie path, and self-links. A positive is an unauthenticated attacker controlling the cookie path and redirect target; note that CR/LF is not injectable, so this is open-redirect/cookie-path confusion, not header splitting or XSS.
Reporting heuristics¶
- Frame the unauthenticated DSN item as "Adminer
serverfield builds an unsanitized ODBC DSN;TraceFilewrites to the web root," with the connect request and the marker-file evidence. - Keep the SQLite RCE and file-delete items explicitly authenticated and scoped to the query/drop sink, so they are not conflated with the unauthenticated DSN path.
- For the CSRF and prefix items, cite the specific weakness (XOR-recoverable token, low-entropy rand, loose
==, untrusted prefix) and the affected routes. - Cite the version bounds (
< 5.4.3; the prefix note references a5.5.0line).
Safety¶
- Authorized, in-scope targets only; Adminer panels are commonly exposed and often front real databases.
- Fake credentials and a scratch web root; marker files only, never real config/data.
- No executable writes, no real file deletion, no real SQL execution, no exfiltration of live database contents.
- Do not run forged CSRF actions or execute a written trace file on production hosts.
Reviewed but not promoted here¶
No separate items in this Adminer wave exceed the boundaries above; all six are covered.
October 12 follow-up: UTF-8 BOM prefix defeats the ATTACH/VACUUM INTO blocklist → authenticated SQLite RCE (GHSA-q48c-r4q8-5c57/CVE-2026-109347, high, Adminer < 6.1.1)¶
The August page's VACUUM INTO-vs-ATTACH sibling-gap now has its mirror: Adminer blocks those statements by statement-prefix matching, and a leading UTF-8 byte-order mark (EF BB BF) slips past the matcher while SQLite still parses and executes ATTACH/VACUUM INTO. Authenticated SQLite users attach a database file at a PHP-served path and create a table whose name is PHP code — executed as the web-server user.
Sharpened rules for this page's standing axis:
- Blocklists that match the first token of a statement have an invisible-character shadow grammar: BOM, leading whitespace forms, comments (
/*x*/ATTACH), and encoding-prefix variants all deserve a spot in the differential battery. Test byte-level prefixes (raw\xEF\xBB\xBF, UTF-16 BOM, NBSP) before concluding a statement-class block is airtight. - The sink shifted from "write a file to a path" to "write a file whose name is code at a PHP-served path" — in DB admin panels, object names (table, index, function) are file paths or markup on some engines; enumerate naming sinks, not just COPY/OUT/INTO verbs.
- Proof stays lab-bounded per this page's safety section: disposable Adminer + scratch web root + a table named with a harmless
phpinfo()-free marker statement; never write runnable files to live hosts.