背景:
这个题目是今年某一期三个白帽的题目,看过之后很感兴趣,于是便还原出来,题目地址
分析:
上代码:
<?php
function waf($str) {
if(stripos(strtolower($str),"select")!==false)
die("Be a good person!");
if(stripos(strtolower($str),"union")!==false)
die("Be a good person!");
}
function wafArr($arr) {
foreach ($arr as $k => $v) {
waf($k);
waf($v);
}}
wafArr($_GET);
wafArr($_POST);
wafArr($_COOKIE);
wafArr($_SESSION);
function stripStr($str) {
if (get_magic_quotes_gpc())
$str = stripslashes($str);
$a=addslashes(htmlspecialchars($str, ENT_QUOTES, 'UTF-8'));
return $a;
}
$uri = explode("?",$_SERVER['REQUEST_URI']);
if(isset($uri[1])) {
$parameter = explode("&",$uri[1]);
foreach ($parameter as $k => $v) {
$v1 = explode("=",$v);
if (isset($v1[1])) {
$_REQUEST[$v1[0]] = stripStr($v1[1]);
}
}}
var_dump($_REQUEST);
$con=mysqli_connect("localhost","root","123","demo");
$result = mysqli_query($con,"SELECT * FROM ctf where user=".$_REQUEST['id'].";");
echo "</br>";
var_dump($row = mysqli_fetch_array($result));
highlight_file('index.php');?>
数据库结构
CREATE TABLE `ctf` (
`user` int(11) DEFAULT NULL,
`pass` varchar(255) DEFAULT NULL)
CREATE TABLE `ctf2` (
`user` int(11) DEFAULT NULL,
`flag is here` varchar(255) DEFAULT NULL)
下面分析一下waf的代码。首先,检查get,post,cookies,这些参数,在这里我只用了简单的字符串匹配。当然了,这块不是重点,这块我只是想强调get,post这些参数被过滤。
随后进入if 分支,也就是重组request了。首先使用?去分割url。根据规定,?前面是地址,后面是用户所提交的参数。并且将前面所得到的数组的第二个元素,也即是里面是参数的那个,再使用=分割,获取健值,并且将值使用stripStr函数过滤。过滤完成后,重组在系统的REQUEST数组中。
全部过滤好参数后,再执行数据库语句。
这个题主要考查的是http参数污染。也就是说同时提交许多参数一样的值,根据环境的不同,将会产生覆盖等问题。例如在本题中,如果提交的参数是?id=1?&id=2的话,apache默认会使用后面的值去覆盖前面的值。所以在php接收get参数的时候,将会接收到id=2,而不是id=1。
根据上面讲的,我们可以绕过第一个waf的检测。
第二个是重点,题目把所接收到的url(http://11.com/index.php?id=1)使用?分割,并且使用分割后数组的第二个元素去执行waf过滤。那么问题来了,如果我提交的字符串里面还有一个?呢?(http://11.com/index.php?id=1?&id=2)很简单,会分割成含有三个元素的数组,第一部分含有url地址,第二部分含有id=1,第三部分含有 &id=2,并且第三部分的数据并不会参与任何运算,而且还可以被识别成get所提交的参数。
所以我们可以提交url中参数为id=xxxxxx?id=1这样的参数去绕过waf检测,在if分支中,会使用url中的id=xxx这段代码去重组,并放入request数组中。看一下下面的数据库执行语句,使用的request数组。所以呢,我们只需要继续分析if分支的过滤代码即可。
分支中,值将会被addslashes
, htmlspecialchars
这两个函数过滤。第二个函数是转义html实体编码的,针对于空格,我们使用mysql的注释符号即可绕过/**/
。再看第一个函数。第一个函数会将单引号,双引号,反斜杠注释掉,去防止sql注入。那么是否意味着就没有sql注入了呢?再分析一下数据库和所执行的数据库语句,"SELECT * FROM ctf where user=".$_REQUEST['id'].";"
,而且ctf表的user字段类型是int,题目也恰好不需要单引号和双引号去闭合(参见绕过intval注入)。于是,直接注入即可,不需要考虑addslashes
函数。注意只要不出现单引号和双引号即可注入成功。
参见payload
https://ctf.f4ck0.com/ctf/?id=0/**/union/**/select/**/*/**/from/**/ctf2?&id=1
当然了,这个题要是想load_file怎么办呢,文件的路径名必须的用单引号啊。这里我们可以使用字符串转16进制,即可突破限制。
参见payload
https://ctf.f4ck0.com/ctf/?id=-1/**/union/**/select/**/1,LOAD_FILE(0x2f6574632f706173737764)?&id=1
后记
我在第一次看参考的那两位的wp时候并没有直接看懂,因为有点问题,说的比较笼统。尤其是这句话
当然waf不生效了,而在重组$_REQUEST
时,覆盖并没有发生,所以注入语句顺利的被执行
和
所以根本不需要怼waf,uri被处理后,GET
_REQUEST的值可以不一样,然后直接注入就好了
尤其困惑,而且看payload,也没说出个所以然来。于是根据博客中提供的代码,还原部分环境去本地测试。结果发现题目中好多巧合,才导致的addslashes
的绕过。其实如果这道题稍微做一下改动,两位表哥所分析的可能就不对了。所以我在这里写下详细的分析过程,帮助其他人学习。😄