前面我们完整演示了普通注入、高权限跨库注入、文件读写注入 的全流程,知道了注入的核心是 "用户输入未过滤直接拼接 SQL"。这一篇我们聚焦SQL 注入的基础防御,从 PHP 原生配置(魔术引号)到代码层面的类型校验、关键字过滤,逐一拆解每一种防御手段的原理、实操与坑点。
一、防御前置:被魔术引号 "坑" 过的注入防御
1.1 什么是魔术引号(Magic Quotes)?
大白话解释
PHP 的magic_quotes_gpc是一个自动转义 功能:当用户通过 GET/POST/Cookie 传入数据时,PHP 会自动给单引号'、双引号"、反斜杠\、NULL 字符 加上转义符\,避免特殊字符破坏 SQL 语句。
核心配置(php.ini)
; 开启(默认旧版本常为On)
magic_quotes_gpc = On
; 关闭(PHP7+已移除,推荐彻底关闭)
magic_quotes_gpc = Off
开启后的效果
输入' → 被转义为\';输入" → 被转义为\";输入\ → 被转义为\\。

1.2 魔术引号的 "防御失效" 与坑点
正常情况:无过滤时的读取文件
load_file('D:/d.txt')
页面回显文件内容chicx123456789,正常读取。
开启魔术引号后的 "路径破坏"
\是转义符,路径中的\会被转义为\\,导致路径错误:
load_file('D:\d\d.txt') → 被转义为 load_file('D:\\d\\d.txt')
页面报错,无法读取文件,看似 "防御成功"。
❌ 致命问题:PHP7 + 彻底移除魔术引号
PHP 官方在 PHP7 中删除了magic_quotes_gpc,现在的开发环境基本都是 PHP7+,依赖魔术引号等于自废武功。
核心结论:不要依赖魔术引号做防御,它只是 "临时补丁",既不全面也不兼容新版本。
二、代码层面防御 1:数据类型校验(is_int的坑与正确写法)
注入的核心是用户输入直接拼接 SQL ,数字型注入的最直接防御就是强制校验输入为整数 。
2.1 错误示范:直接用is_int判断
源码问题
$_GET['id']获取的参数默认是字符串类型 (即使输入1,类型也是string),直接用is_int判断会误判:
运行
if(isset($_GET['id'])){
$id = $_GET['id']; // 拿到的是string类型,哪怕输入1,类型也是string
if(is_int($id)){ // 直接判断,结果为false
// 执行SQL
}else{
echo '你的输入不是id!';
}
}
实操验证
访问?id=1,页面显示string你的输入不是id!,因为gettype($id)返回string,明明输入是数字却被拦截 ,这是典型的类型判断错误。
2.2 正确示范:类型校验 + 强制转换(最终防御方案)
核心逻辑
- 先判断输入是否为纯数字 (用
ctype_digit); - 拒绝非数字输入;
- 强制将输入转为
int类型,彻底消除注入风险。
正确代码
运行
if(isset($_GET['id'])){
$id = $_GET['id'];
// 1. 校验是否为纯数字(字符串形式的数字也会通过)
if(!ctype_digit($id)){
die('Invalid input: ID must be numeric');
}
// 2. 强制转为int类型,消除字符串风险
$id = (int)$id;
// 3. 后续拼接SQL,即使有注入字符也会被转为数字,无注入风险
echo "SELECT * FROM users WHERE id=$id LIMIT 0,1";
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
}

实操验证
- 输入
?id=1AVASD→ 触发ctype_digit拦截,输出Invalid input; - 输入
?id=1→ 转为int,正常执行 SQL,回显数据。
2.3 关键函数对比
| 函数 | 作用 | 适用场景 | 缺点 |
|---|---|---|---|
is_int($var) |
判断变量是否为 int 类型 | 校验已转换后的变量 | 对字符串形式的数字返回 false,直接用会误判 |
ctype_digit($str) |
判断字符串是否为纯数字 | 原始输入校验 | 仅判断是否为数字,不转换类型 |
(int)$str |
强制转为整数 | 类型转换 | 非数字字符串会被转为 0 |
三、代码层面防御 2:关键字过滤(str_replace的基础应用)
除了类型校验,还可以通过字符串替换 过滤注入关键字(如union/select/and等),破坏注入语句的完整性。
3.1 核心函数:str_replace
作用:替换字符串中的指定内容,语法:
str_replace(查找的字符串, 替换为的字符串, 原字符串)
实例(对应图片)
将Hello world!中的world替换为Shanghai:
echo str_replace("world","Shanghai","Hello world!");
// 输出:Hello Shanghai!
3.2 注入场景下的关键字过滤
防御逻辑
将注入核心关键字(如select/union)替换为无意义字符串,使注入语句语法错误。
实操代码(对应图片)
if(isset($_GET['id'])){
$id = $_GET['id'];
// 过滤select关键字,将其替换为mc
$id = str_replace('select','mc',$id);
// 拼接SQL
echo "SELECT * FROM users WHERE id=$id LIMIT 0,1";
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
}
注入测试与结果
- 注入语句:
?id=1%20union%20select%201,2,3 - 替换后:
id=1%20union%20mc%201,2,3 - 最终 SQL:
SELECT * FROM users WHERE id=1 union mc 1,2,3 LIMIT 0,1 - 页面结果:报语法错误 ,注入语句被破坏,防御成功。

