Go语言编写的代码越来越普遍,尤其是在容器、Kubernetes或云生态系统相关的开发中。Docker是最早采用Golang的项目之一,其次是Kubernetes,之后大量新项目在众多编程语言中选择了Go。与其他语言一样,Go也有其优点和缺点(例如安全漏洞)。这些缺陷可能源于语言本身的缺陷以及程序员编码不当,例如,C代码中的内存安全问题。无论出于何种原因,安全问题都应该在开发过程的早期解决,然后再出现在打包软件中。幸运的是,静态分析工具可以帮助您以更可重复的方式处理这些问题。静态分析工具通过解析以编程语言编写的代码来发现问题。其中许多工具称为linters。传统上,linters更侧重于检查代码中的编码问题、错误、代码风格等。他们可能不会发现代码中的安全问题。例如,Coverity是一种流行的工具,可帮助查找C/C++代码中的问题。但是,也有一些工具旨在检查源代码的安全问题。例如,Bandit可以检查Python代码中的安全漏洞。gosec用于搜索Go源代码中的安全漏洞。gosec通过扫描Go的AST(抽象语法树)来检查源代码是否存在安全问题。开始使用gosec在开始学习和使用gosec之前,需要准备一个用Go语言编写的项目。有这么多开源软件,我相信这不是问题。您可以在GitHub上流行的Golang存储库中找到一个。对于本文,我随机选择了DockerCE项目,但您可以选择任何Go项目。安装Go和gosec如果您没有安装Go,您可以先从存储库中拉取它。如果您使用的是Fedora或其他基于RPM的Linux发行版:$dnfinstallgolang.x86_64如果您使用的是其他操作系统,请参考Golang安装页面。使用version参数验证Go是否安装成功:$goversiongoversiongo1.14.6linux/amd64运行goget命令轻松安装gosec:$gogetgithub.com/securego/gosec/cmd/gosec以上命令会从GitHub源码下载gosec代码,编译安装到指定位置。您还可以在存储库的自述文件中查看安装该工具的其他方法。gosec的源码会被下载到$GOPATH的位置,编译好的二进制文件会安装到你系统设置的bin目录下。可以运行以下命令查看$GOPATH和$GOBIN目录:$goenv|grepGOBINGOBIN="/root/go/gobin"$goenv|grepGOPATHGOPATH="/root/go"如果goget命令成功,gosec二进制应该是你可以使用它:$ls-l~/go/bin/total9260-rwxr-xr-x.1rootroot9482175Aug2004:17gosec你可以将$GOPATH下的bin目录添加到$PATH中。这将允许您像系统上的任何其他命令一样使用gosec命令行工具(CLI)。$whichgosec/root/go/bin/gosec$使用gosec命令行工具的-help选项查看是否按预期工作:$gosec-helpgosec-GolangsecuritycheckergosecanalyzesGosourcecodetolookforcommonprogrammingmistakesthatcanleadtosecurityproblems.VERSION:devGITTAG:BUILDDATE:USAGE:Afterthat,create一个目录,把源码下载到这个目录下作为示例工程(本例中我使用DockerCE):$mkdirgosec-demo$cdgosec-demo/$pwd/root/gosec-demo$gitclonehttps://githubcom/docker/docker-ce.gitCloninginto'docker-ce'...remote:Enumeratingobjects:1271,done.remote:Countingobjects:100%(1271/1271),done.remote:Compressingobjects:100%(722/722),done.remote:Total431003(delta384),reused981(delta318),pack-reused429732Receivingobjects:100%(431003/431003),166.84MiB|28.94MiB/s,done.Resolvingdeltas:100%(221338/2213Updating38),860:1文件(10.860%/10861),完成。代码统计工具(本例中的cloc)显示,这个项目大部分是用Go写的,刚好迎合了gosec的功能。$./cloc/root/gosec-demo/docker-ce/10771textfiles.8724uniquefiles.2560filesignored.------------------------------------------------------------------------------语言文件空白注释代码------------------------------------------------------------------------------Go72221907852304781574580YAML374831817156762Markdown52921422067893ProtocolBuffers14950141656210071使用默认选项运行goCEsec项目运行gosec的默认选项,执行gosec./...命令。屏幕上会有很多输出。最后,您会看到一个简短的“摘要”,其中列出了查看的文件数、所有文件的总行数以及在源代码中发现的问题数。$pwd/root/gosec-demo/docker-ce$timegosec./...[gosec]2020/08/2004:44:15Includingrules:default[gosec]2020/08/2004:44:15Excludingrules:default[gosec]2020/08/2004:44:15导入目录:/root/gosec-demo/docker-ce/components/engine/opts[gosec]2020/08/2004:44:17检查包:opts[gosec]2020/08/2004:44:17检查文件:/root/gosec-demo/docker-ce/components/engine/opts/address_pools.go[gosec]2020/08/2004:44:17检查文件:/root/gosec-demo/docker-ce/components/engine/opts/env.go[gosec]2020/08/2004:44:17Checkingfile:/root/gosec-demo/docker-ce/components/engine/opts/hosts.go#EndofgosecrunSummary:Files:1278Lines:173979Nosec:4问题:644real0m52.019suser0m37.284ssys0m12.734s$滚动屏幕,您会看到以不同颜色突出显示的行:红色表示需要尽快审查的高优先级问题,黄色表示中等优先级问题。关于误判在开始检查代码之前,我想分享几个基本原则。默认情况下,静态检查工具根据一组规则分析测试代码并报告他们发现的任何问题。这是否意味着该工具报告的每个问题都需要修复?没有。这个问题的最佳答案是设计和开发软件的人。他们最熟悉代码,更重要的是,他们了解软件将部署在哪里以及如何使用。这个知识点对于判断工具标记的某段代码是否为安全漏洞至关重要。随着工作时间和经验的积累,你会逐渐学会如何让静态分析工具忽略非安全缺陷,让报告内容更具可执行性。因此,判断gosec报告的问题是否需要修复,最好还是让有经验的开发人员手动审计源码。高优先级问题从输出中,gosec发现了DockerCE的高优先级问题,它使用了较低版本的TLS(TransportLayerSecurity())。只要有可能,使用最新版本的软件和库是确保其处于最新状态且没有安全问题的最佳方式。[/root/gosec-demo/docker-ce/components/engine/daemon/logger/splunk/splunk.go:173]-G402(CWE-295):TLSMinVersiontoolow。(置信度:高,严重性:高)172:>173:tlsConfig:=&tls.Config{}174:它还发现了一个弱随机数生成器。它是否是安全漏洞取决于生成的随机数的使用方式。[/root/gosec-demo/docker-ce/components/engine/pkg/namesgenerator/names-generator.go:843]-G404(CWE-338):Useofweakrandomnumbergenerator(math/randinsteadofcrypto/rand)(置信度:中等,严重性:HIGH)842:begin:>843:name:=fmt.Sprintf("%s_%s",left[rand.Intn(len(left))],right[rand.Intn(len(right))])844:ifname=="boring_wozniak"/*SteveWozniakisnotboring*/{中等优先级问题此工具还发现了一些中等优先级问题。它通过可能被恶意行为者利用的与tar相关的减压炸弹标记潜在的DoS威胁。[/root/gosec-demo/docker-ce/components/engine/pkg/archive/copy.go:357]-G110(CWE-409):PotentialDoSvulnerabilityviadecompressionbomb(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):Potentialfileinclusionviavariable(Confidence:HIGH,Severity:MEDIUM)79:ifcaPath!=""{>80:ifca,err=ioutil.ReadFile(caPath);err!=nil{81:returnnil,err文件和目录通常是操作系统安全的最基本元素。这里gosec报了一个问题,可能需要检查目录的权限是否安全。[/root/gosec-demo/docker-ce/components/engine/contrib/apparmor/main.go:41]-G301(CWE-276):Expectdirectorypermissionstobe0750orless(置信度:高,严重度:中)40://makesure/etc/apparmor.dexists>41:iferr:=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):子进程以变量启动(置信度:高,严重度:中等)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{Lowpriorityproblem在这个输出中,gosec报告了一个与unsafecall相关的低优先级问题,它绕过了Go提供的内存保护。仔细分析你的callunsafe方式,看看是否有被他人利用的可能。[/root/gosec-demo/docker-ce/components/engine/pkg/archive/changes_linux.go:264]-G103(CWE-242):Useofunsafecallsshouldbeaudited(置信度:高,严重度:低)263:forlen(buf)>0{>264:dirent:=(*unix.Dirent)(unsafe.Pointer(&buf[0]))265:bufbuf=buf[dirent.Reclen:][/root/gosec-demo/docker-ce/components/engine/pkg/devicemapper/devmapper_wrapper.go:88]-G103(CWE-242):应审核不安全调用的使用(置信度:高,严重性:低)87:funcfree(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):未处理的错误。(置信度:高,严重度:低)171:err:=tar.Close()>172:os.RemoveAll(dockerfileDir)173:returnerr自定义gosec扫描使用gosec的默认选项会导致很多问题。然而,在人工审核之后,随着时间的推移,您将了解哪些问题不需要标记。您可以指定要排除和包括哪些测试。正如我上面提到的,gosec根据一组规则来发现Go源代码中的问题。这是它使用的完整规则列表:G101:查找硬编码凭据G102:绑定到所有接口G103:审计使用不安全块G104:审计未检查的错误G106:审计使用ssh.InsecureIgnoreHostKeyG107:提供给HTTP请求的url作为污点输入G108:自动暴露/debug/pprof上的分析端点G109:当strconv.Atoi转换为int16或int32时潜在的整数溢出G110:通过解压炸弹的潜在DoSG201:使用格式字符串的SQL查询构造G202:使用字符串的SQL查询构造串联G203:在HTML模板中使用未转义的数据G204:审计命令执行G301:创建目录时文件权限分配不当G302:使用chmod时文??件权限分配不当G303:创建具有可预测路径的临时文件G304:污点输入提供的文件路径G305:解压zip/tar压缩文件时遍历文件G306:写入新文件时文件权限分配不合理G307:放在defer内部返回错误的函数G401:检测DES、RC4、MD5或SHA1的使用G402:查找错误的TLS连接设置G403:确保最小RSA密钥长度为2048位G404:不安全的随机数源(rand)G501:导入黑名单列表:crypto/md5G502:导入黑名单:crypto/desG503:导入黑名单:crypto/rc4G504:导入黑名单:net/http/cgiG505:导入黑名单:crypto/sha1G601:在range语句中可以自定义gosec来避免通过使用隐式元素别名排除指定测试来扫描和报告已知安全的问题。您可以使用带有上述规则编号的-exclude选项来忽略指定的问题。例如,如果您不希望gosec检查源代码中是否存在与硬编码凭据相关的未处理错误,则可以运行以下命令来忽略这些错误:$gosec-exclude=G104./...$gosec-exclude=G104,G101./...有时候明明知道某段代码是安全的,gosec还是报错。但是,你不想完全排除整个检查,因为你希望gosec检查新添加的代码。通过将#nosec标记添加到您知道安全的代码块来避免gosec扫描。这样gosec会继续扫描新的代码,而忽略#nosec标记的代码块。另一方面,如果您只想检查特定问题,您可以通过传递-include选项和规则编号来告诉gosec要运行哪些检查:$gosec-include=G201,G202./...扫描测试filesGo语言自带对测试的支持,单元测试用于验证一个元素是否符合预期。在默认模式下,gosec会忽略测试文件,您可以使用-tests选项将它们包括在内:gosec-tests./...修改输出格式以发现问题只是其功能的一半;另一半是检查它遇到的问题以用户友好和工具友好的方式报告。幸运的是,gosec可以以不同的方式输出。例如,如果您想查看JSON格式的报告,则使用-fmt选项指定JSON格式并将结果保存在results.json文件中:$gosec-fmt=json-out=results.json./...$ls-lresults.json-rw-r--r--.1rootroot748098Aug2005:06results.json${“严重性”:“低”,“信心”:“高”,“cwe”:{“ID”:"242","URL":"https://cwe.mitre.org/data/definitions/242.html"},"rule_id":"G103","details":"Useofunsafecallsshouldbeaudited","file":"/root/gosec-demo/docker-ce/components/engine/daemon/graphdriver/graphtest/graphtest_unix.go","code":"304:\t//Castto[]byte\n305:\theader:=*(*reflect.SliceHeader)(unsafe.Pointer(\u0026buf))\n306:\theader.Len*=8\n","line":"305","column":"36"},用gosec检查静态容易发现的问题检查工具并不能完全替代人工代码审计。但是,当代码量变大且开发人员众多时,此类工具通常有助于以可重复的方式找到容易发现的问题。它有助于帮助新开发人员在编码时识别并避免引入这些安全漏洞。
