
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
Klasse: SQLi Reichweite: 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 konkateniert 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 Request-Daten in func()->count('*')->over()->range($start, $end) weiterleitet, setzt den String des Aufrufers ungebunden und unquotiert in RANGE BETWEEN.
| ID | noch kein CVE |
| Klasse | SQLi |
| Reichweite | Remote (App, die Request-Daten 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.
| Privileg | Methode / Sink | Art | Lab-bewiesen 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 | Lab-bewiesen. Kompiliertes SQL ist RANGE BETWEEN 1) /* CAKEPHP-WINDOW-SQLI-WITNESS */ -- PRECEDING mit leerem Binder |
| Zeilen als DB-Benutzer der App lesen oder ändern | Dieselbe Query, ausgeführt | Integrität / Recon der DB | Abgeleitet aus dem Splice. Dieses Lab ist nur Compile |
| Frame-Typ oder Richtung auf dieselbe Weise ändern | frame($type, $offset, $direction, ...) konkateniert diese Strings ebenfalls | Integrität des kompilierten SQL | Abgeleitet aus sql() / buildOffsetSql(). Lab verwendete range() |
rows() / groups() auf dieselbe Weise brechen | Typisiert ?int | Geschlossen | Lab-bewiesen geschlossen. rows(3) ist ROWS BETWEEN 3 PRECEDING, nicht quotiert, nicht gebunden |
Geschlossen auf diesem Pin: FunctionsBuilder cast / extract / dateAdd / jsonValue (CVE-2026-79752 / CVE-2026-77635); Query::limit / offset (typisiert int). 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-Reste: collate SQLi, attachment CRLF, cache unserialize.
Ich habe cakephp/cakephp 5.4.2 nach den FunctionsBuilder-SQLi-GHSAs von 2026 gepinnt. Zehn Suchen auf diesem Tag. CSRF / FormProtection, View LFI, BodyParser XXE, Mass Assignment, Helper XSS und clientIp() ohne trustProxy kamen in dieser Messlatte 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 ein ExpressionInterface.
Bereits dokumentierte Irrwege: rows() als das Loch zu behandeln (es ist typisiert int); zu behaupten, dass Standard-cakephp/app dies über ein öffentliches Formular exponiert; das injizierte SQL in SQLite als Oracle auszuführen (POC-READY ist nur Compile); eine Reverse Shell. Theater.
python3 cakephp-window-sqli-Abraxas-Labs.py
oder
cd lab
./run.sh
Ziel nur http://127.0.0.1:19440. Winzige PHP-App, cakephp/database 5.4.2, SQLite :memory:. GET /?mode=range&start= wird als String in range() übergeben. GET /?mode=rows&n=3 ist die Int-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
Kontrolle:
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 quotieren oder ExpressionInterface verlangen. Einen Test hinzufügen, dass ein nicht-int-range()-Offset zu einem Platzhalter oder einem quotierten Literal wird, nicht zu rohem SQL.
887b641de9f7aba06071f98f17edcbc573cbcf63)WindowExpression.php · AggregateExpression.phpGNU Affero GPL v3.0. Siehe LICENSE. Nur Loopback-Lab. Keine Gewährleistung.