从用户控制的来源构建命令¶
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
如果系统命令调用是从用户提供的数据构建的,并且没有进行充分的净化,则恶意用户可能能够运行命令来窃取数据或破坏系统。
建议¶
尽可能使用硬编码字符串字面量来表示命令,并避免使用 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)
}
}