本文由红日安全成员: DYBOY 编写,如有不当,还望斧正。
前言
大家好,我们是红日安全-代码审计小组。最近我们小组正在做一个PHP代码审计的项目,供大家学习交流,我们给这个项目起了一个名字叫 PHP-Audit-Labs 。现在大家所看到的系列文章,属于项目 第一阶段 的内容,本阶段的内容题目均来自 PHP SECURITY CALENDAR 2017 。对于每一道题目,我们均给出对应的分析,并结合实际CMS进行解说。在文章的最后,我们还会留一道CTF题目,供大家练习,希望大家喜欢。下面是 第15篇 代码审计文章:
Day 1 Sleigh Ride
题目叫做滑雪橇,代码如下:
漏洞解析 :
这一关主要考察的是$_SERVER['PHP_SELF']
引发的一个任意网址跳转漏洞
首先,分析一下程序的运行
如果有
$_GET['redirect']
参数,那么就New
一个Redirect
对象,同时调用Redirect
对象的startRedirect
成员函数startRedirect
函数接受一个GET
类型的params
参数,然后在explode('/', $_SERVER['PHP_SELF'])
函数中,将$_SERVER['PHP_SELF']
得到的值,通过/
来生成一个$parts
数组。$baseFile
的值为$parts
数组的最后一个值$url
的值为$baseFile?http_build_query($params)
,其中的http_build_query()
函数就是一个将参数进行URL编码的一个操作,比如$params='test=123'
然后调用
setHeaders
函数,首先解码$url
参数,然后header()
函数直接跳转$url
$_SERVER['PHP']
存在的问题:
初看这个程序没什么问题,但是PHP自带的$_SERVER['PHP_SELF']
参数是可以控制的。其中PHP_SELF
指当前的页面绝对地址,比如我们的网站:http://www.test.com/redict/index.php
,那么PHP_SELF
就是/redict/index.php
。但有个小问题很多人没有注意到,当URL
是PATH_INFO
的时候,比如:http://www.test.com/redict/index.php/admin
,那么PHP_SELF
就是/redict/index.php/admin
也就是说,其实PHP_SELF
有一部分是我们可以控制的。
双编码问题:
URL本来是被浏览器编码过一次,服务器接收到来自浏览器URL请求的时候,会将URL解码一次,由于在程序中我们看到有urldecode()
函数存在,它会再次解码一次URL,此时双编码URL就可以利用,用于绕过某些关键词检测。比如将/
编码为:%252f
漏洞利用:
比如我们要跳转到我的博客:blog.dyboy.cn
,那么就可以构造Payload
:http://www.test.com/index.php/http:%252f%252fblog.dyboy.cn?redirect=test¶ms=test123
,访问即可重定向跳转到http://blog.dyboy.cn
网址
如下图所示,发生了302
跳转:
实例分析
其实关于这个漏洞的利用是有很多src,但是都是黑盒测试不是很清楚后台的代码怎么设计的,这里可以提及到一个关于360webscan的防护脚本一个历史漏洞,正是由于$_SERVER['PHP_SELF']
这个参数导致可以绕过360webscan防护脚本的防护,脚本的防护效果失效,现在此防护脚本更新了
最新版下载地址: http://webscan.360.cn/protect/down?domain=www.test.com
旧版本下载地址:https://www.lanzous.com/i1qj0qh
其结构为:
因为这只是一个防护的辅助脚本任何的程序都可以安装使用,这里就以Emlog5.3.1博客程序为例子,程序不重要,这个脚本可以安装接入到任何的程序中。
安装的方法:解压得到360safe
文件夹,上后上传到我们的网站根目录中,同时在任意的全局文件中加入如下代码即可安装成功:
在按照上述安装方法安装后,测试访问:http://www.test.com/index.php?test=<script>alert(1)</script>
XSS拦截显示:
比如GET类型存在SQL注入关键词的等都会被拦截
虽然本脚本的正则过滤规则很好了,但是通过这一个$_SERVER['PHP_SELF']
,可以通过白名单规则绕过攻击防护
在存在绕过漏洞的360webscan历史版本中,在194行至219行的的代码(拦截目录白名单检测):
在上图的第五行,我们看到$url_path
的值是直接取的$_server['PHP_SELF']
的值,同时没有做任何的验证或过滤
那么我们只要在请求的URL(提交的参数中)存在白名单目录,那么就可以绕过安全检测
因为在webscan_cache.php
中的默认的白名单目录存在admin
然后我们访问:http://www.test.com/index.php/admin?test=%3Cscript%3Ealert(1)%3C/script%3E
此处虽然返回的状态码是404
,但是,我们发现已经不再拦截了,如果再配合某些CMS或者PHP系统的伪静态特殊性,那么就可以成功的绕过防护
修复建议
本次审计的其实不是漏洞,主要是一个$_SERVER['PHP_SELF']
的问题,再遇上某系伪静态规则配合下,就会导致各种由此形成的各种漏洞
因此,这里推荐使用$_SERVER['SCRIPT_NAME']
代替即可,同时,我们可以看到在最新的360webscan中已经更新了这个问题,并且使用了$_SERVER['SCRIPT_NAME']
结语
看完了上述分析,不知道大家是否对 $_SERVER['PHP_SELF']
函数有了更加深入的理解,文中用到的emlog可以从 百度网盘 (密码: hkb4
) 下载,当然文中若有不当之处,还望各位斧正。如果你对我们的项目感兴趣,欢迎发送邮件到 hongrisec@gmail.com 联系我们。
Day15的分析文章就到这里,我们最后留了一道CTF题目给大家练手,题目如下:
index.php
文件:
<?php require('./waf.php'); $conn = mysql_connect('localhost', 'root', 'root') or die('bad!'); mysql_select_db('sql_inject', $conn) OR die("连接数据库失败,未找到您填写的数据库"); //执行sql语句 Measurement_url() $id = isset($_GET['id']) ? $_GET['id'] : 1; $sql = "SELECT user FROM sql_inject1 WHERE id='$id'"; $result = mysql_query($sql, $conn) or die(mysql_error()); ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>sql_inject</title> </head> <body> <?php if(isset($_GET['file'])) { if(is_file($_GET['file'])) die("Do you want to touzou my code?!"); else{ if(stripos($_GET['file'],'index')) die("you can only read waf.php!"); else { readfile($_GET['file']); exit(); } } } $row = mysql_fetch_array($result, MYSQL_ASSOC); echo "<h2>{$row['user']}</h2>\n"; mysql_free_result($result); ?> <!--waf.php--> <!--please give me a file..--> </body> </html>
waf.php
文件:
<?php error_reporting(0); $_GET=Add_S($_GET); $_POST=Add_S($_POST); $_COOKIE=Add_S($_COOKIE); $_REQUEST=Add_S($_REQUEST); function Add_S($array) { foreach($array as $key=>$value){ if(!is_array($value)){ $check= preg_match('/regexp|and|like|\"|%|insert|update|delete|union|into|load_file|outfile|\/\*/i', $value); if($check) { exit("Hacker!"); } }else{ $array[$key]=Add_S($array[$key]); } } return $array; } function Measurement_url() { $url=parse_url($_SERVER['REQUEST_URI']); parse_str($url['query'],$query); $Keyword=array("from","select","like","or"); foreach($query as $key) { foreach($Keyword as $value) { if(preg_match("/".$value."/",strtolower($key))) { die("fuck u!"); } } } } ?>
sql.sql
数据文件(MYSQL):
CREATE TABLE IF NOT EXISTS `sql_inject1`( `id` int auto_increment not null, `user` varchar(20) not null, `password` varchar(40) not null, primary key(`id`) )ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into sql_inject1(user,password) values('ur10ser','injectmeifucan'); insert into sql_inject1(user,password) values('admin','injectmeifucan'); insert into sql_inject1(user,password) values('Chabug','66666666666'); insert into sql_inject1(user,password) values('flag','flag{Parse_ur1_iz_not_safe2333333!}');
题解我们会阶段性放出,如果大家有什么好的解法,可以在文章底下留言,祝大家玩的愉快!
相关文章
版权声明:《 [红日安全]代码审计Day15 - $_SERVER['PHP_SELF']导致的防御失效问题 》为DYBOY原创文章,转载请注明出处!
最后编辑:2018-11-2 23:11:24
2020-12-26 01:48
2018-11-10 14:04
2018-12-01 16:21