CodeQL 文档

从用户控制的来源构建命令

ID: go/command-injection
Kind: path-problem
Security severity: 9.8
Severity: error
Precision: high
Tags:
   - security
   - external/cwe/cwe-078
Query suites:
   - go-code-scanning.qls
   - go-security-extended.qls
   - go-security-and-quality.qls

点击查看 CodeQL 代码库中的查询

如果系统命令调用是从用户提供的数据构建的,并且没有进行充分的净化,则恶意用户可能能够运行命令来窃取数据或破坏系统。

建议

尽可能使用硬编码字符串字面量来表示命令,并避免使用 shell 字符串解释器,例如 sh -c

如果将参数作为单个字符串给出,请避免简单地按空格拆分字符串。参数可能包含带引号的空格,导致它们拆分为多个参数。

如果无法做到这一点,请净化用户输入以避免出现空格和各种类型的引号等字符,因为这些字符可能会改变命令的含义。

示例

在以下示例中,假设函数 handler 是 Web 应用程序中的 HTTP 请求处理程序,其参数 req 包含请求对象

package main

import (
	"net/http"
	"os/exec"
)

func handler(req *http.Request) {
	imageName := req.URL.Query()["imageName"][0]
	outputPath := "/tmp/output.svg"
	cmd := exec.Command("sh", "-c", fmt.Sprintf("imagetool %s > %s", imageName, outputPath))
	cmd.Run()
	// ...
}

处理程序从请求中提取图像文件名,并使用该文件名构造一个 shell 命令,该命令使用 `sh -c` 执行,这可能会导致命令注入。

最好通过直接使用 exec.Command 函数来避免使用 shell 命令,如下例所示

package main

import (
	"log"
	"net/http"
	"os"
	"os/exec"
)

func handler(req *http.Request) {
	imageName := req.URL.Query()["imageName"][0]
	outputPath := "/tmp/output.svg"

	// Create the output file
	outfile, err := os.Create(outputPath)
	if err != nil {
		log.Fatal(err)
	}
	defer outfile.Close()

	// Prepare the command
	cmd := exec.Command("imagetool", imageName)

	// Set the output to our file
	cmd.Stdout = outfile

	cmd.Run()
}

或者,可以使用正则表达式来确保图像文件名在 shell 命令中使用是安全的

package main

import (
	"log"
	"net/http"
	"os/exec"
	"regexp"
)

func handler(req *http.Request) {
	imageName := req.URL.Query()["imageName"][0]
	outputPath := "/tmp/output.svg"

	// Validate the imageName with a regular expression
	validImageName := regexp.MustCompile(`^[a-zA-Z0-9_\-\.]+$`)
	if !validImageName.MatchString(imageName) {
		log.Fatal("Invalid image name")
		return
	}

	cmd := exec.Command("sh", "-c", fmt.Sprintf("imagetool %s > %s", imageName, outputPath))
	cmd.Run()
}

某些命令(例如 git)可以在攻击者指定了传递给该命令的标志时间接执行命令。

为了降低这种风险,可以添加一个 -- 参数以确保后续参数不被解释为标志,或者验证参数是否不以 "--" 开头。

package main

import (
	"log"
	"net/http"
	"os/exec"
	"strings"
)

func handler(req *http.Request) {
	repoURL := req.URL.Query()["repoURL"][0]
	outputPath := "/tmp/repo"

	// Sanitize the repoURL to ensure it does not start with "--"
	if strings.HasPrefix(repoURL, "--") {
		log.Fatal("Invalid repository URL")
	} else {
		cmd := exec.Command("git", "clone", repoURL, outputPath)
		err := cmd.Run()
		if err != nil {
			log.Fatal(err)
		}
	}

	// Or: add "--" to ensure that the repoURL is not interpreted as a flag
	cmd := exec.Command("git", "clone", "--", repoURL, outputPath)
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
}

参考

  • ©GitHub, Inc.
  • 条款
  • 隐私