CakePHP 5.4.2 - Élevé - À distance - Désérialisation - les moteurs de cache désérialisent des objets sans liste blanche de classes
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cakephp-cache-unserialize
Write-up : https://abraxaslabs.tech/research/cakephp-cache-unserialize
Classe : Injection d'objet Portée : Distante (écriture Redis/Memcached) / Locale (FileEngine)
CakePHP 5.4.2 (887b641) - Cake Software Foundation
La version 5.3.7 a ajouté allowedClasses pour Redis pour cette raison et a laissé la valeur par défaut à true (tout autoriser) pour des raisons de compatibilité ascendante. RedisEngine appelle toujours unserialize() nu lorsque cette valeur par défaut est en vigueur. FileEngine n'a aucune liste d'autorisation. MemcachedEngine conserve par défaut serialize à php.
Une personne capable de définir une clé cake_ sur Redis, ou d'écrire un fichier de cache, y place un objet sérialisé. Le prochain Cache::read (ou un chargement CacheSession) l'exécute dans le worker PHP.
| ID | pas encore de CVE |
| Classe | Injection d'objet |
| Portée | Distante lorsque Redis ou Memcached est accessible en écriture par l'attaquant. Locale pour FileEngine sur tmp/cache. |
| CWE | CWE-502 |
| CVSS | Élevé : 8.8 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H (nécessite l'écriture dans le stockage de cache). FileEngine est AV:L. |
| Produit | CakePHP |
| Affecté | 5.4.2 (887b641de9f7aba06071f98f17edcbc573cbcf63) |
| Auth | écriture dans le stockage de cache (Redis SET, Memcached set, ou un fichier de cache). Pas de HTTP non authentifié vers un Redis localhost verrouillé. |
| Licence | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 uniquement |
Placer un objet sérialisé dans le cache de l'application. La lecture suivante exécute du PHP dans le worker. Ce lab s'arrête à un fichier témoin. Il ne démontre ni chaîne de gadgets ni shell.
Prouvé en lab (intégrité)
cake_<key> puis Cache::read. Le préfixe par défaut est cake_. Le unserialize() de RedisEngine utilise unserialize($value) nu lorsque allowedClasses est à la valeur par défaut true. Un objet LabMarker placé et instancié en tant que LabMarker et __wakeup a écrit le fichier témoin avec l'UID PHP.{expiry}\n{serialize($obj)} sous le chemin de cache. FileEngine::get() appelle toujours unserialize($data) nu. Même marqueur, même témoin. Le nom de fichier est prefix plus la clé (cake_labfile).allowedClasses => false refuse les objets. La même charge utile Redis est revenue en __PHP_Incomplete_Class. __wakeup ne s'est pas exécuté. Le fichier témoin était absent.Déduit (non exercé ici)
CacheSession::read est le même sink lorsque la session a defaults => cache. Chaque chargement de session devient un unserialize d'octets de cache contrôlés par l'attaquant.serialize => php est la même classe de bug. Ce lab n'a pas démarré Memcached.Fermé / pas ce reliquat
cakephp/app standard qui n'expose pas Redis.allowedClasses => false (ou une liste restreinte) bloque déjà les objets lorsqu'un opérateur le définit. Ce n'est pas la valeur par défaut.Mêmes restes d'épinglage : window SQLi, collate SQLi, attachment CRLF.
J'ai épinglé cakephp/cakephp 5.4.2 après la vague 2026 FunctionsBuilder / jsonValue / Smtp CRLF / View element. RedisEngine documente l'option et la laisse toujours ouverte par défaut :
* - `allowedClasses` ... Defaults to `true` (allow all) for backwards compatibility.
...
'allowedClasses' => true,
Lire le chemin :
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 n'a pas d'option équivalente :
if ($data !== '' && !empty($this->_config['serialize'])) {
$data = unserialize($data);
Fausses pistes déjà consignées : traiter un Redis localhost verrouillé comme une RCE HTTP non authentifiée ; livrer une chaîne de gadgets ; prétendre que FileEngine est distant ; un reverse shell. Du théâtre. L'oracle est un marqueur __wakeup qui écrit un fichier flag.
python3 cakephp-cache-unserialize-Abraxas-Labs.py
ou
cd lab
./run.sh
Ciblez uniquement 127.0.0.1:19470. CakePHP 5.4.2 en direct depuis le tag 5.4.2 dans Docker. Redis 7 avec protected-mode désactivé afin que l'hôte puisse effectuer SET. PHP 8.3 avec ext-redis. Classe marqueur LabMarker avec __wakeup qui écrit /tmp/CAKEPHP-CACHE-UNSERIALIZE-WITNESS. Pas de 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
Définir par défaut allowedClasses de Redis à false, ou à une liste restreinte de classes que l'application stocke réellement. Ajouter la même option à FileEngine et l'honorer dans get(). Définir par défaut serialize de Memcached à json. Documenter que CacheSession hérite de la politique de désérialisation du moteur de cache.
887b641de9f7aba06071f98f17edcbc573cbcf63)RedisEngine.php · FileEngine.phpGNU Affero GPL v3.0. Voir LICENSE. Laboratoire en boucle locale uniquement. Aucune garantie.