漏洞描述
命令注入是一种通过存在漏洞的应用程序向主机操作系统执行任意命令。当应用程序对用户提供的数据(表单,cookie,http头)不进行检测传递给系统shell,那么就有可能存在命令注入。在这种攻击中,攻击者提供的操作系统命令通常以易受攻击的应用程序的特权执行。主要原因是输入验证不足导致命令注入攻击。
漏洞示例
示例1
下面代码是UNIX命令cat
,通过将文件内容打印输出,不过这是可注入的:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv) {
char cat[] = "cat ";
char *command;
size_t commandLength;
commandLength = strlen(cat) + strlen(argv[1]) + 1;
command = (char *) malloc(commandLength);
strncpy(command, cat, commandLength);
strncat(command, argv[1], (commandLength - strlen(cat)) );
system(command);
return (0);
}
正常使用时,输出的时文件的内容:
$ ./catWrapper Story.txt
When last we left our heroes...
但是,如果我们在该行的末尾添加分号和另一个命令时,这个命令可以由cat执行,显然也执行了ls命令
$ ./catWrapper "Story.txt; ls"
When last we left our heroes...
Story.txt doubFree.c nullpointer.c
unstosig.c www* a.out*
format.c strlen.c useFree*
catWrapper* misnull.c strlength.c useFree.c
commandinjection.c nodefault.c trunc.c writeWhatWhere.c
如果catWrapper被设置为具有比标准用户更高的特权级别,则可以使用该更高特权执行任意命令。
示例2:
以下简单程序接受文件名作为命令行参数,并将文件的内容显示给用户。该程序安装了setuid root,因为它旨在用作学习工具,允许系统管理员在测试中检查特权系统文件,而无需修改它们或损坏系统。
int main(char* argc, char** argv) {
char cmd[CMD_MAX] = "/usr/bin/cat ";
strcat(cmd, argv[1]);
system(cmd);
}
因为程序以root权限运行,所以对system()的调用也以root权限执行。如果用户指定标准文件名,则该调用按预期工作。但是,如果攻击者传递了“; rm -rf /”
形式的字符串,那么对system()的调用由于缺少参数而无法执行cat,但是会递归地删除根分区的内容。
示例3
以下代码(有特权程序)使用环境变量$ APPHOME来确定应用程序的安装目录,然后在该目录中执行初始化脚本。
...
char* home=getenv("APPHOME");
char* cmd=(char*)malloc(strlen(home)+strlen(INITCMD));
if (cmd) {
strcpy(cmd,home);
strcat(cmd,INITCMD);
execl(cmd, NULL);
}
...
与示例2中一样,此示例中的代码允许攻击者使用应用程序提升特权执行任意命令。此示例中,攻击者可以修改环境变量$ APPHOME以指定包含恶意版本INITCMD的不同路径。由于程序不验证从环境读取的值,通过控制环境变量,攻击者可以欺骗应用程序运行恶意代码。
示例4
下面的代码来自一个基于网络的CGI工具,它允许用户更改它们的密码。NIS(网络信息服务)下的密码更新过程在/var/yp
目录下,注意到程序密码更新记录,因为它已经安装setuid root
程序调用make如下:
system("cd /var/yp && make &> /dev/null");
与前面的示例不同,此示例中的命令是硬编码的,因此攻击者无法控制传递给system()的参数。但是,由于程序没有指定make的绝对路径,并且在调用该命令之前不删除任何环境变量,攻击者可以修改它们的$ PATH变量来指向一个名为make的恶意二进制文件,并从shell提示符执行CGI脚本。而且由于程序已经安装了setuid root,所以程序以root权限运行。
环境在执行程序内的系统命令中扮演着重要的角色。像system()和exec()这样的函数使用调用它们的程序的环境,因此攻击者有可能利用这些调用的行为。
有很多网站会告诉你,Java的Runtime.exec和C的系统函数完全一样。这是不完全正确的。两者都允许你调用一个新的程序/进程。但是,C的系统函数将其参数传递给shell(/ bin / sh)进行解析,而Runtime.exec尝试将该字符串拆分为一个单词数组,然后执行数组中的第一个单词并将其余单词作为参数。Runtime.exec不会尝试在任何时候调用shell。关键的区别在于shell提供的很多可用于特殊的功能(使用“&”,“&&”,“|”,“||”等链接命令,重定向输入和输出)作为传递给第一个命令的参数,并可能导致语法错误,或作为无效参数被抛出。
示例5
以下简单的代码片段在Unix / Linux平台上易受OS命令注入的影响:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
char command[256];
if(argc != 2) {
printf("Error: Please enter a program to time!\n");
return -1;
}
memset(&command, 0, sizeof(command));
strcat(command, "time ./");
strcat(command, argv[1]);
system(command);
return 0;
}
示例6
以下PHP代码片段容易受到命令注入攻击:
<?php
print("Please specify the name of the file to delete");
print("<p>");
$file=$_GET['filename'];
system("rm $file");
?>
以下请求和响应是成功攻击的一个例子:
请求:
http://127.0.0.1/delete.php?filename=bob.txt;id
响应:
Please specify the name of the file to delete
uid=33(www-data) gid=33(www-data) groups=33(www-data)
控制输入:
Replace or Ban arguments with “;”
Other shell escapes available
Example:
– &&
– |
– ...
相关攻击
相关控制
理想情况下,开发人员应将现有的API用于他们的语言。 例如(Java):与使用Runtime.exec()发出’mail’命令不同,请使用位于javax.mail。*中的可用Java API。
如果没有这样可用的API存在,开发者应该清除所有输入中的恶意字符。 实施积极的安全模式将是最有效的。 通常,定义合法字符比非法字符要容易得多。