Local File Inclusion (LFI) is a web vulnerability where an application builds a file path from user input and loads a file the developer never meant to expose. One query parameter can return database credentials, application source code, or system configuration. The weakness maps to CWE-98 for unsafe inclusion and CWE-22 for path traversal, and path traversal still ranks sixth in the MITRE CWE Top 25, with ten entries in the CISA catalogue of actively exploited flaws. This guide covers how LFI works, how it differs from related attacks, how it escalates into code execution, and how to prevent and detect it. The first surprise is how ordinary the vulnerable code usually looks.
Key Takeaways
- LFI occurs when untrusted input reaches a file load operation such as include, require, a template resolver, or a file download handler.
- The pattern maps to CWE-98 (unsafe include) and CWE-22 (path traversal), which MITRE scored at 8.99 and placed sixth in its latest Top 25.
- Read access alone is severe, because configuration files, environment files, and source code normally carry live credentials.
- LFI becomes remote code execution when an attacker can influence the contents of a file the interpreter will parse, such as a log, session store, or upload directory.
- Blocking traversal strings is not a fix. Encoding variants, absolute paths, and second-order inputs defeat pattern matching.
- The dependable control is an allow-list that maps an identifier to a fixed file, supported by path canonicalisation and least privilege.
- Edge filtering and rate limiting cut attack volume and buy time for patching, but the code fix remains the real remediation.
What Local File Inclusion Means for Your Application
Local File Inclusion means the application lets a user decide which file it opens. The code takes a value from a query string, form field, cookie, or header, joins it to a directory, and hands the result to a function that reads or executes the file. When that value is not restricted to a known set, the file system happily returns whatever the path resolves to.
Think of an application that concatenates input into a path as a librarian who fetches any call number a visitor shouts, including the numbers behind the staff door. The librarian is not malicious. The librarian was simply never told which shelves are off limits.
MITRE documents the canonical shape of the flaw in CWE-98: a script reads a module name from a request, appends a fixed filename, and passes the whole string to an include statement. Nothing validates the first half of that path. LFI is the inclusion-shaped relative of a directory traversal attack, where traversal reads a file and inclusion loads it into the running application. The difference sounds academic until the loaded file contains code.
“attackers can escape outside of the restricted location to access files”

The scale is not theoretical. MITRE built its latest Top 25 from 39,080 CVE records, scored path traversal at 8.99, and counted ten CWE-22 entries in the CISA Known Exploited Vulnerabilities catalogue. Those are flaws with confirmed real-world exploitation, not laboratory findings. So what does the attack look like once someone starts probing?
How an LFI Attack Works
An LFI attack runs in four stages: the application builds a path from input, the attacker steers that path upward or sideways, the file system resolves a location outside the intended directory, and the contents come back in a response. No exotic tooling is required for the first three stages.
- Path construction. A parameter such as a page name, template name, language code, or report id is joined to a base directory.
- Traversal or substitution. The attacker supplies relative segments, an absolute path, or an encoded variant so the resolved location changes.
- Resolution outside the root. The operating system resolves the path literally, since it has no concept of an application boundary.
- Disclosure. File contents appear in the response body, in an error message, in a rendered template, or in a generated download.
Like any other injection attack, the root cause is data crossing into a control position. Here the control position is a file path rather than a database query or a shell command, and the interpreter is the file system.
Server software gets this wrong too. Apache HTTP Server 2.4.49 shipped a change to path normalisation that allowed crafted requests to map URLs outside the configured directories, tracked as CVE-2021-41773. The Apache Software Foundation confirmed exploitation in the wild, and where CGI was enabled the file read became command execution. The follow-up release 2.4.50 turned out to fix it incompletely, which produced CVE-2021-42013 and a second emergency release, 2.4.51.
The same path problem also carries three different labels, and the distinctions matter more than the vocabulary suggests.
LFI vs RFI vs Directory Traversal: What Actually Differs
The three names describe the same root cause with different reach. Directory traversal reads a file. Local file inclusion loads a local file into the application, which sometimes means executing it. Remote file inclusion loads a file from a location the attacker controls.

