Skip to main content

Remote command execution

Need

Prevention of unauthorized remote command execution

Context

  • Usage of Go 1.16 for building high-performance and scalable applications
  • Usage of Gin framework for building web applications in Go
  • Usage of os/exec for executing external commands and processes

Description

Non compliant code

package main

import (
"os/exec"
"github.com/gin-gonic/gin"
)

func main() {
r := gin.Default()
r.GET("/cmd", func(c *gin.Context) {
cmd := c.Query("cmd")
out, _ := exec.Command("bash", "-c", cmd).Output()
c.String(200, string(out))
})
r.Run()
}

This code snippet is a simple HTTP server using the Gin framework in Go. It listens for GET requests on the "/cmd" route. The handler function for this route takes a query parameter cmd from the URL, which is supposed to be a command to be executed on the server's OS.

The vulnerability lies in the line:

out, _ := exec.Command("bash", "-c", cmd).Output()

Here, the server executes the command received from the client without any form of sanitization or validation. This is a classic example of a Remote Command Execution (RCE) vulnerability, where an attacker can run arbitrary commands on the server's OS just by manipulating the cmd query parameter in the URL.

For instance, an attacker could send a request like http://server.com/cmd?cmd=rm -rf / to delete all files in the server's root directory, or http://server.com/cmd?cmd=cat /etc/passwd to read the server's password file.

This vulnerability can lead to severe consequences, like data loss, data theft, denial of service (DOS), or even full system compromise if the server process runs with high privileges.

Steps

  • Validate and sanitize user input: Before using the user input to build the OS command, validate and sanitize it to ensure it only contains allowed characters and does not include any special characters or commands.
  • Use parameterized queries or prepared statements: Instead of directly concatenating user input into the OS command, use parameterized queries or prepared statements to separate the command from the user input. This helps to prevent command injection attacks.
  • Implement input validation and whitelisting: Define a whitelist of allowed commands or inputs and validate the user input against this whitelist. Reject any input that does not match the allowed commands or inputs.
  • Limit privileges: Run the backend application with the least privileges necessary to perform its intended functions. This helps to minimize the potential impact of a successful command injection attack.
  • Regularly update dependencies: Keep all dependencies, including the Gin framework, up to date with the latest security patches and fixes. This helps to mitigate any known vulnerabilities in the framework.
  • Implement strong access controls: Ensure that only authorized users or systems can access the vulnerable endpoint. Implement authentication and authorization mechanisms to restrict access to sensitive functionality.
  • Implement logging and monitoring: Enable logging and monitoring for the backend application to detect and respond to any suspicious or malicious activities, including command injection attempts.
  • Educate developers: Provide training and awareness programs to developers about secure coding practices, including the risks and mitigation strategies for command injection vulnerabilities.

Compliant code

package main

import (
"net/http"
"os/exec"
"github.com/gin-gonic/gin"
"regexp"
)

func main() {
r := gin.Default()
r.GET("/cmd", func(c *gin.Context) {
cmd := c.Query("cmd")

// Validate and sanitize user input
isValid := validateInput(cmd)
if !isValid {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid command"})
return
}

out, _ := exec.Command("bash", "-c", cmd).Output()
c.String(200, string(out))
})
r.Run()
}

// Validate input against a whitelist of allowed commands
func validateInput(input string) bool {
// Define a whitelist of allowed commands
allowedCommands := regexp.MustCompile(`^[a-zA-Z0-9\\s]*$`)

// Validate the input against the whitelist
return allowedCommands.MatchString(input)
}

The original code was vulnerable to remote command execution because it directly used user input to build an OS command without any validation or sanitization. This could allow an attacker to manipulate the input to execute arbitrary commands on the system.

The fixed code introduces a validation function validateInput that checks the user input against a whitelist of allowed commands. This whitelist is defined using a regular expression that only allows alphanumeric characters and spaces. Any input that does not match this pattern is rejected with a 400 Bad Request response.

This validation helps to ensure that the user input cannot include any special characters or commands that could modify the OS command. As a result, it helps to prevent command injection attacks.

In addition to this code fix, it's also recommended to implement other security measures such as using parameterized queries or prepared statements, running the application with the least privileges necessary, keeping all dependencies up to date, implementing strong access controls, enabling logging and monitoring, and educating developers about secure coding practices.

References