
CakePHP 5.4.2 — высокий уровень — удалённо — SQLi — смещения диапазона/фрейма WindowExpression вставляются в SQL
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cakephp-window-sqli
Класс: SQLi Доступ: Удалённый
CakePHP 5.4.2 (887b641) - Cake Software Foundation
CVE-2026-79752 закрыл FunctionsBuilder cast / extract / dateAdd с помощью ensureSimpleString(). WindowExpression::range() и frame() не вошли в это исправление. rows() и groups() принимают ?int. range() по-прежнему принимает строку. buildOffsetSql конкатенирует смещение, тип фрейма и PRECEDING / FOLLOWING в OVER. Биндер не используется, если смещение уже не является ExpressionInterface.
Приложение, которое передаёт данные запроса в func()->count('*')->over()->range($start, $end), помещает строку вызывающего в RANGE BETWEEN без привязки и без кавычек.
| ID | CVE пока нет |
| Класс | SQLi |
| Доступ | Удалённый (приложение, передающее данные запроса в range() / frame()) |
| CWE | CWE-89 |
| CVSS | Высокий: 8.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
| Продукт | CakePHP (cakephp/database) |
| Затронуто | 5.4.2 (887b641de9f7aba06071f98f17edcbc573cbcf63) |
| Аутентификация | приложение, передающее данные query/form в ->over()->range($start, $end) / frame() |
| Лицензия | GNU Affero GPL v3.0 |
| Лаборатория | только 127.0.0.1 |
Пользователь, который может задать границу окна, передаваемую приложением (аналитика «последние N дней», конструктор отчётов), превращает клаузу OVER в дополнительный SQL.
| Привилегия | Метод / точка входа | Тип | Доказано в лаборатории или выведено |
|---|---|---|---|
Внедрить SQL-фрагмент в OVER (RANGE BETWEEN ...) | GET /?mode=range&start= в AggregateExpression::range($start, $end) / WindowExpression::frame() | Целостность скомпилированного SQL | Доказано в лаборатории. Скомпилированный SQL: RANGE BETWEEN 1) /* CAKEPHP-WINDOW-SQLI-WITNESS */ -- PRECEDING с пустым биндером |
| Читать или изменять строки от имени пользователя БД приложения | Тот же запрос, выполненный | Целостность / разведка БД | Выведено из внедрения. Эта лаборатория только компилирует |
| Изменить тип или направление фрейма тем же способом | frame($type, $offset, $direction, ...) также конкатенирует эти строки | Целостность скомпилированного SQL | Выведено из sql() / buildOffsetSql(). В лаборатории использовался range() |
Сломать rows() / groups() тем же способом | Типизированы как ?int | Закрыто | Доказано в лаборатории как закрытое. rows(3) даёт ROWS BETWEEN 3 PRECEDING, без кавычек, без привязки |
Закрыто на этом коммите: FunctionsBuilder cast / extract / dateAdd / jsonValue (CVE-2026-79752 / CVE-2026-77635); Query::limit / offset (типизированы как int). Стандартный cakephp/app не вызывает range() с данными из строки запроса. Остаётся документированный window API. Это не неаутентифицированный RCE на скелетном приложении. Это не шелл.
Остатки на том же коммите: collate SQLi, attachment CRLF, cache unserialize.
Я зафиксировал cakephp/cakephp 5.4.2 после SQLi GHSA в FunctionsBuilder 2026 года. Десять поисков на этом теге. CSRF / FormProtection, View LFI, BodyParser XXE, mass assignment, helper XSS и clientIp() без trustProxy в этой планке ничего не дали. WindowExpressionTest::testRange по-прежнему утверждает:
$w = (new WindowExpression())->range("'1 day'", "'10 days'");
buildOffsetSql никогда не привязывает строковое смещение:
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 не используется, если смещение уже не является ExpressionInterface.
Уже зафиксированные ошибочные пути: считать rows() дырой (он типизирован как int); утверждать, что стандартный cakephp/app раскрывает это через публичную форму; выполнять внедрённый SQL в SQLite как оракул (POC-READY только компилирует); обратный шелл. Театр.
python3 cakephp-window-sqli-Abraxas-Labs.py
или
cd lab
./run.sh
Цель только http://127.0.0.1:19440. Крошечное PHP-приложение, cakephp/database 5.4.2, SQLite :memory:. GET /?mode=range&start= передаётся как строка в range(). GET /?mode=rows&n=3 — это 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:
SELECT (COUNT(*) OVER (RANGE BETWEEN 1) /* CAKEPHP-WINDOW-SQLI-WITNESS */ -- PRECEDING AND CURRENT ROW)) AS n FROM sqlite_master
Контроль:
SELECT (COUNT(*) OVER (ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)) AS n FROM sqlite_master
Привязывайте числовые смещения. Используйте allowlist для типа фрейма (RANGE / ROWS / GROUPS) и направления (PRECEDING / FOLLOWING). Для интервальных строк заключайте их в кавычки или требуйте ExpressionInterface. Добавьте тест, что не-int смещение range() становится плейсхолдером или строковым литералом в кавычках, а не сырым SQL.
887b641de9f7aba06071f98f17edcbc573cbcf63)WindowExpression.php · AggregateExpression.phpGNU Affero GPL v3.0. См. LICENSE. Только loopback-лаборатория. Без гарантий.