| Aspect | Directory traversal | Local file inclusion (LFI) | Remote file inclusion (RFI) |
|---|---|---|---|
| File source | Local file system | Local file system | Attacker controlled URL |
| Typical result | Sensitive file disclosure | Disclosure, sometimes execution | Attacker code runs on the host |
| Weakness ID | CWE-22 | CWE-98 with CWE-22 | CWE-98 |
| Usual entry point | Download and static file handlers | Include, require, template and view loaders | Include statements with URL fetching enabled |
| Prevalence today | Very common | Common | Rare in default configurations |
One configuration change explains why LFI dominates. PHP ships with allow_url_include disabled by default, so the classic remote file inclusion payload fails on a stock installation while local inclusion keeps working. Attackers adapted. They stopped trying to pull code from outside and started looking for code already sitting on the box.
Which files are worth pulling? The answer is more predictable than most teams expect.
Which Files Attackers Read First in an LFI Attack
Attackers go for credentials, then code, then anything that reveals the environment. The order rarely changes: highest payoff, lowest effort, right at the start.
- Environment and configuration files. Database passwords, API keys, mail credentials, and signing secrets sit together in one predictable place.
- Application source code. Reading the source turns blind testing into targeted testing and often reveals undocumented endpoints.
- Web and application logs. Logs hold session identifiers, internal hostnames, and the parameters other users submitted.
- System and container files. User lists, mount points, process environment data, and cloud metadata paths map the surrounding infrastructure.
- Session and upload stores. These directories matter twice, first for the data inside them and second because the attacker can often influence what goes in.
You might be thinking that a read-only flaw is a medium severity finding at worst. Yes, the primitive is read-only. But a single environment file usually contains the credentials for a database, an object store, and a payment provider, so the blast radius stops being about files within minutes.
LFI also travels with broken access control, since the code path that skips a boundary check on a filename tends to skip an authorisation check on the surrounding action. Fixing one and ignoring the other leaves the same door half open.
The read itself is only the opening move. The escalation is where incidents turn expensive.
How Local File Inclusion Turns Into Remote Code Execution
LFI becomes code execution when the attacker can influence the contents of a file the interpreter will parse. The inclusion mechanism does not care where the text came from. It only cares that the file is readable and that the runtime treats it as code.

