正文
说明
最近在看P神之前分析的漏洞的文章,本篇文章就是记录看了P神的
贷齐乐系统最新版SQL注入
之后的一点体会和收获。
本篇文章主要是要说明的是在PHP中由于对于
$_REQUEST
的认识不足或者是使用不当而导致的漏洞。目前漏洞类型主要是有两种,对
$_REQUEST
认识不足从而导致过滤失效,使用不当则指的存在HPP的漏洞从而导致全局WAF失效,这个漏洞也就是在P神文章中所说明的漏洞。
$_REQUEST使用不当绕过WAF
php手册上面对于
$_REQUEST
的说法是:
由于 $_REQUEST 中的变量通过 GET,POST 和 COOKIE 输入机制传递给脚本文件,因此可以被远程用户篡改而并不可信
这话是什么意思呢?表示的是
$_REQUEST
是直接从GET,POST 和 COOKIE中取值,不是他们的引用。即使后续
GET,POST 和 COOKIE
发生了变化,也不会影响
$_REQUEST
的结果。如下:
foreach ($_REQUEST as $key=>$value) {
$_REQUEST[$key] = md5($value);
}
var_dump($_REQUEST);
var_dump($_GET);
我们访问
http://localhost/test/index.php?id=1&uname=spoock&[email protected]
的结果如下:
可以看到
$_REQUEST
的结果发生了改变,但是
$_GET
的结果并没有改变。
漏洞代码
假设如果存在如下的代码:
foreach ($_REQUEST as $key=>$value) {
$_REQUEST[$key] = waf($value);
}
if(isset($_POST['submit'])) {
$id = $_POST['id'];
$sql = "select * from user where id=$id";
mysql_query($sql);
//....
}
虽然使用了
waf
进行过滤,但是waf过滤的是
$_REQUEST
,在业务代码中使用的是
$_POST
。这样就导致前面的WAF过滤没有任何的作用,防护完全失效。
这个特性在ripstech中的2017年的题目中就出现过。这种问题我相信一定会存在。
REQUEST导致的HPP漏洞
PHP中的特性
在说明这个漏洞之前,我们先需要了解一下在PHP中的几个小的特性。
-
php自身在解析请求的时候,如果参数名字中包含空格、
.
、
[
这几个字符,会将他们转换成
_
。测试如下:
-
php在遇到相同参数时接受的是第二个参数。
-
通过
$_SERVER['REQUEST_URI']
方式获得的参数并不会进行转换。如:
基于以上三种PHP的特性,就可能会导致存在HPP漏洞,从而绕过系统防护。
漏洞代码
<?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;
}
// 经过第一个waf处理
foreach ($_REQUEST as $key => $value) {
$_REQUEST[$key] = dowith_sql($value);
}
// 经过第二个WAF处理
$request_uri = explode("?", $_SERVER['REQUEST_URI']);
var_dump($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['user_id'];
$sql = "select * from users where id=$user_id";
var_dump($sql);
}
-
第一个WAF,采用了
dowith_sql()
函数,如果
$_REQUEST
存在
select|insert|update|delete
等敏感关键字或者是字符,则直接exit()。如果不存在,则原字符串返回。
-
第二个WAF,通过
$_SERVER['REQUEST_URI']
得到请求参数,之后利用
explode("&", $request_uri[1])
得到每个参数,包括参数名和参数值。对每个参数值采用
dhtmlspecialchars()
过滤,对字符
& " < > ( )
都进行了替换。替换完毕之后重新得到
_REQUEST
。
-
在最后的业务处理中,通过
$_REQUEST
获取参数进行处理。
如果能够利用HPP漏洞的原理,在WAF对参数进行过滤时处理的是一个参数,但是在进入到业务中处理的是第二个参数,那么我们就能够绕过WAF了。用一个简单的例子进行说明: