Skip to content
Back to Blog
·9 min read·guides

File Inclusion Attacks: LFI and RFI Explained for Devs

How LFI and RFI let attackers hijack include() and require() calls for disclosure or RCE, plus the allow-list defences Indian dev teams need to ship safely.

BR

Bachao.AI Research Team

Cybersecurity Research

Get Your Free VAPT Scan

What this means for your business

Indian SMBs without documented security controls face 3× higher breach costs (IBM Cost of a Data Breach 2024). This guide helps you close that gap.

Local File Inclusion (LFI) and Remote File Inclusion (RFI) happen when an application uses unsanitised user input to build a file path passed into include(), require(), or an equivalent dynamic-loading function. LFI lets an attacker pull in files already on the server — configuration files, source code, log files, or /proc entries — often escalating to remote code execution (RCE). RFI, a narrower and now rarer case (mainly older PHP with allow_url_include enabled), lets an attacker force the server to fetch and execute a file from a URL they control. Both bugs trace back to one root cause: trusting user-supplied input as part of a file path. For Indian dev teams shipping PHP, Node, Python, or Java apps, this is still one of the most common OWASP Top 10 (A03: Injection / A01: Broken Access Control) findings in real-world code review — and one of the cheapest to prevent.

How LFI Actually Works

Most LFI bugs start with an innocent-looking pattern: a page parameter that selects which file to load, like ?page=about mapping to include($_GET['page'] . '.php'). If the parameter is not restricted, an attacker overrides it with a path to a file the developer never intended to expose.

