
CakePHP 5.4.2 - 高 - リモート - SQLi - WindowExpression の range/frame オフセットが 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 | High: 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 となる |
| アプリの DB ユーザーとして行を読み取りまたは変更 | 同じクエリを実行 | DB の完全性 / 偵察 | 注入からの推測。このラボはコンパイルのみ |
| 同じ方法でフレーム型や方向を変更 | 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 ではない。シェルではない。
同じピンの残存項目: collate SQLi、attachment CRLF、cache unserialize。
2026 年の FunctionsBuilder SQLi GHSA の後、cakephp/cakephp 5.4.2 をピン留めした。そのタグで 10 件の調査を行った。CSRF / FormProtection、View LFI、BodyParser XXE、マスアサインメント、ヘルパー 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 はコンパイルのみ)、リバースシェル。茶番である。
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 を参照。ループバックラボのみ。無保証。