abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cakephp-window-sqli
类别: SQLi 可达性: 远程
CakePHP 5.4.2 (887b641) - Cake Software Foundation
CVE-2026-79752 通过 ensureSimpleString() 修复了 FunctionsBuilder 的 cast / extract / dateAdd。而 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) |
| 认证 | 将查询/表单数据转发进 ->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()。遗留问题在于文档化的窗口 API。不是骨架应用上的未认证 RCE。不是 shell。
同一版本的其他遗留问题:collate SQLi、attachment CRLF、cache unserialize。
在 2026 年 FunctionsBuilder SQLi GHSA 之后,我锁定了 cakephp/cakephp 5.4.2。在该标签上进行了十次搜寻。CSRF / FormProtection、View LFI、BodyParser XXE、批量赋值、helper XSS,以及未设置 trustProxy 的 clientIp(),在此标准下均无发现。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);
}
除非偏移量本身已经是 ExpressionInterface,否则 $binder 不会被使用。
已记录的弯路:将 rows() 视为漏洞点(它是类型化 int);声称原版 cakephp/app 在公开表单上暴露此问题;在 SQLite 中执行注入的 SQL 作为判定依据(POC-READY 仅编译);反弹 shell。都是表演。
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
绑定数值偏移量。对帧类型(RANGE / ROWS / GROUPS)和方向(PRECEDING / FOLLOWING)使用允许列表。对于间隔字符串,对其加引号或要求使用 ExpressionInterface。添加测试,确保非 int 的 range() 偏移量变为占位符或带引号的字面量,而非原始 SQL。
887b641de9f7aba06071f98f17edcbc573cbcf63)WindowExpression.php · AggregateExpression.phpGNU Affero GPL v3.0。参见 LICENSE。仅限回环实验环境。无担保。