Go中的命令路径安全

2021-01-20 11:20:42

今天的Go安全性发行版修复了涉及在不受信任的目录中进行PATH查找的问题,该问题可能导致在go get命令期间远程执行。我们希望人们对这究竟意味着什么以及他们自己的程序中是否可能存在问题有疑问。我们已应用的修复程序,如何确定您自己的程序是否容易遭受类似问题的影响,以及解决该问题的方法。

go命令的设计目标之一是大多数命令-包括go build,go doc,go get,go install和go list-不要从互联网上下载任意代码。有几个明显的例外: ,执行测试和执行生成操作会运行任意代码–这是他们的工作。但是出于各种原因(包括可复制的内部版本和安全性),其他代码都不能执行。因此,当可以使用get get诱骗执行任意代码时,我们认为这是一个安全漏洞。

如果go get一定不能运行任意代码,那么不幸的是,这意味着它调用的所有程序(例如编译器和版本控制系统)也都在安全范围内。例如,过去,我们曾遇到过巧妙使用问题版本控制系统中晦涩的编译器功能或远程执行错误变成了Go中的远程执行错误。(请注意,Go 1.16旨在通过引入GOVCS设置来改善这种情况,该设置允许准确配置允许哪个版本控制系统以及何时使用。)

但是,今天的错误完全是我们的错,而不是gcc或git的错误或晦涩的功能。该错误涉及Go和其他程序如何查找其他可执行文件,因此我们需要花一点时间来研究一下可以了解详细信息。

所有操作系统都具有可执行路径的概念(在Unix上为$ PATH,在Windows上为%PATH%;为简单起见,我们仅使用术语PATH),它是目录的列表。一个shell提示符,shell在列出的每个目录中依次查找以您键入的名称的可执行文件。它将运行找到的第一个可执行文件,或者显示诸如“找不到命令”之类的消息。

在Unix上,这个想法首先出现在第七版Unix的Bourne shell(1979)中。手册说明:

shell参数$ PATH定义了包含命令的目录的搜索路径,每个替代目录名都用冒号(:)分隔,默认路径是:/ bin:/ usr / bin,如果命令名包含/,则不使用搜索路径。否则,在路径中的每个目录中搜索可执行文件。

请注意默认值:当前目录(此处用空字符串表示,但我们称其为“点”)在/ bin和/ usr / bin之前列出。MS-DOS,然后Windows选择了硬编码这样的行为:在那些系统上,始终先自动搜索点,然后再考虑%PATH%中列出的任何目录。

正如Grampp和Morris在其经典论文“ UNIX操作系统安全性”(UNIX Operating System,1984)中指出的那样,在PATH中的系统目录之前放置点是指,如果您将CD插入目录并运行ls,则可能会从该目录中获得恶意副本,而不是从目录中获取恶意副本。如果您可以欺骗系统管理员以root用户身份在主目录中运行ls,那么您可以运行所需的任何代码。由于这个问题以及类似的问题,基本上所有现代Unix发行版都设置了一个新用户& #39;默认的PATH排除点。但是,无论PATH怎么说,Windows系统都会继续首先搜索点。

在通常配置的Unix上,shell在PATH的系统目录中运行go可执行文件。但是在Windows上键入该命令时,cmd.exe首先检查点。如果是。\ go.exe(或。\ go.bat或许多其他选择)存在,cmd.exe运行该可执行文件,而不是您的PATH中的一个。

对于Go来说,PATH搜索由exec.LookPath处理,然后由exec.Command自动调用。为了更好地适应宿主系统,Go的exec.LookPath实现了Unix上的Unix规则和Windows上的Windows规则。 ,此命令

行为与在操作系统外壳中键入go version相同。在Windows上,如果存在则运行。\ go.exe。

(值得注意的是,Windows PowerShell更改了此行为,删除了隐式的点搜索,但cmd.exe和Windows C库的SearchPath函数继续像往常一样运行。Go继续匹配cmd.exe。)

当go get下载并构建包含import" C"的软件包时,它将运行一个名为cgo的程序来准备相关C代码的Goequivalent.go命令在包含软件包源代码的目录中运行cgo。 cgo已经生成了Go输出文件,go命令本身会在生成的Go文件和宿主C编译器(gcc或clang)上调用Go编译器以构建软件包中包含的所有C源代码。所有这些都很好用。找到主机C编译器?它当然在PATH中。幸运的是,当它在包源目录中运行C编译器时,它将从调用go命令的原始目录中进行PATH查找:

因此,即使Windows系统上存在badpkg \ gcc.exe,该代码段也将找不到它。exec.Command中发生的查找并不知道badpkg目录。

go命令使用类似的代码来调用cgo,在这种情况下,甚至没有路径查找,因为cgo始终来自GOROOT:

这比以前的代码段更安全:没有机会运行可能存在的任何错误的cgo.exe。

但是事实证明,cgo本身还会在它创建的某些临时文件上调用宿主C编译器,这意味着它自己执行以下代码:

现在,由于cgo本身在badpkg中运行,而不是在运行go命令的目录中运行,因此如果该文件存在,它将运行badpkg \ gcc.exe,而不是查找系统gcc。

因此,攻击者可以创建一个使用cgo并包含gcc.exe的恶意程序包,然后所有运行Windows的Windows用户都可以下载并构建攻击者的程序包,该程序将优先于攻击者提供的gcc.exe来运行该程序。系统路径。

Unix系统首先避免了该问题,因为通常在路径中不包含点,其次是因为模块解压缩未设置其写入文件的执行位。但是,在PATH中在系统目录之前加点并使用GOPATH模式的Unix用户将像Windows一样容易受到感染。用户(如果能描述您的情况,那么今天是从路径中删除点并开始使用Go模块的好日子。)

使用go get命令下载并运行恶意gcc.exe显然是不可接受的,但是允许这样做的实际错误是什么呢?那么解决方法是什么呢?

一个可能的答案是错误是cgo在不受信任的源目录中而不是在调用go命令的目录中搜索主机C编译器。如果那是错误的,那么解决方法是更改​​go命令将cgo的完整路径传递给主机C编译器,因此cgo无需在不可信目录中进行PATH查找。

另一个可能的答案是错误是在PATH查找过程中查找,无论是在Windows上自动发生还是在Unix系统上由于显式的PATH条目而自动发生。用户可能希望在dot中查找以找到他们在控制台或shell窗口中键入的命令,但不太可能他们也希望在该处查找键入命令的子进程的子进程。如果那是错误的话,则解决方法是更改​​cgo命令,使其在执行过程中不要在点中查找PATH查找。

我们确定这两个都是错误,所以我们都应用了这两个修复程序。go命令现在将完整的主机C编译器路径传递给cgo。最重要的是,cgo,go和Go发行版中的所有其他命令现在都使用os /的变体如果exec软件包以前使用过dot的可执行文件,则会报告一个错误。go / build和go / import软件包对go命令和其他工具的调用使用相同的策略,这应该可以避免出现任何类似的安全问题可能会潜伏。

出于谨慎考虑,我们还对goimports和gopls等命令以及golang.org/x/tools/go/analysis和golang.org/x/tools/go/packages库进行了类似的修复,它们调用了如果您在不受信任的目录中运行这些程序,例如,如果git checkout不受信任的存储库并cd进入它们,然后运行类似的程序,则您使用Windows或在PATH中使用带点的Unix,那么您应该如果您计算机上唯一不受信任的目录是go get管理的模块缓存中的目录,那么您只需要新的Go版本即可。

更新到新的Go版本之后,您可以使用以下方法更新到最新的gopls:

您甚至可以在依赖golang.org/x/tools/go/packages的程序之前更新依赖于golang.org/x/tools/go/packages的程序,方法是在go get期间添加对依赖关系的显式升级:

对于使用go / build的程序,您只需使用更新的Go版本重新编译它们即可。

同样,仅当您是Windows用户或Unix用户(在PATH中带有点)并且您在不信任的源目录中运行这些程序(可能包含恶意程序)时,才需要更新这些其他程序。

如果您在自己的程序中使用exec.LookPath或exec.Command,则只需要担心您(或您的用户)是否在目录中包含不受信任的内容中运行程序即可;如果是这样,则可以使用dot中的可执行文件来启动子进程。 (同样,在Windows上总是使用dot的可执行文件,而在Unix上只有不常见的PATH设置。)

如果您担心的话,我们将更受限制的os / exec变体发布为golang.org/x/sys/execabs。您可以在程序中使用它,只需替换

我们已经在golang.org/issue/38736上进行了讨论,是否应该更改Windows总是在PATH查找中始终偏爱当前目录的Windows行为(在exec.Command和exec.LookPath期间)。有关此博客文章中讨论的安全性问题。一个支持性的观点是,尽管Windows SearchPath API和cmd.exe始终始终搜索当前目录,但cmd.exe的后继者PowerShell却没有,显然可以看出原始行为是错误的反对此更改的观点是,它可能会破坏打算在当前目录中查找程序的现有Windows程序。我们不知道有多少这样的程序,但是如果PATH查找开始完全跳过当前目录,它们将导致无法解释的故障。

我们在golang.org/x/sys/execabs中采用的方法可能是合理的中间立场,它会找到旧PATH查找的结果,然后返回明显的错误而不是使用当前目录中的结果.exec返回的错误存在prog.exe时.Command(" prog")如下所示:

对于确实改变了行为的程序,此错误应该使发生的事情非常清楚。打算从当前目录运行程序的程序可以使用exec.Command(" ./ prog")代替(语法有效在所有系统上,甚至Windows)。