Remote command execution - Scala

Remote command execution - Scala

Need

Prevention of unauthorized execution of commands on the system

Context

  • Usage of Scala for building scalable and functional applications
  • Usage of scala.sys.process for executing system commands in Scala

Description

Non compliant code

        import sys.process._

def runCommand(userInput: String): String = {
  val command = s"ls -l $userInput"
  command.!
}
        
        

The vulnerable code is a Scala method that executes a system command using the sys.process._ package from the Scala standard library. The runCommand method takes a user input string, interpolates it into a system command string, and then executes it using the ! method.

The vulnerability lies in the fact that the user input is directly interpolated into the system command string without any sanitization or escaping. This means that if the user input contains any shell metacharacters or sequences (such as ;, &&, ||, |, >, <, $(command), command, etc.), they will be interpreted by the shell and could potentially alter the command that gets executed.

For example, if the user input is ; rm -rf /, the resulting command string would be ls -l ; rm -rf /, which when executed, would first list the files in the current directory and then delete all files in the system. This is a classic example of a Remote Command Execution vulnerability, which allows an attacker to execute arbitrary commands on the system with the privileges of the user running the vulnerable code.

Steps

  • Validate and sanitize user input before using it to build OS commands.
  • Use parameterized queries or prepared statements to build OS commands.
  • Avoid using user input directly in OS commands.
  • Implement input validation to ensure that only expected characters are allowed.
  • Implement input filtering to remove or escape any special characters that could modify the OS command.
  • Implement input encoding to prevent command injection attacks.
  • Implement least privilege principle by running the OS command with the minimum required privileges.
  • Regularly update and patch the underlying operating system to mitigate known vulnerabilities.

Compliant code

        import sys.process._

def runCommand(userInput: String): String = {
  val sanitizedInput = sanitizeInput(userInput)
  val command = Seq("ls", "-l", sanitizedInput)
  command.!
}

def sanitizeInput(input: String): String = {
  val allowedChars = (('a' to 'z') ++ ('A' to 'Z') ++ ('0' to '9') ++ Seq('-', '_')).toSet
  input.filter(allowedChars.contains(_))
}
        
        

The original code was vulnerable to remote command execution because it was building an OS command using a string that could be manipulated externally. This could allow an attacker to inject malicious commands.

The fixed code mitigates this vulnerability by sanitizing the user input before using it to build the OS command. The sanitizeInput function filters out any characters from the input that are not alphanumeric or the '-' or '_' characters. This ensures that only expected characters are allowed in the user input.

Additionally, the runCommand function now builds the OS command using a sequence of strings instead of a single string. This prevents the user input from being interpreted as part of the command itself, further mitigating the risk of command injection attacks.

Finally, the code follows the principle of least privilege by running the OS command with the minimum required privileges. This limits the potential damage that could be caused if an attacker were able to exploit a vulnerability in the command.

It's also recommended to regularly update and patch the underlying operating system to mitigate known vulnerabilities.

References