安全矩阵

 找回密码
 立即注册
搜索
查看: 942|回复: 0

命令注入成因小谈

[复制链接]

417

主题

417

帖子

2391

积分

金牌会员

Rank: 6Rank: 6

积分
2391
发表于 2023-12-7 21:53:41 | 显示全部楼层 |阅读模式
衡阳信安 2023-12-04 19:00 发表于湖南

最近做测试的时候,发现不同平台/语言下命令注入的结果会有一定的不同,于是尝试对命令注入的成因做了一个更细致的探索。
语言实现PHP
[color=rgba(0, 0, 0, 0.9)]PHP命令执行的函数很多,其中大部分函数的实现都在 php-src/ext/standard/exec.c 中:
·     system
·     exec
·     passthru
·     proc_open
·     shell_exec
[color=rgba(0, 0, 0, 0.9)]其中 system / exec/ passthru 的实现调用链都是 php_exec_ex -> php_exec -> VCWD_POPEN 。
[color=rgba(0, 0, 0, 0.9)]proc_open 的实现在 php-src/ext/standard/proc_open.c 中,调用 execle / execl 执行 /bin/sh -c 。
[color=rgba(0, 0, 0, 0.9)]popen的实现在 php-src/ext/standard/file.c 中,和 shell_exec 一样直接调用 VCWD_POPEN 。
[color=rgba(0, 0, 0, 0.9)]pcntl_exec 的实现在 php-src/ext/pcntl/pcntl.c 中,调用 execve 执行 /bin/sh -c。
[color=rgba(0, 0, 0, 0.9)]而 VCWD_POPEN 定义在 Zend\zend_virtual_cwd.h 中,根据编译配置有两种实现:


  1. #define VCWD_POPEN(command, type) virtual_popen(command, type)
  2. // or
  3. #define VCWD_POPEN(command, type) popen(command, type)
复制代码
其中 virtual_popen 在 Zend/zend_virtual_cwd.c 中
  1. CWD_API FILE *virtual_popen(const char *command, const char *type) /* {{{ */
  2. {
  3.     return popen_ex(command, type, CWDG(cwd).cwd, NULL);
  4. }
复制代码


所以PHP中的命令执行大都只是对popen做了不同程度和形式的封装。
[color=rgba(0, 0, 0, 0.9)]可以用下面这个小demo来做简单验证:
  1. <?php

  2. system('id');
  3. echo exec('id');
  4. passthru('id');
  5. echo shell_exec('id');

  6. $descriptorspec = array(
  7.    0 => array("pipe", "r"),
  8.    1 => array("pipe", "w"),
  9.    2 => array("pipe", "w")
  10. );

  11. $process = proc_open('id', $descriptorspec, $pipes);

  12. if (is_resource($process)) {
  13.     echo stream_get_contents($pipes[1]);
  14.     fclose($pipes[1]);
  15.     $return = proc_close($process);
  16. }

  17. $handle = popen('/usr/bin/id 2>&1', 'r');
  18. $read = fread($handle, 4096);
  19. echo $read;
  20. pclose($handle);

  21. pcntl_exec('/usr/bin/id');
复制代码

