许多系统,例如命令行 shell 和 SQL 解释器,都有“元字符”,即它们输入中的字符不被解释为数据。 这些字符可能是命令,或者将数据与命令或其他数据分隔开。 如果您正在使用的系统接口有语言规范,那么它肯定有元字符。 如果您的程序调用了这些其他系统并允许攻击者插入此类元字符,通常的结果是攻击者可以完全控制您的程序。
最普遍的元字符问题之一是涉及 shell 元字符的问题。 标准的类 Unix 命令行 shell(存储在 /bin/sh 中)会特殊地解释许多字符。 如果这些字符被发送到 shell,那么除非转义,否则将使用它们的特殊解释; 这一事实可用于破坏程序。 根据 WWW 安全 FAQ [Stein 1999, Q37],这些元字符是
& ; ` ' \ " | * ? ~ < > ^ ( ) [ ] { } $ \n \r |
我应该注意到,在许多情况下,您还需要转义制表符和空格字符,因为它们(以及换行符)是默认的参数分隔符。 分隔符值可以通过设置 IFS 环境变量来更改,但是如果您不信任此变量的来源,您应该在环境变量处理过程中将其丢弃或重置。
不幸的是,在现实生活中,这不是一个完整的列表。 以下是一些其他可能存在问题的字符
'!' 在表达式中表示“非”(就像在 C 语言中一样); 如果测试程序的返回值,则前置 ! 可能会欺骗脚本,使其认为某事失败了,而实际上它成功了,反之亦然。 在某些 shell 中,“!” 也访问命令历史记录,这可能会导致实际问题。 在 bash 中,这仅在交互模式下发生,但 tcsh(在某些 Linux 发行版中发现的 csh 克隆)即使在脚本中也使用“!”。
'#' 是注释字符; 行上的所有后续文本都将被忽略。
'-' 可能会被误解为选项前导符(或者,如 -�-,禁用所有后续选项)。 即使它在文件名的“中间”,如果它前面是 shell 认为的空白字符,您也可能会遇到问题。
' ' (空格)、'\t' (制表符)、'\n' (换行符)、'\r' (回车符)、'\v' (垂直空格)、'\f' (换页符) 和其他空白字符可能会产生许多危险的影响。 例如,它们可能会将“单个”文件名变成多个参数,或者在存储时将单个参数变成多个参数。 换行符和回车符还有许多其他危险,例如,它们可以用于在某些程序中创建“欺骗性”日志条目,或者在单独的命令之前插入,然后执行该命令(如果底层协议使用换行符或回车符作为命令分隔符)。
其他控制字符(特别是 NIL)可能会给某些 shell 实现带来问题。
根据您的用法,甚至可以想象 ``.'' (“在当前 shell 中运行”)和 ``='' (用于设置变量)可能是令人担忧的字符。 但是,到目前为止,我发现的任何示例中,这些问题都存在其他(更糟糕的)安全问题。
shell 元字符尤其普遍的原因是,一些重要的库调用(例如 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 反引号 (`) 也调用命令行 shell; 有关 Perl 的更多信息,请参见 第 10.2 节。
由于 SQL 也具有元字符,因此类似的问题围绕着对 SQL 的调用展开。 当元字符作为输入提供以触发 SQL 元字符时,通常称为“SQL 注入”。 有关此方面的进一步讨论,请参见 SPI Dynamic 的论文“SQL 注入:您的 Web 应用程序是否易受攻击?”。 正如 第 5 章 中讨论的那样,定义一个非常有限的模式,并且只允许与该模式匹配的数据进入; 如果您将模式限制为 ^[0-9]$ 或 ^[0-9A-Za-z]*$,那么您就不会有问题。 如果您必须处理可能包含 SQL 元字符的数据,一个好的方法是在存储之前(尽早)将其转换为其他编码,例如 HTML 编码(在这种情况下,您还需要对任何 & 符号字符进行编码)。 此外,即使数据是数字,也要在所有用户输入的前面和后面加上引号; 这样,插入空格和其他类型的数据就不会那么危险。
忘记其中一个字符可能是灾难性的,例如,许多程序忽略了反斜杠作为 shell 元字符 [rfp 1999]。 正如 第 5 章 中讨论的那样,一些人推荐的方法是在输入时立即转义至少所有这些字符。 但是,再一次,迄今为止最好的方法是确定您希望允许哪些字符,并使用过滤器仅允许这些字符。
许多程序,尤其是那些为人类交互而设计的程序,都有执行“额外”活动的“转义”代码。 其中一种更常见(且危险)的转义代码是调出命令行的代码。 确保这些“转义”命令不能被包含在内(除非您确定特定命令是安全的)。 例如,许多面向行的邮件程序(例如 mail 或 mailx)使用波浪号 (~) 作为转义字符,然后可以使用它来发送许多命令。 因此,表面上无辜的命令(例如 ``mail admin < file-from-user'')可用于执行任意程序。 诸如 vi、emacs 和 ed 之类的交互式程序具有“转义”机制,允许用户从其会话中运行任意 shell 命令。 始终检查您调用的程序的文档,以搜索转义机制。 最好只调用旨在供其他程序使用的程序; 请参见 第 8.4 节。
避免转义码的问题甚至可以追溯到低级硬件组件及其模拟器。 大多数调制解调器都实现了所谓的“Hayes”命令集。 除非禁用命令集,否则引入延迟,短语“+++”,然后再延迟一段时间会迫使调制解调器将任何后续文本解释为发送给调制解调器的命令。 这可用于实施拒绝服务攻击(通过发送“ATH0”,挂断命令),甚至强制用户连接到其他人(复杂的攻击者可以通过攻击者控制下的机器重新路由用户的连接)。 对于调制解调器的特定情况,这很容易应对(例如,在调制解调器初始化字符串中添加“ATS2-255”),但一般问题仍然存在:如果您正在控制较低级别的组件或其模拟,请确保禁用或以其他方式处理内置于其中的任何转义码。
许多“终端”接口实现了古老的、早已消失的物理终端(如 VT100)的转义码。 这些代码可能很有用,例如,用于加粗字符、更改字体颜色或移动到终端界面中的特定位置。 但是,不要允许将任意不受信任的数据直接发送到终端屏幕,因为其中一些代码可能会导致严重问题。 在某些系统中,您可以重新映射键(例如,当用户按下“Enter”键或功能键时,它会发送您希望他们运行的命令)。 在某些系统中,您甚至可以发送代码来清除屏幕,显示一组您希望受害者运行的命令,然后将该组“发送回”,迫使受害者运行攻击者选择的命令,甚至无需等待按键。 这通常使用“页面模式缓冲”来实现。 这个安全问题就是为什么模拟的 tty(表示为设备文件,通常在 /dev/ 中)应该只能由其所有者写入,而不能由其他任何人写入的原因 - 它们永远不应设置“其他写入”权限,除非只有用户是该组的成员(即“用户私有组”方案),否则也不应为终端设置“组写入”权限 [Filipski 1986]。 如果您正在(模拟的)终端上向用户显示数据,您可能需要从发送回用户的数据中过滤掉所有控制字符(值小于 32 的字符),除非您将它们标识为安全字符。 最坏的情况是,您可以将制表符和换行符(以及可能的 carriage return)标识为安全字符,删除所有其余字符。 设置了高位的字符(即值大于 127 的字符)在某些方面更难处理; 一些旧系统将它们实现为好像没有设置一样,但是简单地过滤它们会抑制许多国际用途。 在这种情况下,您需要查看您具体的情况。
一个相关的问题是 NIL 字符(字符 0)可能会产生令人惊讶的影响。 大多数 C 和 C++ 函数都假设此字符标记字符串的结尾,但是其他语言(例如 Perl 和 Ada95)中的字符串处理例程可以处理包含 NIL 的字符串。 由于许多库和内核调用都使用 C 约定,因此结果是检查的内容与实际使用的内容不符 [rfp 1999]。
当调用另一个程序或引用文件时,始终指定其完整路径(例如,/usr/bin/sort)。 对于程序调用,即使 PATH 值设置不正确,这也会消除调用“错误”命令的可能错误。 对于其他文件引用,这减少了来自“错误”起始目录的问题。