September 24, 2026
How to Harden PHP So a File-Inclusion Bug Cannot Reach Code Execution
A file-inclusion bug only becomes remote code execution when the runtime hands it a gadget and a writable place to execute from. That means…
By Anthony Bahn
4 min read
A file-inclusion bug only becomes remote code execution when the runtime hands it a gadget and a writable place to execute from. That means the PHP configuration around your application, not just the application code, determines whether a flaw like the WordPress core bug CVE-2026–87902 ends in a shell or an error page. This guide walks through the runtime controls that break the LFI-to-RCE chain, ordered from highest leverage to supporting measures, so a single missed patch does not become a compromise.
Start by Confining the Filesystem
The single most effective control is to stop PHP from reaching files outside the application at all. The [open_basedir](https://www.anthonybahn.com/glossary/openbasedir/) A PHP configuration directive that confines all filesystem access, including include and require, to a named directory tree. Set to the application's own paths, it blocks a file-inclusion bug from reaching gadgets or writable directories elsewhere on the server, breaking the escalation to code execution. directive limits every filesystem operation, including include and require, to a named directory tree. When a script tries to include a file outside that tree, PHP refuses. Set open_basedir to the document root plus any genuinely required paths such as an upload directory and the session path, and nothing else. With it in place, a traversal payload that tries to reach a PEAR helper or a temporary directory simply fails, because those paths are outside the allowed tree.
Confinement is powerful because it does not depend on knowing which gadget an attacker will choose. It denies the whole category of "include a file somewhere else on the server," which is the escalation step described in How a Local File Inclusion Bug Becomes Remote Code Execution Without an Upload. Test it carefully: a too-narrow open_basedir breaks legitimate includes, so enumerate the paths your application actually touches before enforcing it.
Remove the Known Gadgets
The exploitation of CVE-2026–87902 in the wild relied on PEAR's command helper as its gadget, so the next step is to remove or neutralize the files attackers reach for.
- Disable
[register_argc_argv](https://www.anthonybahn.com/glossary/registerargcargv/)A PHP configuration directive, enabled by default, that populates the argument variables from request data. Web applications rarely need it, but leaving it on lets gadgets such as PEAR's command helper read attacker-controlled arguments from the query string, a key link in local-file-inclusion attack chains.. This directive defaults to enabled and is what lets the PEAR command helper read attacker input from the query string. Turning it off breaks that specific chain. It is a per-directory setting, so you can disable it in the web context without affecting command-line scripts that legitimately need argument parsing. - Remove PEAR from web-facing hosts. If the application does not use PEAR at runtime, the helper file has no business being readable by the web-server account. Uninstall it, or move it outside any path PHP can include.
- Keep
allow_url_includedisabled. It defaults to off and should stay off. Enabling it would let inclusion bugs pull code from a remote URL, converting a local-file limitation into a far worse remote-file one.
Each of these removes a rung from the ladder an attacker climbs. None is a substitute for confinement, but together they mean that even inside an over-broad filesystem scope there is no obvious gadget to abuse.
Constrain What Executed Code Can Do
Assume, for planning purposes, that an attacker does reach execution. The controls in this layer limit what that execution achieves, the same defense-in-depth logic that applies to any post-exploitation containment.
- Run PHP as an unprivileged, per-site account. A separate low-privilege user per application means a foothold in one site cannot read or write another's files. This directly limits blast radiusThe full set of systems, data, and access an attacker can reach after compromising a given asset. Ranking assets by blast radius rather than by how exposed they are pushes high-reach systems like a firewall management console to the top of the priority list. on shared hosting.
- Make execution directories non-writable and writable directories non-executable. The LFI-to-RCE chain needs one directory that PHP can both write to and include from. Break that overlap: mount temporary and upload directories so PHP cannot include from them, and keep code directories read-only to the web account.
- Trim
disable_functions. Disabling process-spawning functions the application does not use raises the cost of turning a foothold into command execution. Verify against your real code first, because an overzealous list breaks legitimate features.
Verify, Then Keep It Verified
Hardening that no one checks drifts. Bake these settings into the image or configuration management that builds your hosts, so a rebuilt server is hardened by default rather than by a manual afterthought. After any platform upgrade, re-confirm the directives, because defaults change between PHP versions and a major upgrade can quietly re-enable something you turned off.
Build a short, repeatable check: for each web-facing host, record whether open_basedir is set and to what, whether register_argc_argv is enabled, whether PEAR is present and readable, and whether any single directory is both writable and includable by the PHP process. That inventory is what lets you answer, on the day an advisory lands, which sites are actually exploitable rather than merely running the affected version.
How This Connects to Patching
Runtime hardening does not replace patching; it buys you time and reduces the stakes. When CVE-2026–87902 went from patch to public exploit Code or technique that takes advantage of a vulnerability to cause unintended behavior, such as gaining unauthorized access. tooling in a single day, hardened hosts still needed the fix, but their operators were not racing a working exploit against an unhardened target. The two disciplines work together: harden so that a missed patch is survivable, and patch fast so you are not leaning on hardening alone. The process side of moving quickly is covered in Why Auto-Updates Are Not a Substitute for a Patch Response Plan, and the mechanics of how the inclusion bug escalates in the first place are in How a Local File Inclusion Bug Becomes Remote Code Execution Without an Upload.Read the full guide on the site | More from Anthony Bahn | Daily cyber news
Originally published at https://www.anthonybahn.com.
Read the full guide on the site