CakePHP 5.4.2 - High - Remote - Unserialize - cache engines unserialize objects with no class allowlist
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cakephp-cache-unserialize
Class: Object injection Reach: Remote (Redis/Memcached write) / Local (FileEngine)
CakePHP 5.4.2 (887b641) - Cake Software Foundation
5.3.7 added Redis allowedClasses for this reason and left the default at true (allow all) for backwards compatibility. RedisEngine still calls bare unserialize() when that default holds. FileEngine has no allowlist at all. MemcachedEngine still defaults serialize to php.
Someone who can SET a cake_ key on Redis, or write a cache file, plants a serialized object. The next Cache::read (or a CacheSession load) runs it in the PHP worker.
| ID | no CVE yet |
| Class | Object injection |
| Reach | Remote when Redis or Memcached is writable from the attacker. Local for FileEngine on tmp/cache. |
| CWE | CWE-502 |
| CVSS | High: 8.8 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H (needs write to the cache store). FileEngine is AV:L. |
| Product | CakePHP |
| Affected | 5.4.2 (887b641de9f7aba06071f98f17edcbc573cbcf63) |
| Auth | write to the cache store (Redis SET, Memcached set, or a cache file). Not unauth HTTP to a locked-down localhost Redis. |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
Plant a serialized object in the app cache. The next read runs PHP in the worker. This lab stops at a witness file. It does not demonstrate a gadget chain or a shell.
Lab-proven (integrity)
cake_<key> then Cache::read. Default prefix is cake_. RedisEngine unserialize() uses bare unserialize($value) when allowedClasses is the default true. A planted LabMarker object instantiated as LabMarker and __wakeup wrote the witness file as the PHP UID.{expiry}\n{serialize($obj)} under the cache path. FileEngine::get() still calls bare unserialize($data). Same marker, same witness. Filename is prefix plus key (cake_labfile).allowedClasses => false refuses objects. Same Redis payload came back as __PHP_Incomplete_Class. __wakeup did not run. The witness file was absent.Inferred (not exercised here)
CacheSession::read is the same sink when session defaults => cache. Every session load becomes an unserialize of attacker-controlled cache bytes.serialize => php is the same class of bug. This lab did not start Memcached.Closed / not this leftover
cakephp/app that does not expose Redis.allowedClasses => false (or a tight list) already blocks objects when an operator sets it. It is not the default.Same pin leftovers: window SQLi, collate SQLi, attachment CRLF.
I pinned cakephp/cakephp 5.4.2 after the 2026 FunctionsBuilder / jsonValue / Smtp CRLF / View element wave. RedisEngine documents the option and still defaults it open:
* - `allowedClasses` ... Defaults to `true` (allow all) for backwards compatibility.
...
'allowedClasses' => true,
Read path:
protected function unserialize(string $value): mixed
{
if (preg_match('/^[-]?\d+$/', $value)) {
return (int)$value;
}
$allowedClasses = $this->getConfig('allowedClasses');
if ($allowedClasses !== true) {
return unserialize($value, ['allowed_classes' => $allowedClasses]);
}
return unserialize($value);
}
FileEngine has no equivalent option:
if ($data !== '' && !empty($this->_config['serialize'])) {
$data = unserialize($data);
Wrong turns already recorded: treating locked-down localhost Redis as unauth HTTP RCE; shipping a gadget chain; claiming FileEngine is remote; a reverse shell. Theatre. The oracle is a marker __wakeup that writes a flag file.
python3 cakephp-cache-unserialize-Abraxas-Labs.py
or
cd lab
./run.sh
Target only 127.0.0.1:19470. Live CakePHP 5.4.2 from tag 5.4.2 inside Docker. Redis 7 with protected-mode off so the host can SET. PHP 8.3 with ext-redis. Marker class LabMarker with __wakeup that writes /tmp/CAKEPHP-CACHE-UNSERIALIZE-WITNESS. No shell.
SUCCESS cakephp-cache-unserialize redis-default-wakeup=yes redis-denied-wakeup=no file-wakeup=yes pin=5.4.2 sha=887b641de9f7aba06071f98f17edcbc573cbcf63 how=live CAKEPHP-CACHE-UNSERIALIZE-WITNESS
Default Redis allowedClasses to false, or to a tight list of classes the app actually stores. Add the same option to FileEngine and honor it in get(). Default Memcached serialize to json. Document that CacheSession inherits the cache engine unserialize policy.
887b641de9f7aba06071f98f17edcbc573cbcf63)RedisEngine.php · FileEngine.phpGNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.