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.
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_DEBUGvalue actually in effect on each production host. It should befalse. Don’t trust the committed.env.example; check the real environment. - Confirm
APP_ENV=productionas well, and that your deployment process doesn’t override these. - Watch for the common ways debug ends up on in prod: a
.envcopied 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(orspatie/laravel-ignitionon 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-devso dev-only packages, including the error-page tooling, aren’t even present on the server.
Step 3 — Reduce the standing exposure
- Make
APP_DEBUG=falsea 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-devon 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
.phpfiles written into the application directory, especially anything resembling a web shell, and modifications tostorage/logsconsistent 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
.envcontents, includingAPP_KEYand 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
-
PHP-FPM CVE-2019-11043: an RCE that depended on a copy-pasted nginx config
CVE-2019-11043 is a remote code execution bug in PHP-FPM, but it only fires on a specific nginx configuration, one that circulated widely in tutorials and got copy-pasted into production everywhere. The bug is in the code; the exposure came from a config snippet.
-
They read one file off the VPN gateway and left with your whole Active Directory
CVE-2024-24919 is filed as 'information disclosure.' On a Check Point gateway that meant unauthenticated file read, which meant password hashes, which meant ntds.dit within hours. It was a zero-day for a month before disclosure, and patching it doesn't undo the theft.
-
Patching the NetScaler RCE doesn't tell you if a webshell is already on it
CVE-2023-3519 was an unauthenticated RCE on Citrix NetScaler used as a zero-day to drop webshells. Patching closes the hole; it doesn't remove an implant planted before you patched. With a black-box appliance, finding out is the hard part. Here's the IOC-hunt runbook.
One email, every weekday morning.
You're in. Check your inbox.