PatchDay Alert
Field Note · 4 min read · 807 words By runbook-desk

Laravel CVE-2021-3129: the RCE that only fires when debug mode is on in production

CVE-2021-3129 is unauthenticated remote code execution in Laravel's Ignition error page. It only works when APP_DEBUG is true, which should never be the case in production. Here's how to confirm debug mode is off everywhere, patch, and check whether you were hit.

Laravel CVE-2021-3129: the RCE that only fires when debug mode is on in production

CVE-2021-3129 is unauthenticated remote code execution in Ignition, the package that draws Laravel’s pretty error pages, CVSS 9.8. It’s reliable, it has public exploit code, and it’s on the KEV catalog (added September 18, 2023, with the ransomware flag) because cryptominers and worse have been using it for years. But it has one precondition that turns the whole thing into a configuration story: it only works when Laravel is running in debug mode, APP_DEBUG=true. In production, that should always be false. So this is two problems in one, an outdated package and a debug flag that escaped to production, and the runbook addresses both.

What the bug is

Ignition before 2.5.2, as bundled with Laravel before 8.4.2, makes insecure use of file_get_contents() and file_put_contents() in its error-page “solutions” feature. An unauthenticated attacker abuses that, via a PHAR-deserialization / log-poisoning style chain, to write and execute arbitrary code on the server. The Ignition error page is only exposed when debug mode is on, which is why APP_DEBUG=true is the gate. Ignition 2.5.2 fixed it by restricting the file operations and disallowing stream wrappers.

Worth saying plainly: debug mode in production is dangerous even without this CVE. Laravel’s debug error pages leak environment variables, including database credentials and application keys, stack traces, and configuration. The RCE is the worst outcome of a misconfiguration that’s a serious data-exposure problem on its own.

This is a runbook. Work both halves.

Step 1 — Confirm debug mode is off in every production environment

  • Check the APP_DEBUG value actually in effect on each production host. It should be false. Don’t trust the committed .env.example; check the real environment.
  • Confirm APP_ENV=production as well, and that your deployment process doesn’t override these.
  • Watch for the common ways debug ends up on in prod: a .env copied from staging, an environment variable set for a one-off debugging session and never reverted, a container image built with dev settings. If you run multiple environments, verify each one independently.

Step 2 — Patch Ignition and Laravel

  • Update the facade/ignition (or spatie/laravel-ignition on newer Laravel) package to a fixed version (2.5.2 or later), and update Laravel to a maintained release.
  • Better still, don’t ship Ignition to production at all. It’s a development dependency. Ensure your production install runs composer install --no-dev so dev-only packages, including the error-page tooling, aren’t even present on the server.

Step 3 — Reduce the standing exposure

  • Make APP_DEBUG=false a deployment gate. Add a check to your CI/CD or startup process that refuses to deploy or boot a production app with debug enabled. This is the durable fix; configuration drift is inevitable, so detect it automatically.
  • Keep dev dependencies out of production builds. --no-dev on install means a future Ignition-class bug can’t be exploited because the package isn’t there.
  • Don’t expose the app server directly. Standard web-app hygiene (a reverse proxy, WAF rules for known exploit patterns) adds a layer, though it’s no substitute for the config fix.

Step 4 — Check whether you were already exploited

If a production app ran with debug mode on and an unpatched Ignition, treat it as potentially compromised. Public exploits have been around since early 2021.

  • Look for unexpected .php files written into the application directory, especially anything resembling a web shell, and modifications to storage/logs consistent with log-poisoning.
  • Review web logs for requests to the Ignition endpoints (_ignition/execute-solution, _ignition/health-check) from unexpected sources.
  • Check for the PHP-FPM/web process spawning shells, outbound connections, and cryptominer processes, which were a common payload.
  • Rotate secrets regardless. If debug mode was on, assume the .env contents, including APP_KEY and database credentials, were exposed. Rotate them, and rebuild rather than clean if you find a web shell.

The general lesson

Carry this past Laravel: development and debugging features are not meant to survive the trip to production, and when they do, they’re frequently the most dangerous thing on the server. Debug consoles, verbose error pages, default admin panels, sample apps, and dev-only dependencies all share the property that they’re convenient in development and catastrophic when internet-facing. The robust pattern is to make “production” a hardened configuration enforced by your pipeline, not a state you trust humans to remember to set. CVE-2021-3129 only hurt the deployments where a debug flag leaked into production, so the fix that matters most isn’t the package update, it’s making that leak impossible to ship. We flag these the day they land, but the durable defense is the deployment check that won’t let debug mode reach production in the first place.

Sources

Share

Related field notes

One email, every weekday morning.

You're in. Check your inbox.

Get the digest

Free. Weekday mornings. Plain English CVE triage.

Check your inbox to confirm.