
CakePHP 5.4.2 - Hoch - Remote - SQLi - WindowExpression-Bereichs-/Frame-Offsets in SQL eingefügt
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cakephp-window-sqli
Write-up: https://abraxaslabs.tech/research/cakephp-window-sqli
Klasse: SQLi Erreichbarkeit: Remote
CakePHP 5.4.2 (887b641) - Cake Software Foundation
CVE-2026-79752 hat FunctionsBuilder cast / extract / dateAdd mit ensureSimpleString() geschlossen. WindowExpression::range() und frame() waren nicht Teil dieses Fixes. rows() und groups() nehmen ?int. range() nimmt weiterhin einen String. buildOffsetSql verkettet den Offset, den Frame-Typ und PRECEDING / FOLLOWING in OVER. Der Binder wird nicht verwendet, es sei denn, der Offset ist bereits ein ExpressionInterface.
Eine App, die Anfragedaten in func()->count('*')->over()->range($start, $end) weiterleitet, setzt den String des Aufrufers ungebunden und ohne Anführungszeichen in RANGE BETWEEN.
| ID | noch keine CVE |
| Klasse | SQLi |
| Erreichbarkeit | Remote (App, die Anfragedaten in range() / frame() weiterleitet) |
| CWE | CWE-89 |
| CVSS | Hoch: 8.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
| Produkt | CakePHP (cakephp/database) |
| Betroffen | 5.4.2 (887b641de9f7aba06071f98f17edcbc573cbcf63) |
| Auth | Anwendung, die Query-/Formulardaten in ->over()->range($start, $end) / frame() weiterleitet |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Ein Benutzer, der die Fenstergrenze festlegen kann, die die App weiterleitet (Analytics „letzte N Tage“, ein Report-Builder), verwandelt die OVER-Klausel in zusätzliches SQL.
| Berechtigung | Methode / Senke | Art | Im Lab nachgewiesen oder abgeleitet |
|---|---|---|---|
Ein SQL-Fragment in OVER (RANGE BETWEEN ...) einschleusen | GET /?mode=range&start= in AggregateExpression::range($start, $end) / WindowExpression::frame() | Integrität des kompilierten SQL | Im Lab nachgewiesen. Das kompilierte SQL lautet RANGE BETWEEN 1) /* CAKEPHP-WINDOW-SQLI-WITNESS */ -- PRECEDING mit einem leeren Binder |
| Zeilen als DB-Benutzer der App lesen oder ändern | Dieselbe Abfrage, ausgeführt | Integrität / Recon der DB | Abgeleitet aus der Einschleusung. Dieses Lab ist nur auf Kompilierung beschränkt |
| Frame-Typ oder -Richtung auf dieselbe Weise ändern | frame($type, $offset, $direction, ...) verkettet auch diese Strings | Integrität des kompilierten SQL | Abgeleitet aus sql() / buildOffsetSql(). Das Lab verwendete range() |
rows() / groups() auf dieselbe Weise brechen | Typisiertes ?int | Geschlossen | Im Lab als geschlossen nachgewiesen. rows(3) ist ROWS BETWEEN 3 PRECEDING, nicht in Anführungszeichen, nicht gebunden |
Geschlossen bei diesem Pin: FunctionsBuilder cast / extract / dateAdd / jsonValue (CVE-2026-79752 / CVE-2026-77635); Query::limit / offset (typisiertes int). Das Standard-cakephp/app ruft range() nicht mit Query-String-Daten auf. Der Rest ist die dokumentierte Window-API. Kein unauthentifiziertes RCE auf einer Skeleton-App. Keine Shell.
Gleiche Pin-Überreste: collate SQLi, attachment CRLF, cache unserialize.
Ich habe cakephp/cakephp 5.4.2 nach den FunctionsBuilder-SQLi-GHSAs von 2026 gepinnt. Zehn Hunts auf diesem Tag. CSRF / FormProtection, View LFI, BodyParser XXE, Mass Assignment, Helper XSS und clientIp() ohne trustProxy kamen in dieser Bar leer zurück. WindowExpressionTest::testRange behauptet weiterhin:
$w = (new WindowExpression())->range("'1 day'", "'10 days'");
buildOffsetSql bindet niemals einen String-Offset:
protected function buildOffsetSql(
ValueBinder $binder,
ExpressionInterface|string|int|null $offset,
string $direction,
): string {
if ($offset === 0) {
return 'CURRENT ROW';
}
if ($offset instanceof ExpressionInterface) {
$offset = $offset->sql($binder);
}
return sprintf('%s %s', $offset ?? 'UNBOUNDED', $direction);
}
$binder wird nicht verwendet, es sei denn, der Offset ist bereits eine ExpressionInterface.
Bereits festgehaltene Fehlversuche: rows() als die Lücke zu behandeln (es ist als int typisiert); zu behaupten, das Standard-cakephp/app lege dies in einem öffentlichen Formular offen; das injizierte SQL in SQLite als Orakel auszuführen (POC-READY ist nur kompilierbar); eine Reverse Shell. Theater.
python3 cakephp-window-sqli-Abraxas-Labs.py
oder
cd lab
./run.sh
Ziele nur http://127.0.0.1:19440. Kleine PHP-App, cakephp/database 5.4.2, SQLite :memory:. GET /?mode=range&start= wird als String an range() übergeben. GET /?mode=rows&n=3 ist die Integer-Kontrolle.
SUCCESS cakephp-window-sqli pin=5.4.2 range-raw=yes rows-numeric=yes bound=no quoted=no CAKEPHP-WINDOW-SQLI-WITNESS
IOC kompiliertes SQL:
SELECT (COUNT(*) OVER (RANGE BETWEEN 1) /* CAKEPHP-WINDOW-SQLI-WITNESS */ -- PRECEDING AND CURRENT ROW)) AS n FROM sqlite_master
Steuerung:
SELECT (COUNT(*) OVER (ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)) AS n FROM sqlite_master
Numerische Offsets binden. Frame-Typ (RANGE / ROWS / GROUPS) und Richtung (PRECEDING / FOLLOWING) per Allowlist zulassen. Intervall-Strings in Anführungszeichen setzen oder ExpressionInterface erfordern. Einen Test hinzufügen, der sicherstellt, dass ein nicht-ganzzahliger range()-Offset zu einem Platzhalter oder einem in Anführungszeichen gesetzten Literal wird, nicht zu rohem SQL.
887b641de9f7aba06071f98f17edcbc573cbcf63)WindowExpression.php · AggregateExpression.phpGNU Affero GPL v3.0. Siehe LICENSE. Nur Loopback-Lab. Keine Gewährleistung.