编写安全的 Shell 脚本

作者:Dave Taylor

不要用马虎的脚本暴露你的系统!

虽然 Linux 桌面或服务器比典型的 Windows 设备更不易受到病毒和恶意软件的攻击,但互联网上没有哪个设备最终不会受到攻击。罪魁祸首可能是刻板印象中的书呆子在卧室里测试他或她的黑客技巧(想想电影《战争游戏》中的马修·布罗德里克或《黑客帝国》中的安吉丽娜·朱莉)。当然,也可能是有组织的军队、犯罪分子、恐怖分子或其他有资金支持的实体创建庞大的僵尸网络,或通过十几个重定向的攻击向量窃取数百万张信用卡。

无论如何,现代系统面临的威胁在 UNIX 开发的早期,甚至在 Linux 作为 UNIX 的业余爱好者重新实现的前几年都是无法想象的。啊,回到过去,当时最大的担忧是关于受版权保护的代码,因此有用的工具不断地从头开始重新实现,以摆脱 AT&T 贝尔实验室的许可等等。

我个人也有这方面的经验。当伯克利团队试图摆脱 AT&T UNIX 的法律纠纷时,我为 BSD 4.2 从头开始重写了猎杀 Wumpus 游戏 wumpus。我知道,这不是最伟大的成就,但我也设法在我的一生中拼凑出了一些其他实用程序。

然而,互联网的演变却倒退了。在现实生活中,无法无天的西部逐渐被驯服,守法的公民取代了 19 世纪 50 年代和淘金热中的歹徒和暴徒。在网上,似乎有比以往任何时候都更多、更聪明、组织得更好的数字歹徒。

这就是为什么学习如何编写 shell 脚本的最重要步骤之一是学习如何确保你的脚本是安全的——即使它只是你自己的家用电脑和你改装成基于 Linux 的媒体服务器(使用 Plex 或类似软件)的旧 PC。

让我们来看看一些基础知识。

了解你调用的实用程序

这是一个经典的特洛伊木马攻击:攻击者将一个名为 ls 的脚本放入 /tmp 中,它只是检查调用它的用户 ID,然后将其整个参数序列传递给真正的 /bin/ls。如果它识别出 userid = root,它会将 /bin/sh 的副本复制到 /tmp 中,并使用一个无害的名称,然后将其权限更改为 setuid root。

这非常容易编写。这是一个我信手拈来的版本


#!/bin/sh

if [ "$USER" = "root" ] ; then
  /bin/cp /bin/sh /tmp/.secretshell
  /bin/chown root /tmp/.secretshell
  /bin/chmod 4666 root /tmp/.secretshell
fi

exec /bin/ls $*

我希望你明白刚才发生了什么。这个简单的小脚本创建了一个 shell,它总是授予其用户对 Linux 系统的 root 访问权限。糟糕。更高级的版本会在 root shell 创建后删除自己,不留下任何痕迹。

因为讽刺应该是,嗯,讽刺的,我在上面演示了如何在小小的特洛伊木马脚本中避免这种危险。永远不要仅通过程序名称来调用程序;请确保包含它们的路径。一个包含 ls $HOME 的脚本是在自找麻烦,所以用 /bin/ls $HOME 来修复它。

另一个可能出现这种风险的有趣地方是你的 PATH 中。再次,想象一下你的 PATH 设置成这样


PATH=".:/bin:/usr/bin:$HOME/bin:/usr/local/bin"

95% 的情况下,这没问题,调用 lscp 甚至 date 都会像你期望的那样工作,因为它在你的 PATH 中的第一个目录中找不到,因此会级联到存储合法二进制文件的 /bin。但是,如果你碰巧在 /tmp 中调用命令会发生什么?在没有意识到的情况下,你实际上调用了特洛伊木马版本,并再次创建了 root shell(当然,如果你当时是 root 用户)。

解决方案:要么永远不要在你的 PATH 中将点(.)作为目录(我的建议),要么将其作为链中的最后一个条目,而不是第一个。

不要在脚本中存储密码

我承认,我在这方面做得不好,因为我有一些别名实际上会将密码推送到我的复制/粘贴缓冲区,然后调用 sshsftp 连接到远程计算机。这是一个愚蠢的解决方案,因为它不可避免地意味着我有一个 shell 脚本——或者在这种情况下是别名文件——其中包含如下行


PASSWORD="froBOZ69"

或者像这样


alias synth='echo secretpw | pbcopy; sftp adt@wsynth.net'

解决方案:就是不要这样做。如果你必须这样做,那么至少不要使用如此明显的(并且在扫描期间很容易识别)变量名。但真的,找一个替代实用程序来完成这项工作,这不值得冒安全风险。

小心调用用户输入的任何内容

这是一个微妙的问题,但在一个简单的序列中存在很大的安全风险,如下所示


echo -n "What file do you seek? "
read name
ls -l $name

如果用户输入一些恶意内容作为文件名,例如


. ; /bin/rm -Rf /

后果非常可怕,无论脚本是以 root 用户还是普通用户身份调用的。Bash 在你引用参数时会提供一定程度的保护,因此早期的序列可以通过更改为


/bin/ls -l "$name"

来保护。但是,如果它以 eval /bin/ls -l "$name" 的形式调用,则不适用。哦,还有臭名昭著的反引号。想象一下像这样的用户输入


. `/bin/rm -Rf /`

这也是一个有风险的,因为 `` 对是 $( ) 的懒人快捷方式,并且在命令行上遇到时会调用子 shell。ls 的调用也将由这样一个 shell 执行。

为了解决这个危险,如果你有理由相信你的脚本可能有恶意用户,请扫描和清理你的输入。简单的解决方案:如果遇到非字母数字字符或一小部分安全标点符号,则报错。

不要使用 Shell 脚本作为 CGI 脚本

运行 Linux Web 服务器并学习 CGI 脚本?使用 shell 脚本进行基本的 CGI 功能不仅很诱人,而且快速且容易。这是一个告诉你服务器负载的脚本


#!/bin/sh

echo "Content-type: text/html"; echo ""
echo "Uptime on the Server:<pre>"
uptime -a
echo "</pre>"

一旦你正确设置了权限,这将工作得很好。这不是很危险,但如果你想做一个类似于你网站的自制搜索系统呢?同样,任何时候你有一个脚本在未知用户的输入下运行,你都增加了一些主要的风险因素。

在这种情况下,解决方案是不要这样做。使用编译后的程序来实施安全和适当的安全性,或者只是使用像 Google Custom Search Engine 这样的第三方搜索系统以获得最大的安全性。

明智地编写代码

有很多理由喜欢在 Linux shell 中编程,其中最重要的一点是它快速且易于原型设计。但是,如果你真的要创建一个安全的计算环境,你需要从一开始就关注安全性,而不是事后才意识到你做了愚蠢的事情。

一些优秀的在线资源更深入地介绍了这些主题。查看 GitHub 上的 Shell 风格指南 以开始入门。此外,Apple 还有一个关于 shell 脚本安全性 的文档,也值得一读。

在外要小心!花一点额外的时间确保你的脚本免受主要的已知风险是值得的。

Dave Taylor 在 UNIX 和 Linux 系统上破解 shell 脚本已经很长时间了。他是 Learning Unix for Mac OS XWicked Cool Shell Scripts 的作者。你可以在 Twitter 上找到他,账号是 @DaveTaylor,你可以通过他的技术问答网站联系他:Ask Dave Taylor

加载 Disqus 评论