现在,用 Go 编程语言 编写代码非常普遍,特别是如果您正在使用容器、Kubernetes 或云生态系统。Docker 是最早采用 Golang 的项目之一,Kubernetes 紧随其后,许多新项目选择 Go 而不是其他编程语言。
与任何其他语言一样,Go 也有其自身的优点和缺点,其中包括安全漏洞。这些漏洞可能是由于编程语言本身的问题以及不安全的编码实践引起的,例如 C 代码中的内存安全问题。
无论它们为何发生,安全问题都需要在开发的早期阶段修复,以防止它们潜入已发布的软件中。幸运的是,静态分析工具可以帮助您以更可重复的方式解决这些问题。静态分析工具的工作原理是解析用编程语言编写的源代码,并查找问题。
许多此类工具被称为代码检查器 (linters)。传统上,代码检查器更侧重于查找编程问题、错误、代码风格问题等,它们可能不会发现代码中的安全问题。例如,Coverity 是一种流行的工具,可帮助查找 C/C++ 代码中的问题。但是,有些工具专门用于查找源代码中的安全问题。例如,Bandit 查找 Python 代码中的安全漏洞。gosec 搜索 Go 源代码中的安全漏洞。Gosec 扫描 Go 抽象语法树 (AST) 以检查源代码中的安全问题。
开始使用 gosec
要试用 gosec 并了解其工作原理,您需要一个用 Go 编写的项目。由于有各种各样的开源软件可用,这应该不是问题。您可以通过查看 GitHub 上的 trending Golang 存储库来找到一个。
在本教程中,我随机选择了 Docker CE 项目,但您可以选择任何您想要的 Go 项目。
安装 Go 和 gosec
如果您尚未安装 Go,您可以从您的存储库中获取它。如果您使用 Fedora 或其他基于 RPM 的 Linux 发行版
$ dnf install golang.x86_64
或者您可以访问 Golang 安装页面,了解适用于您操作系统的其他选项。
使用 version
参数验证 Go 是否已安装在您的系统上
$ go version
go version go1.14.6 linux/amd64
$
安装 gosec 只是运行 go get
命令的问题
$ go get github.com/securego/gosec/cmd/gosec
$
这会从 GitHub 下载 gosec 的源代码,对其进行编译,并将其安装在特定位置。您可以在存储库的 README 中找到安装工具的其他方法。
Gosec 的源代码应下载到 $GOPATH
设置的位置,编译后的二进制文件将安装在您为系统设置的 bin
目录中。要查找 $GOPATH
和 $GOBIN
指向的位置,请运行
$ go env | grep GOBIN
GOBIN="/root/go/gobin"
$
$ go env | grep GOPATH
GOPATH="/root/go"
$
如果 go get
命令有效,则 gosec 二进制文件应可用
$
$ ls -l ~/go/bin/
total 9260
-rwxr-xr-x. 1 root root 9482175 Aug 20 04:17 gosec
$
您可以将 $GOPATH
中的 bin
目录添加到 shell 中的 $PATH
变量中。这使得 gosec 命令行界面 (CLI) 像系统上的任何其他命令行一样可用
$ which gosec
/root/go/bin/gosec
$
尝试使用 -help
选项运行 gosec CLI,以查看它是否按预期工作
$ gosec -help
gosec - Golang security checker
gosec analyzes Go source code to look for common programming mistakes that
can lead to security problems.
VERSION: dev
GIT TAG:
BUILD DATE:
USAGE:
接下来,创建一个目录并使用以下命令获取演示项目(在本例中为 Docker CE)的源代码
$ mkdir gosec-demo
$
$ cd gosec-demo/
$
$ pwd
/root/gosec-demo
$
$ git clone https://github.com/docker/docker-ce.git
Cloning into 'docker-ce'...
remote: Enumerating objects: 1271, done.
remote: Counting objects: 100% (1271/1271), done.
remote: Compressing objects: 100% (722/722), done.
remote: Total 431003 (delta 384), reused 981 (delta 318), pack-reused 429732
Receiving objects: 100% (431003/431003), 166.84 MiB | 28.94 MiB/s, done.
Resolving deltas: 100% (221338/221338), done.
Updating files: 100% (10861/10861), done.
$
快速浏览一下源代码,发现该项目的大部分是用 Go 编写的——这正是您需要使用 gosec 功能的。
$ ./cloc /root/gosec-demo/docker-ce/
10771 text files.
8724 unique files.
2560 files ignored.
-----------------------------------------------------------------------------------
Language files blank comment code
-----------------------------------------------------------------------------------
Go 7222 190785 230478 1574580
YAML 37 4831 817 156762
Markdown 529 21422 0 67893
Protocol Buffers 149 5014 16562 10071
使用默认选项运行 gosec
通过从您刚克隆的 Git 存储库中运行 gosec ./...
,在 Docker CE 项目上使用默认选项运行 gosec。屏幕上将显示大量输出。在末尾,您应该看到一个简短的 摘要
,显示扫描的文件数、这些文件中的行数以及在源代码中发现的问题
$ pwd
/root/gosec-demo/docker-ce
$
$ time gosec ./...
[gosec] 2020/08/20 04:44:15 Including rules: default
[gosec] 2020/08/20 04:44:15 Excluding rules: default
[gosec] 2020/08/20 04:44:15 Import directory: /root/gosec-demo/docker-ce/components/engine/opts
[gosec] 2020/08/20 04:44:17 Checking package: opts
[gosec] 2020/08/20 04:44:17 Checking file: /root/gosec-demo/docker-ce/components/engine/opts/address_pools.go
[gosec] 2020/08/20 04:44:17 Checking file: /root/gosec-demo/docker-ce/components/engine/opts/env.go
[gosec] 2020/08/20 04:44:17 Checking file: /root/gosec-demo/docker-ce/components/engine/opts/hosts.go
# End of gosec run
Summary:
Files: 1278
Lines: 173979
Nosec: 4
Issues: 644
real 0m52.019s
user 0m37.284s
sys 0m12.734s
$
如果您滚动浏览屏幕上的输出,您应该会看到一些以各种颜色突出显示的行:红色表示需要首先查看的高优先级问题,黄色表示中优先级问题。
关于误报
在深入了解调查结果之前,我想分享一些基本规则。默认情况下,静态分析工具会报告所有根据工具针对正在测试的代码进行比较的一组规则发现的问题。这是否意味着工具报告的所有内容都是需要修复的问题?嗯,这取决于情况。关于这个问题的最佳权威是设计和开发该软件的开发人员。他们比任何人都更了解代码,更重要的是,他们了解软件将部署的环境以及如何使用它。
当决定工具标记的一段代码是否真的是安全漏洞时,这种知识至关重要。随着时间的推移和经验的积累,您将学会调整静态分析工具,以忽略不是安全漏洞的问题,并使报告更具可操作性。因此,经验丰富的开发人员对源代码进行手动审核将更好地决定 gosec 报告的问题是否值得关注。
高优先级问题
根据输出,gosec 发现 Docker CE 正在使用旧的传输层安全 (TLS) 版本,这是一个高优先级问题。在可能的情况下,最好使用最新版本的软件或库,以确保它是最新的并且没有安全问题。
[/root/gosec-demo/docker-ce/components/engine/daemon/logger/splunk/splunk.go:173] - G402 (CWE-295): TLS MinVersion too low. (Confidence: HIGH, Severity: HIGH)
172:
> 173: tlsConfig := &tls.Config{}
174:
它还发现了一个弱随机数生成器。根据生成的随机数的用途,您可以决定这是否是安全漏洞。
[/root/gosec-demo/docker-ce/components/engine/pkg/namesgenerator/names-generator.go:843] - G404 (CWE-338): Use of weak random number generator (math/rand instead of crypto/rand) (Confidence: MEDIUM, Severity: HIGH)
842: begin:
> 843: name := fmt.Sprintf("%s_%s", left[rand.Intn(len(left))], right[rand.Intn(len(right))])
844: if name == "boring_wozniak" /* Steve Wozniak is not boring */ {
中优先级问题
该工具还发现了一些中优先级问题。它标记了一个潜在的拒绝服务 (DoS) 漏洞,该漏洞通过与 tar 相关的解压炸弹,可能会被恶意行为者利用。
[/root/gosec-demo/docker-ce/components/engine/pkg/archive/copy.go:357] - G110 (CWE-409): Potential DoS vulnerability via decompression bomb (Confidence: MEDIUM, Severity: MEDIUM)
356:
> 357: if _, err = io.Copy(rebasedTar, srcTar); err != nil {
358: w.CloseWithError(err)
它还发现了一个与通过变量包含的文件相关的问题。如果恶意用户控制了这个变量,他们可以更改其内容以读取不同的文件。
[/root/gosec-demo/docker-ce/components/cli/cli/context/tlsdata.go:80] - G304 (CWE-22): Potential file inclusion via variable (Confidence: HIGH, Severity: MEDIUM)
79: if caPath != "" {
> 80: if ca, err = ioutil.ReadFile(caPath); err != nil {
81: return nil, err
文件和目录权限通常是操作系统上安全性的基本构建块。在这里,gosec 识别出一个问题,您可能需要检查目录的权限是否安全。
[/root/gosec-demo/docker-ce/components/engine/contrib/apparmor/main.go:41] - G301 (CWE-276): Expect directory permissions to be 0750 or less (Confidence: HIGH, Severity: MEDIUM)
40: // make sure /etc/apparmor.d exists
> 41: if err := os.MkdirAll(path.Dir(apparmorProfilePath), 0755); err != nil {
42: log.Fatal(err)
通常,您需要从源代码启动命令行实用程序。Go 使用内置的 exec 库来完成此任务。仔细分析用于生成此类实用程序的变量可以发现安全漏洞。
[/root/gosec-demo/docker-ce/components/engine/testutil/fakestorage/fixtures.go:59] - G204 (CWE-78): Subprocess launched with variable (Confidence: HIGH, Severity: MEDIUM)
58:
> 59: cmd := exec.Command(goCmd, "build", "-o", filepath.Join(tmp, "httpserver"), "github.com/docker/docker/contrib/httpserver")
60: cmd.Env = append(os.Environ(), []string{
低严重性问题
在此输出中,gosec 识别出与 “unsafe” 调用相关的低严重性问题,这些调用通常会绕过 Go 提供的所有内存保护。仔细分析您对 “unsafe” 调用的使用,看看它们是否可能以任何方式被利用。
[/root/gosec-demo/docker-ce/components/engine/pkg/archive/changes_linux.go:264] - G103 (CWE-242): Use of unsafe calls should be audited (Confidence: HIGH, Severity: LOW)
263: for len(buf) > 0 {
> 264: dirent := (*unix.Dirent)(unsafe.Pointer(&buf[0]))
265: buf = buf[dirent.Reclen:]
[/root/gosec-demo/docker-ce/components/engine/pkg/devicemapper/devmapper_wrapper.go:88] - G103 (CWE-242): Use of unsafe calls should be audited (Confidence: HIGH, Severity: LOW)
87: func free(p *C.char) {
> 88: C.free(unsafe.Pointer(p))
89: }
它还标记了源代码库中未处理的错误。您应该处理源代码中可能出现错误的情况。
[/root/gosec-demo/docker-ce/components/cli/cli/command/image/build/context.go:172] - G104 (CWE-703): Errors unhandled. (Confidence: HIGH, Severity: LOW)
171: err := tar.Close()
> 172: os.RemoveAll(dockerfileDir)
173: return err
自定义 gosec 扫描
使用默认设置的 gosec 会引发许多类型的问题。但是,通过手动审核和随着时间的推移,您会了解哪些问题不需要标记。您可以自定义 gosec 以排除或包含某些测试。
正如我上面提到的,gosec 使用一组规则来查找 Go 源代码中的问题。以下是它使用的 规则 的完整列表
- G101:查找硬编码的凭据
- G102:绑定到所有接口
- G103:审核 unsafe 块的使用
- G104:审核未检查的错误
- G106:审核 ssh.InsecureIgnoreHostKey 的使用
- G107:作为污点输入提供给 HTTP 请求的 URL
- G108:在 /debug/pprof 上自动公开性能分析端点
- G109:strconv.Atoi 结果转换为 int16/32 可能导致整数溢出
- G110:通过解压炸弹可能导致 DoS 漏洞
- G201:使用格式字符串构造 SQL 查询
- G202:使用字符串连接构造 SQL 查询
- G203:在 HTML 模板中使用未转义的数据
- G204:审核命令执行的使用
- G301:创建目录时使用了不良的文件权限
- G302:chmod 使用了不良的文件权限
- G303:使用可预测的路径创建临时文件
- G304:文件路径作为污点输入提供
- G305:提取 zip/tar 存档时的文件遍历
- G306:写入新文件时使用了不良的文件权限
- G307:延迟返回错误的方法
- G401:检测 DES、RC4、MD5 或 SHA1 的使用
- G402:查找不良的 TLS 连接设置
- G403:确保 RSA 密钥长度至少为 2048 位
- G404:不安全的随机数源 (rand)
- G501:导入黑名单:crypto/md5
- G502:导入黑名单:crypto/des
- G503:导入黑名单:crypto/rc4
- G504:导入黑名单:net/http/cgi
- G505:导入黑名单:crypto/sha1
- G601:范围语句中项目的隐式内存别名
排除特定测试
您可以自定义 gosec 以防止其查找和报告安全的问题。要忽略特定问题,您可以将 -exclude
标志与上面的规则代码一起使用。
例如,如果您不希望 gosec 查找与源代码中硬编码凭据相关的未处理错误,您可以通过运行以下命令来忽略它们
$ gosec -exclude=G104 ./...
$ gosec -exclude=G104,G101 ./...
有时,您知道源代码的某个区域是安全的,但 gosec 仍然将其报告为问题。但是,您不想完全排除该检查,因为您希望 gosec 扫描添加到代码库的新代码。要防止 gosec 扫描您知道安全的区域,请将 #nosec
标志添加到源代码的该部分。这确保 gosec 继续扫描新代码以查找问题,但忽略标记为 #nosec
的区域。
运行特定检查
另一方面,如果您需要专注于特定问题,您可以使用 -include
选项和规则代码来告诉 gosec 运行这些检查
$ gosec -include=G201,G202 ./...
扫描测试文件
Go 语言内置了对测试的支持,它使用单元测试来验证组件是否按预期工作。在默认模式下,gosec 会忽略测试文件,但如果您希望将它们包含在扫描中,请使用 -tests
标志
gosec -tests ./...
更改输出格式
查找问题只是图片的一部分;另一部分是以对人和工具都易于理解的方式报告它发现的内容。幸运的是,gosec 可以以多种方式输出结果。例如,如果您想以 JSON 格式获取报告,请使用 -fmt
选项指定 JSON 并将结果保存在 results.json
文件中
$ gosec -fmt=json -out=results.json ./...
$ ls -l results.json
-rw-r--r--. 1 root root 748098 Aug 20 05:06 results.json
$
{
"severity": "LOW",
"confidence": "HIGH",
"cwe": {
"ID": "242",
"URL": "https://cwe.mitre.org/data/definitions/242.html"
},
"rule_id": "G103",
"details": "Use of unsafe calls should be audited",
"file": "/root/gosec-demo/docker-ce/components/engine/daemon/graphdriver/graphtest/graphtest_unix.go",
"code": "304: \t// Cast to []byte\n305: \theader := *(*reflect.SliceHeader)(unsafe.Pointer(\u0026buf))\n306: \theader. Len *= 8\n",
"line": "305",
"column": "36"
},
使用 gosec 查找唾手可得的成果
静态分析工具不能替代手动代码审核。但是,当代码库很大,并且有很多人为其做出贡献时,这样的工具通常有助于以可重复的方式查找唾手可得的成果。它还有助于帮助新开发人员识别和避免编写引入这些安全漏洞的代码。
评论已关闭。