执行 strace -f -e trace=execve php 1.php 可以看到6次 execve("/bin/sh",["sh", "-c"... 的调用。
Java
[color=rgba(0, 0, 0, 0.9)]Java执行系统命令使用 Runtime.getRuntime().exec 。JDK实现 Runtime.getRuntime().exec 实际调用了 ProcessBuilder ,而后 ProcessBuilder 调用 ProcessImpl 使用系统调用 vfork ,把所有参数直接传递至 execve 。
[color=rgba(0, 0, 0, 0.9)]但是和PHP不同的是,Java并没有调用 popen ,也没有给 execve 传入 sh -c ,而是直接把参数传递至 execve 。
[color=rgba(0, 0, 0, 0.9)]一个简单的demo如下
  1. import java.io.*;

  2. public class Main {
  3.     public static void main(String[] args) {
  4.         String [] cmd={"/usr/bin/id", "&&", "/usr/bin/id"};
  5.         try {
  6.             Process proc = Runtime.getRuntime().exec(cmd);
  7.             InputStream fis = proc.getInputStream();
  8.             InputStreamReader isr = new InputStreamReader(fis);
  9.             BufferedReader br = new BufferedReader(isr);
  10.             String line = null;
  11.             while((line=br.readLine()) != null)   
  12.             {   
  13.                 System.out.println(line);   
  14.             }   
  15.         } catch (IOException e) {
  16.             e.printStackTrace();
  17.         }
  18.     }
  19. }
复制代码

strace -f -e vfork,execve java Main 跟踪可以看到上面的Java代码在Linux中调用为
  1. execve("/usr/bin/id", ["/usr/bin/id", "&&", "/usr/bin/id"], [/* 22 vars */]
复制代码

Python

跟踪程序的具体实现需要相对多的精力和时间,有一个相对简单的方式是直接使用 strace 跟踪,例如Python常用的调用是 os.systemsubprocess.check_output
  1. import os
  2. import subprocess

  3. os.system('id')
  4. subprocess.check_output('id')
复制代码


那么执行 strace -f -e vfork,execve python t.py ,可以发现 system 对应 execve("/bin/sh", ["sh", "-c","id"] ,而 subprocess.check_output 对应 execve("/usr/bin/id"


系统调用
简单对语言实现层面做了一个了解后,不难看出,大部分语言在Linux平台下,系统调用会调用 popen ,而Windows平台下则是 CreateProcess ,而 popen 和 CreateProcess 的实现则是造成不同场景下命令注入有轻微不同的原因。
popen
popen是libc中的函数,当前版本的glibc中,popen的实现在 libio/iopopen.c 中的 _IO_new_popen 函数。这个函数的调用链是: _IO_new_popen -> _IO_new_proc_open -> spawn_process ->__posix_spawn 最后调用了 sh -c 。 sh -c 会将之后传入的字符串当作命令执行,所以在没有做过滤的情况下,PHP的系统调用,Python的 os.system 都会出现问题。
这里有一个细节是,sh会指向 /bin/sh ,在当前版本的Linux中,/bin/sh 默认指向 /bin/dash 。 dash 编译文件的大小大概在150k左右,而我们常用的 bash 大小在 1000k 左右,很直觉的,bash 的功能会更丰富一些。
例如 dash 中没有function 关键字支持,不支持 here string,不支持 数组,dash 不支持 ++ 操作符等,这会在一些利用了bash特性的复杂payload中造成一些不同。
还有一点需要注意的是虽然 Windows 中也提供了 _popen 作为POSIX的兼容,但是在Windows平台中, _popen 最后调用的是 CreateProcess ,因而与 Linux 平台下的表现有一定的出入。
CreateProcess
上面提到在Windows中,调用链的底层会最后创建进程使用的是 CreateProcess ,在普通情况下,这个函数即使不转义也不会出现问题,但是根据Windows文档,当 CreateProcess 中的第一个参数为 bat 文件或是cmd 文件时,会调用 cmd.exe 。所以当调用的参数第一个是bat文件时,会变成类似 cmd.exe /c "test.bat &id" 的调用,这样就会和 sh -c 一样引入了命令注入的潜在可能。
例如 String [] cmd={"whoami", "&","whoami"}; 并不会正确执行,这里相当于把 & 作为了 whoami 的参数传递了。而 String []cmd={"sth.bat", "&", "whoami","&", "whoami"}; 则会执行两次 whoami 。
还有一个需要注意的特性是,和Linux不同,Windows在处理参数方面有一个特性,如果这里只加上简单的转义还是可能被绕过。例如 <?php system('dir "\"&whoami"'); 在Linux中会报错,而在Windows会执行 dir 和 whoami 两条命令。
这是因为Windows在处理命令行参数时,会将 " 中的内容拷贝为下一个参数,直到命令行结束或者遇到下一个 " ,但是对 \" 的处理有误。因此在调用批处理或者cmd文件时,需要做合适的参数检查才能避免漏洞出现。
参考链接
·      git://git.kernel.org/pub/scm/utils/dash/dash.git
来源:https://xz.aliyun.com/感谢【lyle


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-12-1 00:52 , Processed in 0.013384 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表