OS Command Injection - TypeScript

OS Command Injection - TypeScript

Need

Prevention of OS command injection attacks

Context

  • Usage of Typescript for adding static typing to JavaScript
  • 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('/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.

Steps

  • Sanitize the user input by validating and sanitizing the `userInput` variable before using it in the command execution.
  • Use a whitelist approach to validate the user input. Only allow specific characters or patterns that are necessary for the command execution.
  • Avoid using user input directly in the command execution. Instead, use placeholders or template literals to insert the sanitized user input into the command.
  • Implement proper error handling for the command execution. Instead of logging the error message to the console, handle the error gracefully and return an appropriate response to the user.
  • Consider using a safer alternative to `child_process.exec`, such as `child_process.execFile` or `child_process.spawn`, which provide better security controls.
  • Regularly update the dependencies of the application to ensure that any security vulnerabilities in the dependencies are addressed.

Compliant code

        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.

References