Command Injection
命令注入(Command Injection)是一种安全漏洞,命令注入攻击的目的是,在易受攻击的应用程序中注入和执行攻击者指定的命令。在这种情况下,执行不需要的系统命令的应用程序就像一个伪系统外壳,攻击者可以将其作为任何授权的系统用户使用。但是,执行命令的权限和环境与 web 服务相同。
在大多数情况下,由于缺少正确的输入数据验证,攻击者对例如表单、cookies、HTTP标头等进行操控,使得命令注入攻击成为可能。此攻击也称为“远程命令执行 (RCE)”。
通过 RCE 远程查找操作系统上 web 服务的用户以及机器主机名。
low
This allows for direct input into one of many PHP functions that will execute commands on the OS. It is possible to escape out of the designed command and executed unintentional actions. 这允许直接输入到许多将在操作系统上执行命令的 PHP 函数之一。通过在请求中添加内容,可以在成功执行命令后运行其他命令,从而可能逃离预设的命令并执行意外的操作。 This can be done by adding on to the request, "once the command has executed successfully, run this command". 这可以通过以下方式实现:“一旦命令成功执行,就运行这个命令”。
代码审计:
源码如下,php_uname(mode) 函数会返回运行php的操作系统的相关描述,当参数是 “s” 时操作系统名称。stristr() 函数搜索字符串在另一字符串中的第一次出现,返回字符串的剩余部分(从匹配点),未找到所搜索的字符串返回 FALSE。在这里使用者 2 个函数,目的是为了区分 DVWA 搭建在什么系统上,因为 Windows 系统和 Linux 的 Ping 还是有区别的。不过对于最重要的传入的 ping 命令的参数 target,并没有进行任何过滤。
<?php
2
3if (isset($_POST['Submit'])) {
4 // 检查是否存在表单 POST 提交(即提交按钮被点击)
5
6 // 获取用户输入的 IP 地址
7 $target = $_REQUEST['ip'];
8
9 // 根据操作系统类型执行不同的 ping 命令
10 if (stristr(php_uname('s'), 'Windows NT')) {
11 // 如果当前操作系统为 Windows NT
12
13 // 执行 ping 命令
14 $cmd = shell_exec('ping ' . $target);
15 // 将 ping 命令的结果存储在变量 $cmd 中
16 } else {
17 // 如果当前操作系统为类 Unix(*nix)系统
18
19 // 执行 ping 命令,并发送 4 个 ICMP 包
20 $cmd = shell_exec('ping -c 4 ' . $target);
21 }
22
23 // 将 ping 命令的结果输出到页面上,供用户查看
24 echo "<pre>{$cmd}</pre>";
25 // 使用 <pre> 标签将变量 $cmd 中存储的 ping 命令结果格式化后展示在 HTML 页面上
26}
27?>
渗透测试:可以看到这里直接将target 变量放入 shell_exec()执行ping命令,没有进行任何过滤,用户端可以直接拼接特定的命令,来执行并获取想要的信息。
在文本框中输入ping 127.0.0.1 & whoami 会与ping -c 4拼接在一起,然后当做命令执行。
这就给了我们很大的操作空间。
可以看到这里我加上 & whoami 后爆出了当前用户名。
这里只要有权限很多命令都是可以执行的,尤其是读取文件。
Medium
The developer has read up on some of the issues with command injection, and placed in various pattern patching to filter the input. However, this isn't enough.Various other system syntaxes can be used to break out of the desired command.
开发者已经阅读了一些关于命令注入问题的资料,并放置了各种模式修补来过滤输入。但是,这还不够。可以使用其他各种系统语法来突破预期的命令。
代码审计:
<?php
2
3// 检测表单中"Submit"按钮是否被点击
4if (isset($_POST['Submit'])) {
5
6 // 获取用户输入的 IP 地址
7 $target = $_REQUEST['ip'];
8
9 // 定义黑名单字符及替换规则
10 $substitutions = array(
11 '&&' => '',
12 ';' => '',
13 );
14
15 // 防止命令注入,过滤目标地址中的黑名单字符
16 $target = str_replace(array_keys($substitutions), $substitutions, $target);
17
18 // 根据操作系统类型执行不同的 ping 命令
19 if (stristr(php_uname('s'), 'Windows NT')) {
20 // 如果当前操作系统为 Windows
21 $cmd = shell_exec('ping ' . $target);
22 // 执行 Windows 系统下的 ping 命令并将结果赋值给变量 $cmd
23 } else {
24 // 如果当前操作系统为类 Unix(*nix)系统
25 $cmd = shell_exec('ping -c 4 ' . $target);
26 // 在 *nix 系统下执行发送 4 个 ICMP 包的 ping 命令并将结果赋值给变量 $cmd
27 }
28
29 // 将 ping 命令的结果格式化输出到 HTML 页面上
30 echo "<pre>{$cmd}</pre>";
31}
32?>
渗透测试:
这里命令行注入也是一种注入,所以和sql 注入与xss 一样,这里的防护手段主要是过滤,黑名单机制。
可以看到这里把&& 和; 替换成了'',所以这里也可以用&与| 、||。
我们可以看到不管ping是否执行成功,第二条命令都顺利执行了。
High
In the high level, the developer goes back to the drawing board and puts in even more pattern to match. But even this isn't enough.
在高级别中,开发者回到起点并添加了更多匹配模式。但即使这样也还不够。
The developer has either made a slight typo with the filters and believes a certain PHP command will save them from this mistake.
开发者可能在过滤器中犯了一个轻微的拼写错误,并认为某个 PHP 命令将拯救他们免于这个错误。
代码审计:
相比 Medium 级别的代码,High 级别的代码进一步完善了过滤的黑名单。
<?php
2
3// 检测表单中"Submit"按钮是否被点击
4if (isset($_POST['Submit'])) {
5
6 // 获取用户输入的 IP 地址并去除首尾空白字符
7 $target = trim($_REQUEST['ip']);
8 // 从请求中获取名为 "ip" 的参数,并通过 trim 函数去除首尾空白字符后赋值给变量 $target,用于存储要 ping 的目标地址。
9
10 // 定义黑名单字符及替换规则
11 $substitutions = array(
12 '&' => '',
13 ';' => '',
14 '| ' => '',
15 '-' => '',
16 '$' => '',
17 '(' => '',
18 ')' => '',
19 '`' => '',
20 '||' => '',
21 );
22 // 创建一个数组,包含可能用于命令注入的特殊字符,并将它们替换为空字符串以增强安全性。
23
24 // 删除目标地址中的黑名单字符
25 $target = str_replace(array_keys($substitutions), $substitutions, $target);
26 // 使用 str_replace() 函数过滤掉目标地址中的黑名单字符,防止潜在的命令注入攻击。
27
28 // 根据操作系统类型执行不同的 ping 命令
29 if (stristr(php_uname('s'), 'Windows NT')) {
30 // 如果当前操作系统为 Windows
31 $cmd = shell_exec('ping ' . $target);
32 // 执行 Windows 系统下的 ping 命令并将结果赋值给变量 $cmd
33 } else {
34 // 如果当前操作系统为类 Unix(*nix)系统
35 $cmd = shell_exec('ping -c 4 ' . $target);
36 // 在 *nix 系统下执行发送 4 个 ICMP 包的 ping 命令并将结果赋值给变量 $cmd
37 }
38
39 // 将 ping 命令的结果格式化输出到 HTML 页面上
40 echo "<pre>{$cmd}</pre>";
41}
42?>
渗透测试:
与上一个级别相比,这个代码增加了一些黑名单字符,包括 |、$、(、)、-、&和 ||。并且在用户输入被处理之前使用了 trim() 函数去除首尾空白字符,以避免目标地址包含不必要的空格。
看上去,似乎敏感的字符都被过滤了,但是 | 明显后面有个空格,所以如果不使用空格的话依然可以绕过,尝试127.0.0.1 |ipconfig 成功。
Lmpossible
In the impossible level, the challenge has been re-written, only to allow a very stricted input. If this doesn't match and doesn't produce a certain result, it will not be allowed to execute. Rather than "black listing" filtering (allowing any input and removing unwanted), this uses "white listing" (only allow certain values).
在无法实现的级别,挑战已被重新编写,只允许非常严格的输入。如果输入不匹配且未产生特定结果,则将不允许执行。这种方法不是使用“黑名单”过滤(允许任何输入并删除不需要的输入),而是使用“白名单”(只允许特定值)。
代码审计:
Impossible 级别的代码对参数 ip 进行了严格的限制,只有“数字.数字.数字.数字”的输入才会被接收执行, 加入了 Anti-CSRF token 进行进一步保障,使得命令注入漏洞不存在。
<?php
2
3if (isset($_POST['Submit'])) {
4 // 检查是否有名为 "Submit" 的表单提交按钮被点击。
5
6 // Check Anti-CSRF token
7 checkToken($_REQUEST['user_token'], $_SESSION['session_token'], 'index.php');
8 // 检查反跨站请求伪造(CSRF)保护机制的令牌,确保请求是合法的。
9 // 传递给 checkToken() 函数的参数分别是用户提交的令牌、存储在服务器端的令牌和重定向页面。
10
11 // Get input
12 $target = $_REQUEST['ip'];
13 $target = stripslashes($target);
14 // 获取用户输入的目标地址,并使用 stripslashes() 函数删除反斜杠以防止 SQL 注入攻击。
15
16 // Split the IP into 4 octets
17 $octet = explode(".", $target);
18 // 使用 explode() 函数将目标地址按照 "." 分割成 4 个字节。
19
20 // Check IF each octet is an integer
21 if (
22 (is_numeric($octet[0])) &&
23 (is_numeric($octet[1])) &&
24 (is_numeric($octet[2])) &&
25 (is_numeric($octet[3])) &&
26 (sizeof($octet) == 4)
27 ) {
28 // 如果目标地址的每个字节都是数字,并且有 4 个字节,则继续执行下一步操作。
29
30 // If all 4 octets are int's put the IP back together.
31 $target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];
32 // 将 4 个字节重新组合成目标地址。
33
34 // Determine OS and execute the ping command.
35 if (stristr(php_uname('s'), 'Windows NT')) {
36 // Windows
37 $cmd = shell_exec('ping ' . $target);
38 // 如果操作系统是 Windows,则使用 shell_exec() 函数执行 ping 命令,并将结果赋值给变量 $cmd。
39 } else {
40 // *nix
41 $cmd = shell_exec('ping -c 4 ' . $target);
42 // 如果操作系统不是 Windows,则使用 shell_exec() 函数执行 ping 命令,并指定发送 4 个 ICMP 回显请求,并将结果赋值给变量 $cmd。
43 }
44
45 // Feedback for the end user
46 echo "<pre>{$cmd}</pre>";
47 // 输出 ping 命令的结果到 HTML 页面上。
48 } else {
49 // 如果目标地址的每个字节不是数字,或者字节的数量不是 4,则输出错误信息到 HTML 页面上。
50 echo '<pre>ERROR: You have entered an invalid IP.</pre>';
51 }
52}
53
54// Generate Anti-CSRF token
55generateSessionToken();
56// 为反跨站点请求伪造(CSRF)保护机制生成新的令牌,并将其存储在 $_SESSION 数组中,以便后续检查。
57
58?>
总结与防御
在一些 Web 程序中需要调用一些执行系统命令的函数,例如 PHP 的 system、exec、shell_exec 函数等。当攻击者能够直接操作命令执行的参数,并且没有代码对传入的参数进行过滤时,攻击者就能将用于搞破坏的系统命令夹带进来让系统执行。 在 Windows 系统和 Linux 系统中都有一些管道符,这些管道符可以被用来拼接攻击指令:
“&&”:前面的语句正确执行时,才执行后面的语句;
“&”:前面的语句执行出错时,执行后面的语句;
“|”:将前面的语句运行的结果,当做后面语句的输入,显示后面语句的执行结果;
“||”:前面的语句出错时,执行后面的语句。
总的来说,对于命令注入漏洞,建议采取以下防御措施:
避免使用系统命令执行函数: 尽量避免使用 shell_exec、exec、system 等直接执行系统命令的函数,而是使用更安全的替代方案或者封装好的库。
输入验证和过滤: 对用户输入进行严格的验证和过滤,可以采用白名单验证或者黑名单过滤的方式,确保输入符合预期格式。
参数化查询: 如果需要执行外部命令,尽量使用参数化查询的方式,避免直接拼接用户输入到命令中。
最小权限原则: 在执行系统命令时,尽量以最小的权限执行,避免使用特权用户权限执行命令。