The classic escalation path looks like this:

  1. Disclosure — read files the app was never meant to serve: ../../../../etc/passwd, database config files, .env files, or source code that reveals other vulnerabilities.
  2. Path traversal../ sequences walk the directory tree upward past the intended base folder, bypassing a naive "look inside this one folder" assumption.
  3. Log poisoning — an attacker injects PHP code into a file the server already writes to and can read back, such as the web server access log or a session file, by sending a crafted User-Agent header, then includes that log file to execute the injected code.
  4. PHP wrapper abuse — PHP's php://filter, php://input, data://, and expect:// wrappers let an attacker read source as base64, inject raw code via POST body, or execute commands directly, turning a "read-only" disclosure bug into full RCE.
  5. /proc tricks — on Linux, /proc/self/environ or /proc/self/fd/* can expose environment variables (secrets, keys) or open file descriptors, sometimes enabling code execution when combined with an injectable environment variable like a malicious User-Agent.
graph TD A[User controls file path] --> B[App includes untrusted file] B --> C[Sensitive file disclosure] B --> D[Log or wrapper poisoning] D --> E[Remote code execution] C --> F[Remediate with allow list] E --> F style A fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style B fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style C fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style D fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style E fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style F fill:#1e3d2f,stroke:#10B981,color:#e2e8f0
🚨
DANGER
LFI combined with any file-write primitive — log poisoning, session file injection, an image upload endpoint, or a temp file — is a direct path to remote code execution. Treat "just a read bug" LFI findings as pre-RCE, not as low severity.

Why RFI Is Rarer But Still Worth Knowing

RFI requires the target language's include function to be able to fetch remote content over a URL rather than only reading local disk paths. In PHP this depends entirely on the allow_url_include setting being enabled — it has been disabled by default since PHP 5.2, which is why RFI has become uncommon in current environments even though the related allow_url_fopen setting (which governs remote reads via fopen()/file_get_contents(), not includes) is still on by default in a stock install. Where RFI still shows up is legacy PHP applications, misconfigured shared hosting, or older CMS plugins that had allow_url_include explicitly re-enabled and were never re-audited after a server migration. When RFI does work, it is often more severe than LFI because the attacker controls the entire payload rather than being limited to files already present on the server.

⚠️
WARNING
Never assume "we don't use PHP" means this class is irrelevant. Node's require() with a dynamic, user-influenced path, Python's importlib with attacker-controlled module names, and Java's classloader-based dynamic includes all have equivalent LFI-style risk when fed unsanitised input.

Local vs Remote File Inclusion at a Glance

AspectLFIRFI
File sourceAlready present on the serverFetched from an attacker-controlled URL
Typical preconditionUnsanitised path in include()/require()Above, plus allow_url_include enabled
Common outcomeDisclosure, path traversal, log/wrapper-based RCEDirect RCE via attacker's remote payload
Prevalence todayCommon, still found in code reviewRare on modern PHP defaults
Primary fixAllow-list of valid values, never pass raw input to includeDisable allow_url_include/allow_url_fopen, same allow-list

Know your vulnerabilities before attackers do

Run a free VAPT scan — takes 5 minutes, no signup required.

Book Your Free Scan

Real-World Signals: Why This Still Matters

#1Ranking of Broken Access Control, the OWASP Top 10 category covering unsafe file and path handling (OWASP Top 10 2021)
73%Indian organisations that are unaware whether they have ever been breached (DSCI India Cyber Threat Report 2025)

Code review teams that assess Indian SaaS and fintech codebases regularly find LFI-class bugs in file-download endpoints, template selectors, plugin loaders, and multi-tenant "theme" or "report" pickers — features built for legitimate flexibility that never had their input constrained. The pattern is rarely a single obvious include($_GET[...]) line; it is usually one indirection layer removed, inside a helper function that "just builds a path," making it easy to miss in a quick read and exactly the kind of finding a structured VAPT engagement is built to catch.

Catching File Inclusion Before It Ships

Static analysis alone under-catches this class of bug, because the vulnerable pattern is rarely a single obvious line — it usually lives inside a helper function that concatenates a base directory with a caller-supplied value several calls away from the eventual include() or require(). Effective detection combines three layers, and dropping any one of them leaves a gap:

    1. Manual code review with a specific checklist item. Flag every place a variable derived from request data — query string, header, cookie, uploaded filename, or a value pulled from a database that itself originated from user input — reaches a dynamic-loading function, even indirectly through a wrapper.
    2. Dynamic testing with traversal and wrapper payloads. Attempt ../ sequences with varying encodings, PHP stream wrappers such as php://filter/convert.base64-encode/resource=, and null-byte variants against every parameter that influences which file, template, or module gets loaded — not only the ones that look obviously file-related.
    3. Error-message review. A stack trace or a "failed to open stream" error that echoes back the attempted file path confirms the input reaches a filesystem call, even before a successful inclusion is achieved; this is often the first signal a tester gets, well before full exploitation.
None of these layers is reliable alone. Static analysis misses the indirect, helper-function cases; manual review is inconsistent across a large codebase under deadline pressure; and dynamic testing without source-level context can miss inclusion points that never surface a visible error. A structured VAPT engagement that pairs source review with active exploitation attempts is what actually closes the gap, which is why this vulnerability class keeps turning up in engagements even at teams that already run automated scanning as part of CI.

Defence: The Only Fixes That Actually Hold

The fix for this vulnerability class is not "sanitise better" — string-blacklisting ../ or null bytes has a long history of being bypassed with encoding tricks, double encoding, and wrapper prefixes. The fixes that actually hold are structural:

    1. Never pass user input into include()/require() (or their equivalents) at all. If the feature genuinely needs to select between a fixed set of files, map user input to an internal identifier first.
    2. Use an allow-list, not a deny-list. Maintain an explicit array of permitted values (['home', 'about', 'contact']) and reject anything not in it, rather than trying to strip dangerous characters.
    3. Disable allow_url_include and allow_url_fopen in PHP configuration unless a specific, reviewed feature requires remote fetches — this alone removes the RFI class entirely.
    4. Validate and canonicalise paths server-side — resolve the final absolute path and confirm it still sits inside the intended base directory before opening it, rejecting anything that resolves outside it.
    5. Run the web server process with least privilege and restrict open_basedir (PHP) or equivalent filesystem sandboxing so even a successful traversal cannot reach unrelated system files.
    6. Don't rely on file extensions or content-type checks as security controls — an attacker-controlled log or session file with a .log or .txt extension is still interpreted as code once included.
🛡️
SECURITY
Log poisoning and php://filter/php://input abuse are the two techniques manual code review most often misses, because they require reasoning about what an included file becomes after user-controlled data lands in it — not just where the include path comes from. Combine static code review with dynamic testing (a VAPT pass that actually attempts inclusion payloads) rather than relying on either alone.
pie title File Inclusion Attack Outcome Distribution "Sensitive file disclosure" : 40 "Path traversal to source or config" : 25 "Log or wrapper poisoning" : 20 "Remote code execution" : 15
🎯Key Takeaway
File inclusion vulnerabilities exist because a file path was built, even partially, from user input — the fix is never a smarter filter, it is removing that trust boundary entirely with an allow-list and disabling remote includes at the platform level.

Fitting This Into Your SDLC

File inclusion bugs are a textbook example of why secure code review needs to happen before a feature ships, not after a customer reports it. Static analysis catches the obvious include($_GET[...]) pattern; it routinely misses the indirect, helper-function version, and it never catches log-poisoning or wrapper-chaining because those require actually attempting the exploit chain. Indian dev teams building anything that dynamically loads templates, plugins, reports, or user-selectable content should treat this as a standing code-review checklist item, and validate it periodically with a proper penetration test rather than a one-time review. This class of check runs as part of Dhisattva AI Pvt Ltd's automated and manual VAPT methodology, and for regulated sectors needing a formal certificate, the deeper manual pass is delivered with a CERT-In empanelled partner.

Teams that want this checked against their own codebase can start with a free VAPT scan, and map any file-handling findings into a DPDP-aligned remediation plan via the DPDP compliance guidance. More secure-coding methodology is on the Bachao.AI blog.

Sources

Frequently Asked Questions

What is the difference between LFI and path traversal?
Path traversal is the technique (using ../ sequences to escape an intended directory); LFI is the vulnerability class that results when that traversed path is passed into an include()/require() function and executed, not just read. Path traversal can exist without LFI if the file is only read and returned, not included as code.
Can LFI lead to remote code execution without RFI being possible?
Yes. Log poisoning, PHP wrapper abuse (php://filter, php://input), and session file injection all achieve code execution using files already on the server, with no remote fetch required, which is why LFI alone is treated as a critical-severity finding.
Does disabling allow_url_include fully fix this vulnerability class?
It eliminates RFI, but LFI remains possible through local file disclosure, path traversal, and log poisoning. Both an allow-list on include paths and disabling remote URL includes are needed for full coverage.
Why do blacklists and string filtering fail to stop LFI?
Attackers bypass character blacklists with encoding variations, double URL-encoding, null-byte tricks, and wrapper prefixes that don't contain the blocked substring. An allow-list of permitted values is structurally immune to these bypasses because anything not on the list is rejected outright.
Is this vulnerability specific to PHP?
No. The underlying pattern — building a dynamic-load path or module reference from user input — applies to Node's require(), Python's dynamic imports, and Java classloaders, though PHP's include()/require() combined with stream wrappers makes the exploitation path unusually direct.
How should a dev team catch this before it ships?
Add "no user input in dynamic include/require paths" as a mandatory code-review checklist item, enforce allow-lists in code, disable remote includes at the platform config level, and validate the control with periodic penetration testing rather than static analysis alone.
BR

Bachao.AI Research Team

Cybersecurity Research

AI-powered security research and threat intelligence from the Bachao.AI team. Covering the latest vulnerabilities, CVEs, and cybersecurity developments affecting Indian businesses.

Get cybersecurity insights for Indian SMBs

Weekly vulnerability alerts, DPDP compliance tips, and security guides. No spam — unsubscribe anytime.

We respect your privacy. Your email is never shared.

Know your vulnerabilities before attackers do

Free automated scan — risk score in under 2 hours. No credit card required.

Get Your Free VAPT Scan
Find your vulnerabilitiesStart free scan →