根据Go语言的设计,当用户在使用go get命令去下载go包的时候,该命令会在不运行包中任何代码的前提下编译和安装这个包。这种设计的本意是让用户在第三方包不可信的前提下仍然可以安全地下载、编译和安装Go包。
理论上讲,对Go工具链来说这似乎短小精悍,但实际上,这种使用模式隐藏着许多问题。go get命令对底层操作做了很多封装,封装的内容包括如:可以根据配置文件来调用如git和clang等第三方工具等。因此,如何确保在go get命令中对第三方工具的安全调用,是go目前面临的一项巨大挑战,并且预计需要很大的努力才可以解决。
任意代码执行(ACE)漏洞回顾
Go 1.9.1 (CVE-2017–15041)
2017年,Simon Rawet 报告了一个允许通过代码库嵌套的方式来执行任意代码的bug(#22125)。该bug是首先将整个Git repo提交到SVN仓库,此时就可以通过利用git hooks的方式在该代码库中运行恶意程序代码。
这个漏洞的危险系数以CVSS 3.1的标准来评判的话,评分高达9.8(几乎是最高的分数),这个漏洞也是go语言中第一个被发现的ACE漏洞。
Go 1.9.4 (CVE-2018–6574)
2018年,我发现可以利用#cgo指令向编译器传递恶意标志位的方式来调用clang和gcc命令,从而在执行go get时运行任意代码(#23672)。
针对这个bug的修复具有代表性意义。Go语言团队通过设置合法参数列表的形式来限制编译器和链接器的参数。但这种修复方式却是一种具有一定破坏性的修复:一般来说,任何针对Go 1.0.0版本编译的包都可以用最新的Go版本进行重新编译,但这个安全补丁影响了许多使用cgo的包,可能导致这些包不能重新编译使用。
Go 1.11.3 (CVE-2018–16873)
2018年底,Heroku平台安全团队的Etienne Stalmans发现了一个利用git执行恶意指令的bug(#29230)。这与第一个Git漏洞的工作原理有些类似,它通过欺骗go get去获取恶意的.git目录到文件系统,然后运行git命令去执行恶意代码。
Go 1.11.3 (CVE-2018–16874)
同时,腾讯安全平台的ztz发现,在导入路径中使用大括号可以让他在执行go get命令时拥有对任意文件系统写入权限(#29231)。这个漏洞可以可以通过多种方式执行任意代码(例如,可以在PATH环境变量中设置可执行文件路径中或向.git目录写入钩子函数)
Go 1.15.5 (CVE-2020–28366)
上个月,我花了一些时间重新审视了cgo命令行参数攻击,发现我可以通过恶意符号变量名来将任意链接标志位注入到代码库编译过程中去(#42559)。
Go 1.15.5 (CVE-2020–28367)
与此同时,Imre Rad发现对cgo命令行参数的验证并不充分(#42556)
这是真正的安全问题吗?
你可能想知道这是否真的是一个问题。毕竟,其他语言允许在编译构建阶段任意执行代码。比如在像Rust这类的语言中,编译构建阶段执行代码被认为是一种特性。此时,我们就很好奇在这种情况下,这两者有什么区别呢?
区别在于Go的维护者认为在在构建时执行任意执行代码是不可接受的。就这点区别而已。如果有人说你可以安全地构建不受信任的代码,那么开发人员就会默认执行这样的操作(不加检查的构建第三方代码)。针对抱有这种期待的开发人员来说,当第三方可以在他们意料之外的地方执行代码时,就会造成严重的安全问题。
上述类似的漏洞可能会一直层出不穷,而且对安全问题的讨论可能会一直持续下去。我不能断定Go维护者是基于什么原因选择了他们特有的安全模型,但我确定的是,要像对待rust编译构建过程的态度一样,不要盲目相信Go的编译构建过程的安全性。我们可以打赌,以后还会有更多的任意代码执行漏洞在被发现(如果你自己发现任何一个漏洞,你可以向谷歌报告,并从谷歌的漏洞奖励计划中获得奖励!)。
上面的讨论说到底并不是在挖苦Go。Go语言团队在响应问题报告和解决语言bug 及安全漏洞方面做得非常出色,Go仍然是我们最喜欢的语言之一。这里只是提醒大家,要在使用的过程中要注意包引入过程中可能出现的安全问题,并且做好安全防护。