Prevention of OS command injection attacks
import express from 'express';
import { exec } from 'child_process';
const app = express();
const port = 3000;
app.get('/runCommand', (req, res) => {
const userInput = req.query.command;
exec(`ls ${userInput}`, (error, stdout, stderr) => {
if (error) {
console.error(`Error executing command: ${error.message}`);
return res.status(500).send('Internal Server Error');
}
console.log(`Command output: ${stdout}`);
res.send(`Command output: ${stdout}`);
});
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
The vulnerability in the provided code is an OS Command Injection. This vulnerability occurs when user-controlled input is directly used to construct a command that is executed on the server without proper validation or sanitization.
In the vulnerable code, the application receives a user input through the query parameter
command
in the
/runCommand
route. This input is directly passed to the
exec
function from the
child_process
module without any validation or sanitization.
The
exec
function executes the command provided as a string in the system shell. In this case, the user input is directly interpolated into the command string without any checks. This means that an attacker can manipulate the
command
parameter to execute arbitrary commands on the server.
For example, if an attacker sends a request with the
command
parameter set to
; rm -rf /
, the server will execute the command
ls ; rm -rf /
, which will list the directory contents and then delete all files and directories on the server's root directory.
This vulnerability can have severe consequences as it allows an attacker to execute arbitrary commands on the server, potentially escalating privileges, accessing or modifying sensitive information, or even compromising the entire system.
To mitigate this vulnerability, it is recommended to validate and sanitize all user input before using it in commands. Additionally, it is advised to avoid using functions that execute commands with user-controlled inputs whenever possible.
import express from 'express';
import { exec } from 'child_process';
const app = express();
const port = 3000;
app.get('/runCommand', (req, res) => {
const userInput = req.query.command;
// Sanitize user input using a whitelist approach
const sanitizedInput = userInput.replace(/[^a-zA-Z0-9.-]/g, '');
// Use template literals to insert the sanitized user input into the command
const command = `ls ${sanitizedInput}`;
// Use child_process.execFile instead of exec for better security controls
exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`Error executing command: ${error.message}`);
return res.status(500).send('Internal Server Error');
}
console.log(`Command output: ${stdout}`);
res.send(`Command output: ${stdout}`);
});
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
The fixed code addresses the vulnerability by implementing several security measures:
1. The code uses the Express framework to create a web server.
2. It imports the
exec
function from the
child_process
module to execute commands.
3. The server listens on port 3000 for incoming requests.
In the
/runCommand
route handler, the following steps are taken:
1. The user input is obtained from the query parameter
command
using
req.query.command
.
2. To sanitize the user input, a whitelist approach is used. The
replace
method is called on the user input, using a regular expression
/[^a-zA-Z0-9.-]/g
to remove any characters that are not alphanumeric, dots, or dashes. This helps prevent any malicious commands from being injected.
3. The sanitized user input is then inserted into a command string using template literals. In this example, the command is
ls
followed by the sanitized input. This is just an example command; you should replace it with a command that fits your specific use case.
4. Instead of using the
exec
function, which can be prone to command injection vulnerabilities, the code uses
execFile
.
execFile
is a safer alternative that allows better security controls.
5. The
execFile
function is called with the command string, and a callback function is provided to handle the execution result.
6. If an error occurs during command execution, an error message is logged to the console, and a 500 Internal Server Error response is sent to the client.
7. If the command executes successfully, the output is logged to the console and sent back as a response to the client.
By implementing these security measures, the code mitigates the risk of OS command injection vulnerabilities by sanitizing user input and using a safer method for executing commands.