❌ 局限性
单靠str_replace无法防御大小写绕过 (如Union/SeLeCt)、双写绕过 (如selselectect),必须结合其他防御手段。
四、综合防御:从 "被动转义" 到 "主动加固"
结合前面的所有知识点,整理SQL 注入的综合防御体系,覆盖从配置到代码的全链路。
4.1 配置层面加固(PHP+MySQL)
- 开启魔术引号 :
magic_quotes_gpc = ON(PHP7 + 默认已关闭); - MySQL 安全配置 :
- 关闭
load_file等文件读写函数(如需); - 严格设置
secure_file_priv=NULL,禁止文件读写注入。
- 关闭
4.2 代码层面核心防御(最有效!)
方案 1:参数化查询(PDO/MySQLi,推荐)
彻底避免 SQL 拼接,从根源消除注入风险,这是行业标准防御方案:
// PDO参数化查询示例
$pdo = new PDO("mysql:host=localhost;dbname=security", "root", "root");
$stmt = $pdo->prepare("SELECT * FROM users WHERE id=?");
$stmt->execute([$id]); // 输入会被自动转义,无需手动处理
$row = $stmt->fetch();
方案 2:手动加固(无框架场景)
结合类型校验 + 转义函数:
if(isset($_GET['id'])){
$id = $_GET['id'];
// 1. 数字型参数:纯数字校验+强制转int
if(!ctype_digit($id)){
die('非法输入');
}
$id = (int)$id;
// 2. 字符串型参数:使用mysql_real_escape_string转义
// $id = mysql_real_escape_string($id);
// 执行SQL...
}
4.3 辅助防御:WAF 与运维层面
- 部署 Web 应用防火墙(WAF) :拦截包含注入特征的请求(如
union/select/load_file); - 隐藏错误信息:关闭 PHP 报错,避免泄露网站绝对路径、SQL 语法;
- 最小权限原则:Web 应用数据库账号仅授予最小权限,不给 root/File 权限。
五、知识点归纳与避坑总结
5.1 常见防御手段对比
表格
| 防御手段 | 原理 | 有效性 | 适用场景 |
|---|---|---|---|
| 魔术引号(Magic Quotes) | 自动转义特殊字符 | ❌ 极低(PHP7 + 已移除) | 仅兼容 PHP5,不推荐使用 |
is_int直接判断 |
校验整数类型 | ❌ 无效(GET 参数默认 string) | 错误用法,需结合类型转换 |
ctype_digit+ 强制转 int |
纯数字校验 + 类型转换 | ✅ 高(数字型注入专用) | 数字型注入点的核心防御 |
str_replace关键字过滤 |
替换注入关键字 | ⭕ 中(易被绕过) | 辅助防御,需结合其他手段 |
| 参数化查询(PDO/MySQLi) | SQL 语句与参数分离 | ✅ 最高(行业标准) | 所有注入场景的终极防御 |
5.2 注入防御的核心逻辑
- 不相信用户输入:所有用户输入(GET/POST/COOKIE)都是 "不可信的";
- 分类防御 :
- 数字型参数:纯数字校验 + 强制转整数;
- 字符串型参数:转义特殊字符 + 参数化查询;
- 多层防御:代码层 + 配置层 + WAF 层,避免单一防御被绕过。
5.3 靶场实操复盘
- 魔术引号:开启会破坏路径,但 PHP7 + 已移除,不是可靠防御;
- 类型校验:
is_int是坑,必须用ctype_digit校验纯数字,再强制转int; - 关键字过滤:
str_replace能破坏基础注入,但无法防绕过,最终还是要靠参数化查询。
六、学习复盘
至此我们完整走完了 **SQL 注入从入门到实战(普通注入→高权限注入→文件读写→基础防御)** 的全流程:
- 注入篇 :搞懂了数字型 / 字符型注入的原理,
union/order by/load_file等核心函数的用法,以及跨库、写 Shell 的高权限攻击; - 防御篇 :搞懂了魔术引号的坑,
is_int/ctype_digit/str_replace等函数的正确用法,以及行业标准的参数化查询防御。
SQL 注入的核心是 **"输入拼接",而防御的核心是"拆分输入与 SQL"**:无论是参数化查询,还是类型校验 + 转义,本质都是让数据库 "把输入当成数据,而不是 SQL 命令"。
