Command Injection(命令注入)
Abstract
执行不可信赖资源中的命令,或在不可信赖的环境中执行命令,都会导致程序以攻击者的名义执行恶意命令。
Explanation
Command Injection 漏洞主要表现为以下两种形式:
- 攻击者能够篡改程序执行的命令: 攻击者直接控制了所执行的命令。
- 攻击者能够篡改命令的执行环境: 攻击者间接地控制了所执行的命令。
在这种情况下,我们着重关注第一种情况,即攻击者控制所执行命令的可能性。 这种形式的 Command Injection 漏洞在以下情况下发生:
- 数据从不可信赖的数据源进入应用程序。
- 数据被用作代表应用程序所执行命令的字符串,或字符串的一部分。
- 通过命令的执行,应用程序会授予攻击者一种原本不该拥有的特权或能力。
例 1: 下面这段来自系统实用程序的代码根据系统属性 APPHOME 来决定其安装目录,然后根据指定目录的相对路径执行一个初始化脚本。
...
String home = System.getProperty("APPHOME");
String cmd = home + INITCMD;
java.lang.Runtime.getRuntime().exec(cmd);
...
例 1 中的代码使得攻击者可通过修改系统属性 APPHOME 而指向一个包含恶意版本 INITCMD 的其他路径,从而提高自己在应用程序中的权限,继而随心所欲地执行命令。 由于程序不会验证从环境中读取的值,所以如果攻击者能够控制系统属性 APPHOME 的值,他们就能欺骗应用程序去运行恶意代码从而取得系统控制权。例 2: 以下代码来自一个管理 Web 应用程序,该程序旨在允许用户通过使用围绕 rman 实用程序的批处理文件包启动 Oracle 数据库备份,然后运行 cleanup.bat 脚本删除一些临时文件。 脚本文件 rmanDB.bat 接受一个命令行参数,其中指明了需要执行的备份类型。 由于访问数据库受限,所以应用程序执行备份需要具有较高权限的用户。
...
String btype = request.getParameter("backuptype");
String cmd = new String("cmd.exe /K
"c:\util\rmanDB.bat "+btype+"&&c:\utl\cleanup.bat"")
System.Runtime.getRuntime().exec(cmd);
...
这里的问题是:程序没有对读取自用户的 backuptype 参数做任何验证。 通常情况下,一次调用Runtime.exec() 函数并不会执行多条命令,但是在本例中,程序首先运行了 cmd.exe 指令,进而在一次
调用 Runtime.exec() 后便可以运行多条命令了。 一旦调用了该 shell,它即会心甘情愿地执行用两个与号分隔的多条命令。 如果攻击者传递了一个形式为 "&& del c:dbms." 的字符串,那么应用程序将会在执行其他程序指定的命令时执行这些命令。 由于该应用程序的特性,运行该应用程序需要具备与数据库进行交互所需的权限,这就意味着攻击者注入的任何命令都将通过这些权限得以运行。 例 3: 以下代码来自一个 Web 应用程序。通过该应用程序,用户可以访问能够更新其系统密码的接口。 在特定的网络环境中更新密码时,其中的一个步骤就是在 /var/yp 目录中运行 make 命令,下面显示了此步骤的代码。
...
System.Runtime.getRuntime().exec("make");
...
这里的问题在于程序没有在它的构造中指定一个绝对路径,并且没能在执行Runtime.exec() 调用前清除它的环境变量。 如果攻击者能够修改 $PATH 变量,把它指向名为 make 恶意二进制代码,程序就会在其指定的环境下执行,然后加载该恶意二进制代码,而非原本期望的代码。 由于应用程序自身的特性,运行该应用程序需要具备执行系统操作所需的权限,这意味着攻击者会利用这些权限执行自己的 make,从而可能导致攻击者完全控制系统。 此种类来源于 Cigital Java Rulepack。 http://www.cigital.com/securitypack/
Recommendation
应当禁止用户直接控制由程序执行的命令。 在用户的输入会影响命令执行的情况下,应将用户输入限制为从预定的安全命令集合中进行选择。 如果输入中出现了恶意的内容,传递到命令执行函数的值将默认从安全命令集合中选择,或者程序将拒绝执行任何命令。 在需要将用户的输入用作程序命令中的参数时,由于合法的参数集合实在很大,或是难以跟踪,使得这个方法通常都不切实际。 开发者通常的做法是使用黑名单。 在输入之前,黑名单会有选择地拒绝或避免潜在的危险字符。 但是,任何一个定义不安全内容的列表都很可能是不完整的,并且会严重地依赖于执行命令的环境。 较好的方法是创建一份白名单,允许其中的字符出现在输入中,并且只接受完全由这些经认可的字符组成的输入。 攻击者可以通过修改程序运行命令的环境来间接控制这些命令的执行。 我们不应当完全信赖环境,还需采取预防措施,防止攻击者利用某些控制环境的手段进行攻击。 无论何时,只要有可能,都应由应用程序来控制命令,并使用绝对路径执行命令。 如果编译时尚不了解路径(如在跨平台应用程序中),应该在执行过程中利用可信赖的值构建一个绝对路径。 应对照一系列定义有效值的常量,仔细地检查从配置文件或者环境中读取的命令值和路径。 有时还可以执行其他检验,以检查这些来源是否已被恶意篡改。 例如,如果一个配置文件为可写,程序可能会拒绝运行。 如果能够预先得知有关要执行的二进制代码的信息,程序就会进行检测,以检验这个二进制代码的合法性。 如果一个二进制代码始终属于某个特定的用户,或者被指定了一组特定的访问权限,这些属性就会在执行二进制代码前通过程序进行检验。 尽管可能无法完全阻止强大的攻击者为了控制程序执行的命令而对系统进行的攻击,但只要程序执行外部命令,就务必使用最小授权原则: 不给予超过执行该命令所必需的权限。