
CakePHP 5.4.2 - Alto - Remoto - SQLi - Offset di intervallo/frame di WindowExpression inseriti in SQL
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cakephp-window-sqli
Classe: SQLi Raggiungibilità: Remota
CakePHP 5.4.2 (887b641) - Cake Software Foundation
CVE-2026-79752 ha chiuso FunctionsBuilder cast / extract / dateAdd con ensureSimpleString(). WindowExpression::range() e frame() non erano inclusi in quella correzione. rows() e groups() accettano ?int. range() accetta ancora una stringa. buildOffsetSql concatena l'offset, il tipo di frame e PRECEDING / FOLLOWING in OVER. Il binder non viene utilizzato a meno che l'offset non sia già un ExpressionInterface.
Un'app che inoltra dati di richiesta in func()->count('*')->over()->range($start, $end) inserisce la stringa del chiamante in RANGE BETWEEN senza binding e senza quoting.
| ID | nessuna CVE al momento |
| Classe | SQLi |
| Raggiungibilità | Remota (app che inoltra dati di richiesta in range() / frame()) |
| CWE | CWE-89 |
| CVSS | Alta: 8.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
| Prodotto | CakePHP (cakephp/database) |
| Affetto | 5.4.2 (887b641de9f7aba06071f98f17edcbc573cbcf63) |
| Auth | applicazione che inoltra dati di query/form in ->over()->range($start, $end) / frame() |
| Licenza | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1 |
Un utente che può impostare il limite della window che l'app inoltra (analytics "ultimi N giorni", un report builder) trasforma la clausola OVER in SQL aggiuntivo.
| Privilegio | Metodo / sink | Tipo | Provato in lab o dedotto |
|---|---|---|---|
Inserire un frammento SQL in OVER (RANGE BETWEEN ...) | GET /?mode=range&start= in AggregateExpression::range($start, $end) / WindowExpression::frame() | Integrità dell'SQL compilato | Provato in lab. L'SQL compilato è RANGE BETWEEN 1) /* CAKEPHP-WINDOW-SQLI-WITNESS */ -- PRECEDING con binder vuoto |
| Leggere o modificare righe come l'utente DB dell'app | Stessa query, eseguita | Integrità / ricognizione del DB | Dedotto dall'inserimento. Questo lab è solo di compilazione |
| Cambiare il tipo di frame o la direzione allo stesso modo | frame($type, $offset, $direction, ...) concatena anche quelle stringhe | Integrità dell'SQL compilato | Dedotto da sql() / buildOffsetSql(). Il lab ha usato range() |
Rompere rows() / groups() allo stesso modo | Tipizzati ?int | Chiuso | Provato chiuso in lab. rows(3) è ROWS BETWEEN 3 PRECEDING, senza quoting, senza binding |
Chiusi su questo pin: FunctionsBuilder cast / extract / dateAdd / jsonValue (CVE-2026-79752 / CVE-2026-77635); Query::limit / offset (int tipizzati). Il default cakephp/app non chiama range() con dati da query-string. Ciò che resta è l'API window documentata. Non è RCE non autenticata su un'app skeleton. Non è una shell.
Altri residui sullo stesso pin: collate SQLi, attachment CRLF, cache unserialize.
Ho fissato cakephp/cakephp 5.4.2 dopo le GHSA del 2026 sulla SQLi in FunctionsBuilder. Dieci ricerche su quel tag. CSRF / FormProtection, View LFI, BodyParser XXE, mass assignment, helper XSS e clientIp() senza trustProxy sono risultati vuoti in questa soglia. WindowExpressionTest::testRange asserisce ancora:
$w = (new WindowExpression())->range("'1 day'", "'10 days'");
buildOffsetSql non fa mai il binding di un offset stringa:
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 non viene utilizzato a meno che l'offset non sia già un ExpressionInterface.
Vicoli ciechi già registrati: trattare rows() come la falla (è int tipizzato); sostenere che il cakephp/app stock esponga questo su un form pubblico; eseguire l'SQL iniettato in SQLite come oracolo (POC-READY è solo di compilazione); una reverse shell. Teatro.
python3 cakephp-window-sqli-Abraxas-Labs.py
oppure
cd lab
./run.sh
Target solo http://127.0.0.1:19440. Piccola app PHP, cakephp/database 5.4.2, SQLite :memory:. GET /?mode=range&start= viene passato come stringa in range(). GET /?mode=rows&n=3 è il controllo int.
SUCCESS cakephp-window-sqli pin=5.4.2 range-raw=yes rows-numeric=yes bound=no quoted=no CAKEPHP-WINDOW-SQLI-WITNESS
IOC SQL compilato:
SELECT (COUNT(*) OVER (RANGE BETWEEN 1) /* CAKEPHP-WINDOW-SQLI-WITNESS */ -- PRECEDING AND CURRENT ROW)) AS n FROM sqlite_master
Controllo:
SELECT (COUNT(*) OVER (ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)) AS n FROM sqlite_master
Fare il binding degli offset numerici. Consentire tramite allowlist il tipo di frame (RANGE / ROWS / GROUPS) e la direzione (PRECEDING / FOLLOWING). Per le stringhe di intervallo, metterle tra virgolette o richiedere ExpressionInterface. Aggiungere un test che verifichi che un offset non-int di range() diventi un placeholder o un letterale tra virgolette, non SQL grezzo.
887b641de9f7aba06071f98f17edcbc573cbcf63)WindowExpression.php · AggregateExpression.phpGNU Affero GPL v3.0. Vedi LICENSE. Solo lab su loopback. Nessuna garanzia.