Introduction

Welcome to the final lesson of the "A03: Injection" course! In previous lessons, we've explored various injection vulnerabilities, such as SQL and XSS injections, and their impact on web application security. In this lesson, we'll focus on command injection, a critical vulnerability that occurs when user input is improperly handled in command execution, particularly in file processing.

Understanding and mitigating command injection is essential to prevent unauthorized command execution and potential system compromise. Let's dive in! 🚀

Understanding Command Injection

Command injection (also known as OS injection) is a type of security vulnerability that occurs when an application passes unsafe, user-supplied data to a system shell. This allows an attacker to execute arbitrary operating system (OS) commands on the server. Unlike other injection attacks that target specific components (like SQL injection targeting a database), command injection targets the underlying host operating system, making it one of the most dangerous vulnerabilities.

Developers often use system commands to leverage powerful and efficient utilities built into the OS, such as file manipulation tools (ls, wc, find), network utilities (curl, ping), or other custom binaries. The vulnerability arises when user input is concatenated into a command string that is then executed by a shell. The shell interprets special characters in the user input not as simple text, but as instructions to chain, modify, or execute new commands.

Suppose that in our pastebin application, we have an endpoint for getting file statistics (like word count) of uploaded files. This endpoint uses the system's wc command to count words in files. Without proper security measures, an attacker could manipulate the filename input to run commands far beyond the intended wc utility.

Now that we understand the basics of command injection and have context for our example, let's look at some vulnerable code to see how this attack works in practice.

The Vulnerable Code

Let's examine a code snippet that demonstrates a vulnerable endpoint. This example uses Python's subprocess module with FastAPI, a common way to interact with the system shell. The vulnerability is introduced when user input is directly formatted into a command string and executed with shell=True.

In this code, the file_stats function takes a filename from a POST request. The core of the vulnerability lies in these two lines:

  1. command = f"wc {filename}": The filename provided by the user is directly embedded into the command string. There is no sanitization or validation to check for malicious characters.
  2. subprocess.run(command, shell=True, ...): The shell=True argument is critical. It tells subprocess to pass the entire command string to the system's default shell for execution. The shell then parses this string, and if it contains command separators (like ), it will execute the commands sequentially.
Exploiting the Vulnerability

An attacker can exploit this vulnerability by crafting a filename that includes shell metacharacters to append new commands. The semicolon (;) is a common choice in Unix-like systems because it acts as a command separator, allowing multiple commands to be run in sequence on a single line.

Here are two examples demonstrating how an attacker could use curl to send malicious JSON payloads to our vulnerable endpoint:

This attack works because the shell doesn't distinguish between the developer's intended command and the attacker's injected command. It simply receives a string and executes it. An attacker could use this technique to execute more dangerous commands, such as:

  • Reading sensitive files: test.txt; cat /etc/passwd
  • Writing to files to create backdoors: test.txt; echo '<hacked>' > /var/www/html/index.html
  • Establishing a reverse shell for persistent access.

Now that we understand how the vulnerability can be exploited, let's look at a multi-layered approach to prevent these attacks.

Input Validation

The first step in preventing command injection is to perform basic validation on all user-supplied input. This is a fundamental security practice that acts as a first line of defense.

By checking that the filename is a non-empty string, we can filter out malformed requests (e.g., null values or incorrect data types). However, this check is not sufficient on its own. An attacker's payload ("test.txt; ls -la") is a valid, non-empty string. Therefore, we need stronger defenses to neutralize the actual threat.

Safe Path Construction

A common attack vector combined with command injection is directory traversal, where an attacker uses sequences like ../ to navigate the file system and access files outside the intended directory. To prevent this, we must construct a safe file path by isolating the filename from any path information.

Using Path(filename).name is a crucial step. It extracts only the filename component from the input string, effectively neutralizing any directory traversal attempts. For example, if an attacker provides ../../boot.ini, Path().name will return just boot.ini. We then join this sanitized base name with a trusted base directory (Path.cwd()). With our path now properly sanitized, we can move on to the most important step: implementing safe command execution.

Parameterized Command Execution

The most effective way to prevent command injection is to avoid using a shell to interpret commands. Instead, we should pass the command and its arguments as a list, where each element is treated as a single, literal argument. This is achieved by setting shell=False (which is the default and recommended setting) in subprocess.run.

Here is the secure implementation of our endpoint:

By using subprocess.run(['wc', str(safe_path)], shell=False), we are instructing the OS to execute the wc program and pass the value of safe_path as a single argument to it. If an attacker tries to inject a command like test.txt; ls -la, the safe_path variable will contain that entire string. The wc command will then try to find a file literally named , which will fail, and no secondary command will be executed. The shell's command parser is never invoked on the user input, completely mitigating the vulnerability.

Restricting Allowed File Extensions

As part of a defense-in-depth strategy, we can further harden our endpoint by restricting the types of files it is allowed to process. This reduces the application's attack surface by ensuring it only interacts with expected file types and prevents it from being used to execute uploaded scripts or other potentially dangerous files.

By whitelisting allowed extensions, you add another layer of validation. If an attacker manages to upload a shell script (e.g., backdoor.sh), this check would prevent the wc command from ever being run on it, even if other defenses were to fail.

Real-World Examples and Consequences

Command injection vulnerabilities have led to several significant security breaches. The most notable example is the 2014 Shellshock vulnerability (CVE-2014-6271), which affected millions of web servers running Bash. It allowed attackers to execute arbitrary commands through specially crafted HTTP headers, leading to widespread system compromises. Additionally, numerous Internet of Things (IoT) devices have been compromised through command injection vulnerabilities in their web interfaces, leading to the creation of massive botnets (like Mirai) used for DDoS attacks.

The consequences of these attacks can be severe, including data breaches, complete system compromise, financial losses, and irreparable damage to a company's reputation. These real-world examples emphasize the critical importance of implementing proper security measures to prevent command injection attacks.

Conclusion and Next Steps

In this lesson, we've explored command injection vulnerabilities in file processing, demonstrated how they can be exploited, and discussed a multi-layered strategy for prevention. By understanding and applying these concepts—input validation, safe path construction, parameterized execution, and defense-in-depth—you can enhance the security of your web applications and protect against unauthorized command execution.

As you move on to the practice exercises, remember to apply the techniques you've learned to reinforce your understanding. Keep exploring related topics like secure file handling and vulnerability detection tools to further deepen your knowledge. Happy coding! 🎉

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal