修复单元测试并到处挖坑
几年前,我在公司上班,和一些朋友一起做一个项目。 由于在一家非常大的公司,有各种各样的库可以用来做事情。 鉴于功能列表中有一个“would be nice”的项目涉及对文件系统进行一些操作,我开始寻找匹配的功能,结果逐渐失控。 事情是这样的。程序需要创建一系列路径。 这是有“/my/path
”的地方,需要在它下面嵌套几个目录,所以最终会得到“/my/path/project/staging
”和“/my/path/project/prod
”等等像这样的格式。当处于那种情况时,不能只是 mkdir() 最终路径。 它不会工作。 你必须从上到下开始。 已经存在的那些可能会因 -EEXIST
而失败,因此有时查看那个文件夹的内容,然后再调用 mkdir 调用更有意义。
所以,你可以这样做:mkdir("/my") : -EEXIST
mkdir("/my/path") : -EEXIST
mkdir("/my/path/project")
: 这个工作正常mkdir("/ my/path/project/staging")
:这也很好用mkdir("/my/path/project/prod")
:这个也可以
或者,你可以做更多类似这样的事情:lstat("/my")
:有效,并且是一个目录,继续lstat("/my/path")
:有效,是一个目录,继续lstat("/my /path/project'") : -ENOENT
,好的,切换到调用 mkdir!mkdir("/my/path/project")
:这成功了
……等等,到这里为止,你明白了吧?
正如你所看到的,这是一种需要付出一些努力才能做好的事情,一旦有人做到了,就会很高兴与公司的其他人分享。 出于这个原因,我研究了我们代码库的“公共”部分,看看在“目录创建实用程序”方面有什么。 也许已经有了可以做到这一点的东西!我的发现是……令人不安的。 我发现了一个声称可以创建目录的函数,但它不仅仅是对通常的 C 库 mkdir() 函数的简单传递。 它也没有像上面那样做任何事情。 它基本上是这样做的(在伪代码中):void create_a_directory(path) { system("mkdir -p " + path); }
是的,没错,它启动了一个子进程来运行 mkdir 程序,也就是说,如果你现在正在使用一些模糊的 Unixy,那么它现在可能就在您的 /bin 目录中。如果使用的是运行 Monterey 的 Mac,那就是:-rwxr-xr-x 1 root wheel 134144 Dec 7 15:39 /bin/mkdir
为什么有人会更改此功能来执行此操作?“-p”开关是最神奇的部分。 实际的“mkdir”命令行工具支持“根据需要创建中间目录”。 它处理我之前提到的所有内容,从这里你可以明白为什么有人可能想要“利用”它。不幸的是,在这样做的过程中,它也造成了一个巨大的安全漏洞。C system() 调用through a shell
来运行子命令,无论传递给 shell 什么都取决于各种有趣的 shell 扩展规则。
这些 shell 扩展规则包括用于拆分命令的分号。 这意味着如果用分号与另一个命令分隔,这个命令将在“mkdir -p /whatever
”之后运行。更糟糕的是,由于这在公司所有类型的程序使用的通用功能中发生了变化,这意味着任何以该语言(使用该库)创建目录的东西现在都容易受到这种注入的攻击。
想象一下。 在某些时候,编写了一些基于用户名或类似内容创建临时路径的东西。将公司的公共目录设为对象,只调用 mkdir() ,使用一个路径,仅此而已。 如果有人传入一些用户名,如“foo;touch you.are.owned
”,那将成为目录名称的一部分。如果它只是文件系统上的一个愚蠢的名字,不会有什么不好的事情发生。
然后,多年后的一天,有人将 system() 的通用工具更改为其他东西,现在它创建了一个名为“foo”的目录,然后创建了一个名为“you.are.owned
”的空文件。此时通过它做一些更糟糕的事情是轻而易举的,比如打开一个 shell 到其他地方,泄露数据,或者其他什么。 你的程序现在对这种攻击是敞开的,你什么也没改变。我想你应该不会感到惊讶,我们不得不撤消这个,然后不得不让每个使用这个功能的人重新编译并发布他们的东西的新版本。那很有趣”。
最后,你可能想知道为什么会发生这种情况,以及为什么有人会将调用 C 库函数的函数更改为运行 $(*^&$( 子进程的函数。啊,好吧,这很容易。有人有一个单元测试调用具有多个元素的复杂路径的目录创建函数。有时,并不是所有的中间目录都存在,然后它会失败。他们知道“mkdir -p
”是需要一步一步来操作,但是需要程序为他们做这件事。他们更改了公共库,将其注入,重新运行单元测试,现在它开始通过了,所以他们完成了。事情就是这样发生的:一个微小的变化就会让大门敞开。有人解决了他们自己的局部问题而给全局埋下了隐患。愿你的假期没有这种纠结。