
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는 ensureSimpleString()으로 FunctionsBuilder의 cast / extract / dateAdd를 닫았다. WindowExpression::range()와 frame()은 그 수정에 포함되지 않았다. rows()와 groups()는 ?int를 받는다. range()는 여전히 문자열을 받는다. buildOffsetSql은 offset, frame type, 그리고 PRECEDING / FOLLOWING을 OVER에 연결한다. offset이 이미 ExpressionInterface가 아닌 한 binder는 사용되지 않는다.
요청 데이터를 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 전용 |
앱이 전달하는 window bound를 설정할 수 있는 사용자(분석의 "최근 N일", 리포트 빌더)는 OVER 절을 추가 SQL로 바꾼다.
| 권한 | 메서드 / 싱크 | 종류 | 랩 입증 또는 추론 |
|---|---|---|---|
SQL 조각을 OVER (RANGE BETWEEN ...)에 끼워 넣기 | GET /?mode=range&start=를 AggregateExpression::range($start, $end) / WindowExpression::frame()으로 | 컴파일된 SQL의 무결성 | 랩 입증. 컴파일된 SQL은 빈 binder와 함께 RANGE BETWEEN 1) /* CAKEPHP-WINDOW-SQLI-WITNESS */ -- PRECEDING이다 |
| 앱의 DB 사용자로 행 읽기 또는 수정 | 동일 쿼리, 실행됨 | DB의 무결성 / 정찰 | splice로부터 추론. 이 랩은 컴파일 전용 |
| 같은 방식으로 frame type 또는 direction 변경 | 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.
2026년 FunctionsBuilder SQLi GHSA 이후 cakephp/cakephp 5.4.2를 핀했다. 그 태그에서 열 번의 사냥. CSRF / FormProtection, View LFI, BodyParser XXE, mass assignment, helper XSS, 그리고 trustProxy 없는 clientIp()는 이 기준에서 빈손으로 돌아왔다. WindowExpressionTest::testRange는 여전히 다음을 단언한다:
$w = (new WindowExpression())->range("'1 day'", "'10 days'");
buildOffsetSql은 문자열 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는 offset이 이미 ExpressionInterface가 아닌 한 사용되지 않는다.
이미 기록된 잘못된 방향: 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
숫자 offset을 바인딩하라. frame type(RANGE / ROWS / GROUPS)과 direction(PRECEDING / FOLLOWING)을 허용 목록으로 지정하라. interval 문자열의 경우, 인용하거나 ExpressionInterface를 요구하라. int가 아닌 range() offset이 원시 SQL이 아니라 placeholder 또는 인용된 리터럴이 되는지 테스트를 추가하라.
887b641de9f7aba06071f98f17edcbc573cbcf63)WindowExpression.php · AggregateExpression.phpGNU Affero GPL v3.0. LICENSE 참조. 루프백 랩 전용. 보증 없음.