MODRACXKENNETH D'SILVA

← Archive & Insights

Laravel Ignition RCE (CVE-2021-3129): Exploiting PHP Debug Pages in Production

CVSS 9.8. How leaving debug mode enabled in Laravel exposed the Ignition error handler to insecure deserialization and unauthenticated remote code execution via PHP phar wrappers.

By Kenneth D'SilvaReading Time: 24 min readCategory: Security & Compliance

1. The Danger of APP_DEBUG=true

Laravel is the most popular PHP framework for building modern web applications, bespoke ecommerce engines, and payment API integrations. By default, when developing locally, Laravel uses a beautiful error page handler called Ignition (developed by Spatie).

In 2021, CVE-2021-3129 (CVSS 9.8) demonstrated that if an application was deployed to production with APP_DEBUG=true in its .env file, unauthenticated remote attackers could achieve instant, full remote code execution on the hosting server.

2. CVE Metadata Overview

CVE ID CVSS Package Vulnerability Mechanism
CVE-2021-3129 9.8 (Critical) facade/ignition < 2.5.2 Insecure Deserialization via phar:// Stream Wrapper & Log Poisoning

3. Technical Root Cause: The "Execute Solution" Endpoint

Ignition includes a feature that offers automated solutions for common errors (such as generating a missing application key via php artisan key:generate). To execute these solutions, the frontend sends an unauthenticated POST request to /_ignition/execute-solution containing a solution class name and parameter dictionary.

The MakeViewVariableOptionalSolution class contained a method that accepted a file path parameter and attempted to clean it up using PHP's file_get_contents(). In PHP, passing a phar:// stream wrapper to filesystem functions automatically triggers deserialization of the Phar archive's metadata manifest.

The 4-Step Log Poisoning Exploit Chain:

  1. Clear Logs: The attacker clears the Laravel log file (storage/logs/laravel.log) using PHP stream filters (php://filter/write=convert.base64-decode/resource=...).
  2. Poison Logs: The attacker sends a crafted request containing a base64-encoded Monolog/PHPGGC serialized POP gadget chain, which Laravel writes into laravel.log.
  3. Convert Log to Phar: The attacker applies a combination of UTF-7 and base64 stream filters to decode the log file into a valid binary Phar archive in place.
  4. Trigger Deserialization: The attacker invokes file_get_contents('phar://storage/logs/laravel.log'), executing the gadget chain and triggering arbitrary shell commands:

POST /_ignition/execute-solution HTTP/1.1
Host: api.enterprise-store.com
Content-Type: application/json

{
  "solution": "Facade\Ignition\Solutions\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "test",
    "viewFile": "phar://storage/logs/laravel.log/test.txt"
  }
}

4. Detection and Remediation Blueprint


# Search Nginx/Apache logs for ignition exploitation attempts
grep "_ignition/execute-solution" /var/log/nginx/access.log

5. Mandatory Hardening Rules

  1. Disable Debug Mode in Production: In your .env file, strictly set:
    
    APP_ENV=production
    APP_DEBUG=false
    
  2. Upgrade Ignition Dependency: In composer.json, update facade/ignition to version 2.5.2+ (or migrate to spatie/laravel-ignition).
  3. Disable Insecure Stream Wrappers: Restrict PHP's phar.readonly settings and disable dangerous PHP functions (system, exec, passthru, shell_exec) in php.ini.

Suggested & Related Reading

Explore related engineering guides from Kenneth D'Silva: