CakePHP 5.4.2, Http Client regex suffix cookie domain
CookieCollection matches domains as a regex suffix, not RFC 6265 domain-match. api.partner.com leaks to evilapi.partner.com. Set-Cookie Domain= is not checked against the response host. Labbed 5.4.2. No CVE yet.
- Name
- CakePHP 5.4.2, Http Client regex suffix cookie domain
- Type
- 0-day analysis
- CVE
- n/a
- CVE Risk
- high
- Disclosure Status
- public
- Vendor
- CakePHP
- Affected
- CakePHP 5.4.2 (887b641) Cake\\Http\\Client default cookie jar. Stock cakephp/app does not call a second host by itself. other.com does not match. Not a browser cookie bug.
- Published
- 12 Oct 2026
- Updated
- 12 Oct 2026
- Tags
- 0-day, cakephp, cookie, cwe-565, cwe-201, http-client
bank.com matches notbank.com
I am @abraxas_null. The proof of concept is on GitHub: abraxas/cakephp-cookie-suffix (loopback client). The lab stack is lab/: Dockerfile, docker-compose.yml, run.sh, poc.py, oracle.php. Authorized lab only. It talks to loopback.
This is CakePHP 5.4.2 (887b641de9f7aba06071f98f17edcbc573cbcf63). No CVE yet. CWE-565 / CWE-201. 8.2 High (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N). Remote cookie leak / plant. App uses Cake\Http\Client with the default jar across hosts.
RFC 6265 domain-match is exact equality or a dot-separated suffix. Cake uses preg_match('/' . preg_quote($domain) . '$/', $host). A cookie for api.partner.com is sent to evilapi.partner.com. A cookie for bank.com is sent to notbank.com. other.com does not match.
Cookie::createFromHeaderString() also copies Set-Cookie Domain= with no check against the response host. addFromResponse from attacker.test with Domain=victim.internal stores that cookie and later attaches it to victim.internal.
This is not a browser cookie bug. Stock cakephp/app does not call a second host by itself. An app that uses the default jar across OAuth, webhooks, a user-supplied URL, or a second API is the exposed case.
Why this leftover, on the same pin
I was already on 5.4.2 for the SmtpTransport pair-only strip. Http Client cookie matching is a regex suffix in the same tree. Ten hunts. Cookie jar is hunt ten.
I only wanted Critical or High. Local is in-scope. Medium stays on the floor unless it chains. Framework leftover: needs an app that keeps the jar across hosts.
Pinning 5.4.2
Same tag as the SMTP pack. 887b641. Git clone tag 5.4.2 in PHP 8.3. The oracle runs CookieCollection from the pin. No extra HTTP origin is required.
Intake is security@cakephp.org.
The skip list is half the hunt
Same skip list as the SMTP pack. GHSA-vjqc, GHSA-fxf7, GHSA-2qh5 (incomplete, different leftover), GHSA-wpvj, GHSA-qh8m, GHSA-6g8q, 5.4.1 RateLimit ipHeader, 5.4.2 filename=. CSRF, View LFI, XXE, mass assignment, helper XSS, ungated HTTP_CLIENT_IP already gated. I did not re-file any of that.
Browser cookie jars are not this bug. I wrote that down before I opened CookieCollection.
Ten surfaces, then the suffix
CSRF / FormProtection / _method override: patched. View element() path: patched. FunctionsBuilder SQLi: patched. Paginator XSS: patched. RateLimit default: patched. Multipart filename=: patched.
Http Client cookie matching:
$pattern = '/' . preg_quote($domain, '/') . '$/';
if (!preg_match($pattern, $host)) {
continue;
}addFromResponse passes the request host only as a default. A Domain= attribute from Set-Cookie overwrites it and is stored.
An app that uses Cake\Http\Client with the default jar across more than one host can leak a partner session cookie to a suffix-confused sibling, or plant a cookie onto an internal host after fetching an attacker URL.
What I almost filed instead
Treating this as a browser cookie bug. Browsers do RFC 6265. Cake does a regex suffix.
Claiming other.com also matches. It does not. That is the negative control.
Inflating a Cookie header oracle to account takeover without an app that uses the jar across hosts. Lab stops at the Cookie header. Session fixation on the next Client call is inferred.
Same-host send as a finding. Cookie domain api.partner.com to host api.partner.com still sends sid=SECRET. Expected, not a bug.
Public-suffix rejection as the whole fix. Needed, but the first bug is the missing dot.
The lab
Git clone tag 5.4.2 in PHP 8.3. Loopback oracle HTTP at 127.0.0.1:19420.
- Add
sid=SECRETdomainapi.partner.com.addToRequesthttps://evilapi.partner.com/->Cookie: sid=SECRET. - Control
https://other.com/-> no Cookie header. - Same host
https://api.partner.com/still sendssid=SECRET. - Extra suffix
bank.com->https://notbank.com/sendssid=SECRET. addFromResponsefromhttps://attacker.test/withSet-Cookie: sid=x; Domain=victim.internal; Path=/. Nexthttps://victim.internal/sendssid=x.
Replay:
git clone https://github.com/abraxas/cakephp-cookie-suffix
cd cakephp-cookie-suffix/lab
./run.shBind it to loopback. The loopback client is lab/run.sh with a banner on it.
SUCCESS cakephp-cookie-suffix suffix-leak=yes control-other=yes plant-domain=yes how=cakephp-5.4.2 CAKEPHP-COOKIE-SUFFIX-WITNESSWrong turns, in the order I made them
Browser RFC as Cake's behavior. It is not. The regex is the leftover.
other.com as a hit. Control is empty. Suffix, not substring in the middle.
Skeleton app. Framework leftover. Needs a second host.
A reverse shell. Theatre. The oracle is a Cookie header.
What an attacker can do
Needs an application that keeps Cake\Http\Client cookies across more than one host (OAuth, webhooks, a user-supplied URL, a second API).
| Privilege | Method / sink | Lab-proven or inferred |
|---|---|---|
| Leak a stored cookie to a suffix-confused sibling | CookieCollection::addToRequest for https://evilapi.partner.com/ after a cookie with domain api.partner.com |
Lab-proven. Request Cookie is sid=SECRET. RFC 6265 would not match |
| Same leak for a registrable suffix | Cookie domain bank.com, request host notbank.com |
Lab-proven. Request Cookie is sid=SECRET |
| Negative control | Request host other.com |
Lab-proven. No Cookie header |
| Plant a cookie for an internal host | addFromResponse of Set-Cookie: sid=x; Domain=victim.internal from https://attacker.test/ |
Lab-proven. Stored domain is victim.internal; next request to that host sends sid=x |
| Session fixation / stolen partner session on the next Client call | Planted or leaked sid used as an API session |
Inferred. Lab stops at the Cookie header oracle |
| Same-host send still works | Cookie domain api.partner.com to host api.partner.com |
Lab-proven. Expected, not a bug |
Closed on this leftover: other.com does not match. Browser cookie jars are not this bug. No shell. Not unauthenticated RCE on a skeleton app.
The fix
RFC 6265 domain-match (host === domain || str_ends_with($host, '.' . $domain)). Host-only cookies when Domain is omitted. Reject Domain that does not domain-match the request host. Reject public suffixes.
Until then, do not share a Cake\Http\Client cookie jar across hosts you do not already trust with each other's cookies. Per-host clients are a workaround, not a fix.
I am not going to print a Set-Cookie you can paste at someone else's webhook fetch. The missing dot is the useful part. If you own the app, run the proof of concept against loopback.
References
- Proof of concept: abraxas/cakephp-cookie-suffix · cakephp-cookie-suffix-Abraxas-Labs.py
- Lab:
lab/· Dockerfile · docker-compose.yml · run.sh · poc.py · oracle.php - @abraxas_null · github.com/abraxas · abraxaslabs.tech · abraxas.null@proton.me
- CWE-565 · CWE-201
- Tree: cakephp/cakephp 5.4.2 (
887b641de9f7aba06071f98f17edcbc573cbcf63) ·CookieCollection.php·Cookie.php - RFC 6265 domain-match
- Same pin: cakephp-smtp-crlf · cakephp-trusted-proxies