
CakePHP 5.4.2 - 높음 - 원격 - SQLi - WindowExpression 범위/프레임 오프셋이 SQL에 삽입됨
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cakephp-window-sqli
분석 문서: https://abraxaslabs.tech/research/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) |
| 인증 | 쿼리/폼 데이터를 ->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.
cakephp/cakephp 5.4.2를 2026년 FunctionsBuilder SQLi GHSA 이후로 고정했습니다. 해당 태그에서 열 번의 헌트를 진행했습니다. 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);
}
$binder는 오프셋이 이미 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
숫자 오프셋을 바인딩하세요. 프레임 유형(RANGE / ROWS / GROUPS)과 방향(PRECEDING / FOLLOWING)을 허용 목록에 추가하세요. 간격 문자열의 경우 따옴표로 감싸거나 ExpressionInterface를 요구하세요. 정수가 아닌 range() 오프셋이 원시 SQL이 아닌 플레이스홀더 또는 따옴표로 감싼 리터럴이 되는지 확인하는 테스트를 추가하세요.
887b641de9f7aba06071f98f17edcbc573cbcf63)WindowExpression.php · AggregateExpression.phpGNU Affero GPL v3.0. LICENSE를 참조하세요. 루프백 실험실 전용입니다. 보증은 제공되지 않습니다.