
CakePHP 5.4.2 - High - Remote - SQLi - COLLATE collation is a raw SQL fragment
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cakephp-collate-sqli
Class: SQLi Reach: Remote
CakePHP 5.4.2 (887b641) - Cake Software Foundation
StringExpression::sql binds the string value and concatenates collation. IdentifierExpression does the same. There is no ensureSimpleString. Postgres quoteIdentifier is '"' . trim($collation, '"') . '"', so an embedded quote closes CAST early.
An app that forwards request/locale collation into new StringExpression($term, $collation) puts that string into COLLATE. The value stays bound. The collation does not.
| ID | no CVE yet |
| Class | SQLi |
| Reach | Remote (app that forwards collation/locale into StringExpression / IdentifierExpression) |
| 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 |
| Product | CakePHP (cakephp/database) |
| Affected | 5.4.2 (887b641de9f7aba06071f98f17edcbc573cbcf63) |
| Auth | application that forwards collation/locale into those constructors |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
Needs an application that passes user/locale collation into those constructors. Stock cakephp/app does not expose collation on a public form by itself.
| Privilege | Method / sink | Kind | Lab-proven or inferred |
|---|---|---|---|
Splice SQL after COLLATE | StringExpression::sql concatenates $this->collation | Integrity of compiled SQL | Lab-proven. Raw SQL contains the payload unbound |
| Same splice on identifiers | IdentifierExpression note COLLATE <payload> | Integrity of compiled SQL | Lab-proven. id_raw_sql contains the payload |
Break out of Postgres "..." wrap | quoteIdentifier = '"' . trim($collation, '"') . '"' | Integrity of compiled SQL | Lab-proven. Compiled SQL is COLLATE "C" AS text)) || (SELECT ... |
| Execute the breakout and read another table | CAST(... ) || (SELECT note FROM lab_secret ...) | Recon of DB | Lab-proven. Result xCAKEPHP-COLLATE-SQLI-WITNESS, no exec error |
| Modify rows / more SQL with the connection role | Same injection | Integrity of DB | Inferred. Lab is a SELECT oracle |
| Negative control | collation C | Closed | Lab-proven. Result x, no witness, compiled COLLATE "C" |
Closed on this leftover: bound string value (:c0 stays the literal x); a collation that would pass ensureSimpleString (^[a-zA-Z0-9]+$) is not this payload. Not unauthenticated RCE on a skeleton app. Not a shell.
Same pin leftovers: window SQLi, attachment CRLF, cache unserialize.
I pinned 5.4.2 after the FunctionsBuilder / filename= notes. Ten hunts. The remaining-SQLi pass is this one. StringExpression::sql:
$placeholder = $binder->placeholder('c');
$binder->bind($placeholder, $this->string, 'string');
return $placeholder . ' COLLATE ' . $this->collation;
Postgres quoteIdentifier wraps with ASCII quotes and trim($collation, '"'). An embedded " closes the identifier. -- eats the leftover closer Cake still appends.
Wrong turns already recorded: SQLite as the only driver (concat is real, the wrap-breakout is Postgres); treating the bound value as the injection; claiming ensureSimpleString already ran here (it did not); a reverse shell. Theatre.
python3 cakephp-collate-sqli-Abraxas-Labs.py
or
cd lab
./run.sh
Target only http://127.0.0.1:19450. cakephp/database 5.4.2 plus Postgres 16. Tiny app forwards ?collation= into StringExpression / IdentifierExpression and executes CAST(... AS text). Secret table holds the witness.
SUCCESS cakephp-collate-sqli pin=5.4.2 raw=concat pg-wrap=breakout exec=yes CAKEPHP-COLLATE-SQLI-WITNESS
ensureSimpleString() (or a collation allowlist) on the collation argument. Postgres must quote identifiers properly, not trim plus wrap.
887b641de9f7aba06071f98f17edcbc573cbcf63)StringExpression.php · IdentifierExpression.php · Postgres.phpensureSimpleString()GNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.