最新关于shell通配符展开安全问题
Alexander Hu 发给 distros 安全邮件列表及部分系统安全维护人员一封主题为“shell 通配符展开问题”的邮件,讨论了一个被认为是已知的 shell 行为(尽管有些意外且可能存在风险),但实际上并非一个漏洞。由于此信息已在 distros 列表中讨论,为透明和一致性起见,我们现在将其带到 oss-security。
以下是我对此次讨论的总结和一些额外的看法:
文件名可能包含一些对特定程序有特殊含义的字符串。shell 会直接展开通配符,而不会去判断或识别展开的结果会被传递到哪些程序,或者这些程序如何处理这些字符串。邮件中提到的例子是文件名 --version
,当该文件名传递给 GNU grep 时会被误认为是一个选项。
鉴于此类问题和类似情况已被熟知,许多程序(使用 getopt(3) 和 getopt_long(3))在遇到参数 --
后会停止处理选项。该参数的作用是将选项和其他参数(如文件名)分隔开来。
换句话说,以下命令是不可靠或不安全的:
grep text *
而使用以下命令会更可靠和安全:
grep text -- *
此操作避免了上述问题,尽管它可能并不完全符合预期。例如,它会忽略以点开头的文件名,并且在文件数量过多时会失败。
对不可信的目录内容进行可靠处理是一项复杂的任务。对于递归操作,我们已经有一些较好的做法:
grep -r text .
这样无需 shell 通配符展开,直接传递目录名称,例如 .
代表当前目录。甚至可以使用如下方法:
find . -mindepth 1 -maxdepth 1 -type f -print0 | xargs -0 grep text --
在这个例子中,可以限制递归的深度(或有效地禁用递归),同时避免通配符展开(尽管可以使用 -name
进行匹配)。这里的 -print0
和 -0
参数解决了一个相关问题:文件名可能包含换行符,因此用 NUL 字符进行分隔,因为 POSIX 规范下的 C 字符串接口无法处理 NUL 字符。
综上,shell 本身并没有漏洞,但在使用 shell 时出现这种错误是常见的(如省略 --
参数、在不必要的情况下使用通配符展开等)。
shell 是否可以做些改进以缓解此问题?
从不破坏兼容性的角度来看,我认为几乎无解。一个可能的改变是,shell 在展开通配符时为以 “-” 开头的文件名添加前缀“./”,但这可能仅限于某些情况,例如与内建的 echo
配合使用。我测试了许多 shell 是否已经采用这种策略(使用 /bin/echo *
),但发现没有任何 shell 默认执行此操作。我预估这种改动可能会导致一些脚本出现兼容性问题,例如文件名比对或将文件系统用作简易数据库(某些脚本会这样做)。或许未来可以引入一种可选模式,或开发不需完全兼容旧版 shell 的新 shell 来处理此问题,但即使如此,我也担心这种行为会在其他场景下导致不安全的结果。
以下为 distros 列表讨论的一些摘录:
2024年10月31日(周四),Alexander Hu 写道:
我测试过的所有 *nix shell 都具有以下行为:
创建一个文件夹(不是必须,但便于后续清理)
在文件夹中,使用自己喜欢的文本编辑器创建一个包含 "test" 字符串(不带引号)的文件,文件名需以
--
开头 (在我的情况下,我选择了--version
)使用
grep -lir "test" *
进行搜索 (或使用其他命令来查看效果)如果文件名不同,结果会更有趣。
对于某些网络托管公司的病毒扫描器来说,这也非常有趣。有些扫描器会因文件名特殊字符而停止工作,命令执行也因此中断。
2024年11月1日(周五),Solar Designer 回复道:
首先,感谢你尝试做正确的事,但:
不幸的是,你的消息不符合 distros 列表的目的和政策。我们要求未公开的可操作信息,并指定具体的公开披露日期/时间。你的消息描述了一个已知的(非)问题(见下文),且未提出任何披露建议。
既然此事已在 distros 列表中提及,我们也需要在 oss-security 中公开讨论以保证透明性。你是否希望自行处理,还是由 distros 列表的其他人负责?
2024年10月31日,Alexander Hu 写道:
所有我测试过的 *nix shell 都表现出这种行为:
这是一个已知的、正确的行为,虽然许多人会感到意外,确实也存在风险。许多程序(如 GNU grep)支持在选项和文件名之间使用
--
分隔符,此时可以使用如下安全的写法:grep whattofind -- *
2024年11月1日(周五),Solar Designer 继续写道:
2024年11月1日,Alexander Hu 回复:
这是描述一个漏洞的帖子,说明所有 *nix 系统上的 shell 会评估文件名,而不是列出它们
实际上,问题恰恰相反——shell 将文件名原样列出,而未做任何修改以防止程序对其误处理。理论上,shell 可以用
./--version
来替代--version
以缓解问题,但这会中度破坏兼容性,在其他场景中可能会带来意外的副作用。这对于隐藏恶意软件非常有趣;只需将文件内容改成 base64 编码,许多 web 扫描器在使用
*
展开时会搜索失败。这将是 web 扫描器的问题,源于不正确的 shell 和 grep 使用方法。
任何文件名,甚至带编码的
/
,会对其他命令如 find 或 rm 产生意想不到的效果。包含
/
的文件名在正常情况下无法创建,除非通过直接的块设备访问,这是一种文件系统损坏的表现。我同意,shell 脚本或许不够健壮,未能处理这样的情况,但让所有程序都去应对这种损坏风险似乎意义不大。文件创建时,系统已经限制了这种操作。如果 shell 能够直接列出文件,而不是将它们作为初始命令的选项来解析,这些问题就不会存在。
Shell 正在原样列出文件名,并不清楚字符串会被误当成选项。这是职责分离的问题。
报告漏洞的文本量非常多,我可能仍然没有正确处理...
是的,distros 列表的政策文本的确很长。我在几个月前做了些编辑来缩短文本,但无法进一步精简,否则会影响效果。如果有好的建议,欢迎提出。
免责声明
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本平台和发布者不为此承担任何责任。