Skip to main content

Remote command execution

Need

Prevention of unauthorized remote command execution

Context

  • Usage of TypeScript for type-checking and compiling JavaScript code
  • Usage of Express for building web applications and APIs
  • Usage of child_process for executing external commands and processes

Description

Non compliant code

import express from 'express';
import { exec } from 'child_process';

const app = express();
const port = 3000;

app.get('/execute', (req, res) => {
const command = req.query.command;

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 executed successfully: ${stdout}`);
res.send(stdout);
});
});

app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});

The vulnerability in this code is remote command execution. The code takes a user-supplied input from the query parameter command and directly passes it to the exec function from the child_process module without any validation or sanitization.

This means that an attacker can manipulate the command parameter to execute arbitrary commands on the underlying operating system. They can potentially execute unauthorized code or commands, leading to unauthorized access, data leakage, or other malicious activities.

Since the code does not properly neutralize special characters or escape the command argument, an attacker could inject special characters or command separators to modify the intended command and execute arbitrary commands.

This vulnerability can be mitigated by following best practices:

  • Avoid using external processes whenever possible and use standard library calls instead.
  • Keep the data used to generate the command outside of external control to prevent manipulation.
  • Properly escape the arguments used to generate the command and avoid special characters to prevent command injection attacks.

Steps

  • Sanitize the input: Before using the command input from the request, validate and sanitize it to ensure it only contains allowed characters and does not include any special characters or commands.
  • Use parameterized queries: Instead of directly concatenating the command input into the exec function, use parameterized queries or prepared statements to safely pass the command input as a parameter.
  • Validate and restrict commands: Implement a whitelist of allowed commands to restrict the types of commands that can be executed.
  • Implement input validation: Validate the command input against a predefined set of allowed characters or patterns to prevent any malicious or unexpected commands from being executed.
  • Implement proper error handling: Instead of logging the error message and sending a generic error response, handle the error gracefully and provide a specific error message to the client without exposing sensitive information.
  • Implement access controls: Ensure that only authorized users or roles have access to the endpoint that executes commands.
  • Regularly update and patch dependencies: Keep all dependencies, including the express and child_process modules, up to date with the latest security patches to mitigate any known vulnerabilities.

Compliant code

import express from 'express';
import { exec } from 'child_process';

const app = express();
const port = 3000;

app.get('/execute', (req, res) => {
const command = req.query.command;

// 1. Sanitize the input
const sanitizedCommand = sanitizeInput(command);

// 2. Use parameterized queries
const commandArgs = ['sh', '-c', sanitizedCommand];

// 3. Validate and restrict commands
const allowedCommands = ['ls', 'echo']; // Add more allowed commands as needed
if (!allowedCommands.includes(sanitizedCommand)) {
return res.status(403).send('Forbidden');
}

// 4. Implement input validation
if (!isValidCommand(sanitizedCommand)) {
return res.status(400).send('Bad Request');
}

exec(commandArgs.join(' '), (error, stdout, stderr) => {
if (error) {
// 5. Implement proper error handling
console.error(`Error executing command: ${error.message}`);
return res.status(500).send('Internal Server Error');
}

console.log(`Command executed successfully: ${stdout}`);
res.send(stdout);
});
});

app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});

// Helper functions

function sanitizeInput(input: string): string {
// Implement input sanitization logic here
// e.g., remove special characters or escape them
return input;
}

function isValidCommand(command: string): boolean {
// Implement command validation logic here
// e.g., check against a predefined set of allowed characters or patterns
return true;
}

The fixed code addresses the vulnerability of remote command execution by implementing several security measures:

  1. Sanitizing the input: The code uses a helper function called sanitizeInput to sanitize the input command. This function can be implemented to remove or escape any special characters that could potentially modify the OS command.

  2. Using parameterized queries: The code constructs the command arguments as an array, with the sanitized command as one of the elements. This approach helps to prevent command injection by separating the command and its arguments.

  3. Validating and restricting commands: The code checks if the sanitized command is included in the list of allowed commands. If the command is not in the allowed list, the code returns a 403 Forbidden response. This ensures that only specific commands are executed and prevents unauthorized code execution.

  4. Implementing input validation: The code includes a function called isValidCommand to perform additional validation on the sanitized command. This function can be implemented to check against a predefined set of allowed characters or patterns. If the command fails the validation, the code returns a 400 Bad Request response.

  5. Proper error handling: The code handles errors that may occur during command execution. If an error occurs, it logs the error message and returns a 500 Internal Server Error response. This prevents sensitive information from being exposed and provides a more secure response to the client.

Overall, the fixed code takes precautions to sanitize and validate user input, restrict the execution of commands to a predefined set, and handle errors appropriately. These measures help mitigate the risk of remote command execution vulnerabilities.

References