“used to upgrade the attack from LFI to Remote Code Execution”
- Log poisoning. Attacker-supplied text lands in a log file, and the log file is later included.
- Uploaded file inclusion. An upload that passed a weak content check is loaded through the vulnerable parameter.
- Session file inclusion. Session data is written to disk, so any value the attacker controls in a session can be reached.
- Interpreter stream handlers. Language level wrappers turn a file read into a source disclosure or a parsed payload.
The Apache case shows the pattern at the infrastructure layer: the same request that returned a file became a command when CGI was enabled for the aliased path. Application layer escalations follow the same logic with different plumbing, and the common ingredient is a writable location the runtime is willing to parse.
None of this is limited to one language, which is where plenty of teams get comfortable too early.
Where LFI Vulnerabilities Hide Outside PHP
File inclusion is a design mistake, not a language feature, so it appears anywhere code turns user input into a path. OWASP notes the same pattern in JSP and ASP applications, and modern stacks add their own variations.
- Node services that join a request value into a path helper and stream the result back to the client.
- Python endpoints that pass a user-supplied filename into a file open call or a send-file helper.
- Template and view resolvers that accept a template name from a request parameter.
- Report, invoice, and PDF generators that resolve assets or partials by name.
- Localisation loaders that pick a translation file from a language code in the URL.
- Archive extraction routines that trust entry names, and cloud functions that read a key from an event payload.
You might be thinking a modern framework blocks this automatically. Frameworks help when you use their routing and asset pipelines as intended, and they stop helping the moment a developer reaches for the raw file API to solve a deadline problem. That single helper function, written once and copied across services, is how the same Security Vulnerabilities resurface in codebases that were audited last quarter.
Knowing where it hides is half the job. Removing it for good takes a specific set of controls.
How to Prevent Local File Inclusion Vulnerabilities
The reliable fix is to stop accepting paths altogether. Accept an identifier, map it to a fixed filename on the server, and reject anything that is not in the map. MITRE recommends exactly this mapping approach for CWE-98, and it removes the vulnerability class rather than filtering its symptoms.
When a dynamic path is unavoidable, resolve the real path first and then confirm it still sits inside the permitted directory. Checking the string before resolution is the mistake that produced a second Apache release, because normalisation and decoding order decide what the file system actually sees.
| Control | What it stops | Where it belongs |
|---|---|---|
| Allow-list mapping | Every path a user could invent | Application code, at the request boundary |
| Canonical path check | Encoded and relative escapes that survive filtering | Application code, after resolution |
| Basename extraction | Directory components hidden inside a filename | Application code, on upload and download handlers |
| Least privilege file access | Reads of files the service never needs | Operating system, container, and service account |
| Generic error responses | Path disclosure that guides the next probe | Application and web server configuration |
| Edge filtering and throttling | Automated probing at volume | CDN or reverse proxy in front of origin |
Two configuration details deserve attention because they leak information for free. Stack traces that print absolute paths hand an attacker the directory layout, so route failures to Custom Error Pages that say nothing about the file system. Response headers that advertise the framework, interpreter version, and server build do the same job in fewer bytes, and tightening them through HTTP Header Configuration removes a reconnaissance shortcut.
Prevention closes the hole in code you control. Detection tells you who is looking for the code you have not fixed yet.
How to Detect and Block LFI Attempts in Production
Detection starts with decoding. Log the fully decoded path the application resolved, not only the raw request line, then alert when a decoded path leaves the expected directory or when a traversal pattern returns a success status.
| Signal | What it suggests | Response |
|---|---|---|
| Repeated traversal sequences from one source | Automated scanning for file inclusion | Throttle the source and capture the full request set |
| Successful responses to unusual file parameters | A working read primitive | Treat as an incident and rotate exposed credentials |
| Encoded and double encoded path characters | Filter evasion in progress | Block on the decoded value, not the raw string |
| Requests for well known system paths | Reconnaissance against the host | Alert, then confirm which parameters accept paths |
| Large response size on a small endpoint | File contents returned where output should be short | Compare against the endpoint baseline and investigate |
At the edge, an advanced web application firewall inspects requests before they reach origin and drops the obvious probes. Managed WAF rules cover the traversal patterns already known to scanners, and a maintained baseline such as the OWASP Core Rule Set gives that coverage a public, reviewable definition. Pair it with a rate-limiter so a single source cannot run thousands of path guesses in a minute.
Here is the part vendors rarely lead with. Signature matching buys time. It does not draw a boundary. The Apache incident is the clearest evidence available: the first patch decoded characters one at a time, so a double-encoded sequence walked straight through a check that looked correct. If a normalisation routine inside the web server itself missed the variant, an inspection rule written in a hurry will miss it too.
Good detection depends on good testing, and most LFI testing stops far too early.
The LFI Testing Mistakes That Hide Real Risk
Teams close LFI tickets as unexploitable more often than the evidence supports. The usual reason is a test plan that covers one payload shape, one input location, and one operating system.
- Testing one target file only. A hardened host may block the textbook file while leaving application configuration wide open.
- Ignoring the platform. Windows path separators and drive letters behave differently from Unix paths, and one filter rarely handles both.
- Checking only query strings. POST bodies, JSON fields, cookies, and custom headers reach the same handlers.
- Reading a 404 as safety. Blind inclusion shows up in response timing, error wording, and subtle length differences rather than file contents.
- Skipping second-order inputs. A filename stored during registration and used weeks later in a report is still attacker-controlled.
- Stopping at disclosure. Without checking writable directories, the report understates severity and the fix gets deprioritised.
Severity ratings drive remediation queues, so an understated finding is a finding that waits. Rating an LFI as medium because it only reads files is the call worth pushing back on hardest in a review, since the rating should reflect what the primitive reaches, credentials included.
Final Thought on Local File Inclusion
Local File Inclusion survives because it is a small mistake with a large reach. One parameter joined to a directory, one missing check, and an application that behaved correctly for years starts handing out its own configuration. The weakness has been documented for decades and still earns a place among the most dangerous software weaknesses tracked by MITRE.
Treat file paths the way you already treat database queries. Never let a request decide the destination, map identifiers to fixed resources, resolve and verify before you read, and give the service account only the access it needs. Layer edge filtering and detection on top so an unpatched service is noisy rather than silent.
Start with a search across your codebase for file operations that take request data, and fix the ones that build paths through concatenation. That single pass usually finds more than a scanner will, and it turns an open-ended risk into a short list of changes.
Common Questions About Local File Inclusion
Can an LFI vulnerability be exploited without authentication?
Often yes. If the vulnerable parameter sits on a public page, such as a language selector or a public template switch, no session is needed. Authenticated LFI is still serious, since any registered user, including one with a free trial account, becomes a potential attacker.
Does a web application firewall alone make an application safe from LFI?
No. Edge filtering reduces automated probing and buys time during patching, and it can be bypassed by encoding variants or unusual input locations. Use it as a control layer while the code fix removes the vulnerability, not as a replacement for the fix.
How do you confirm an LFI finding without damaging the system?
Work on a staging copy, choose a harmless target file that you know exists, and record the exact request and response. Confirm the read, document the affected parameter, then stop. Escalation testing belongs in an authorised engagement with an agreed scope and a rollback plan.
What should an incident response team do after a confirmed file read?
Assume every secret readable by the application process is compromised. Rotate database passwords, API keys, tokens, and signing keys, review access logs for the affected paths, check writable directories for planted files, and only then close the vulnerability ticket.