Do not put your trust in princes, in mortal men, who cannot save. | |
Psalms 146:3 (NIV) |
要保证调用其它程序的出口只允许每个参数的合法而且期望的值。听起来不难,但实现起来就难得多了,因为有很多库调用或命令会以潜在的令人惊异的方式调用低级例程。例如,若干popen(3)和system(3)一类的系统调用通过调用命令shell来实现,也就是说,它们会受到shell转义字符的影响。同样,execlp(3)和execvp(3)也可能会调用shell。很多指南建议在产生一个进程时完全避免使用popen(3)、system(3)、execlp(3)和execvp(3),直接用C中的execve(3)[Galvin 1998b]。至少应该避免在可以使用execve(3)时使用system(3);因为system(3)使用shell来扩展字符,对于恶作剧者来说system(3)会提供更多机会。Perl和shell的backtick(`)也以同样的方式调用命令shell;参见Perl一节。
这个问题最令人头疼的例子就是shell转义字符。标准的类Unix命令shell(存储在/bin/sh)对许多字符有特别的解释。如果这些字符被传递给shell,那么除非被忽略,都将使用它们的特殊解释;这一事实可被用来破坏程序。按照WWW安全FAQ[Stein 1999, Q37],这些转义字符是:
& ; ` ' \ " | * ? ~ < > ^ ( ) [ ] { } $ \n \r |
不幸的是,这并非实际存在的所有转义字符。下面是可能出问题是一些其它字符:
“!” 在表达式里意味着“否”(就象在C语言里一样);如果程序的返回值被检测,预先考虑“!”会欺骗脚本在某些事情已经成功时以为它失败了,或是相反。在某些shell里,“ !”会访问命令的过去,这会导致真正的问题。在bash里,只有交互模式会出问题,但tcsh(某些Linux发行版里的csh的复制形式)甚至在脚本里都使用“!”。新的bash看来也用“ !”来访问命令的过去 -- 但可能只用于交互模式。
“#”是注释字符,随后的文本被忽略。
“-”会被误解为后面是一个选项(或者象“--”一样禁用其它选项)。甚至当它位于文件名的中间,或者前面存在被shell认为是空格的字符,都可能会出问题。
“ ”(空格)和其它空格字符会把某个“单独的”文件名变成多个参数。
其它控制字符(特别是NIL)会在某些shell的实现上产生问题。
按照应用的情况,甚至可以想象“.”(“在当前shell下运行”)和“=”(设置变量)都是令人担心的字符,而且目前找到的例子表明这些问题会带来更严重的安全问题。
忘记其中的某个字符会导致灾难性后果,例如,许多程序把反斜杠作为转义字符加以忽略[rfp 1999]。像在“证实输入”一节所讨论的那样,一个推荐方案是至少在输入时立刻忽略掉所有这些字符。同样,目前最好的解决方案是识别出希望允许的字符,并且只使用这些字符。
许多程序有执行“额外”操作的“逃逸”代码;要确保它们没有被包括在内(除非希望它们出现在消息里)。例如,许多面向行命令的邮件程序(如mail和mailx)使用代字符(~)作为逃逸字符,可以用来发送许多命令。结果,像“mail admin < file-from-user”这样看起来清白的命令就会被用来执行任意程序。vi和emacs一类的交互程序有“逃逸”机制,通常允许用户使用过程中执行任意的shell命令。应该无论如何都检查调用程序的文档来寻找逃逸机制。
避免逃逸代码的问题甚至涉及到底层硬件部件及其仿真器。大多数调制解调器实现了所谓的“Hayes”命令集,用“+++”序列、一个延迟再加上“+++”来强迫调制解调器切换模式(并把后面的文本作为命令解释)。这可以被用来实现拒绝服务攻击,甚或强迫用户与另一个人相连接。许多“终端”接口实现了VT100一类早已过时的老物理终端的逃逸代码。比如这些代码可以用来改变终端接口的字体颜色。尽管如此,不要允许直接向终端屏幕发送任意的不可信数据,因为某些代码会导致严重问题。在某些系统上可以重新映射按键;在若干系统上甚至可以发送代码来清除屏幕、显示一组希望受害者运行的命令以及把这些设置发“回去”(强迫受害者运行攻击者选择的命令)。
与之相关的一个问题是NIL字符(字符0)可能会有些出人意料的后果。大多数C和C++函数假设该字符标识字符串的结束,但其它语言(如Perl和Ada95)中的字符串处理例程可以处理包含NIL的字符串。由于很多库和内核调用使用的是C语言规范,结果就是被检查的内容并非实际所用到的[rfp 1999]。
在调用其它程序或引用某个文件时,应该总是指定其完整路径(如/usr/bin/sort)。对于程序调用,即使PATH值设置不正确,这也会排除调用“错误”的命令可能引起的错误。对于引用其它文件,这会减少“错误”的起始目录所带来的问题。