【Web安全】转义字符注入?转义也会失效的SQL注入
文章目录
- 一、明明转义了,为啥还能注入?
- 二、用代码还原漏洞过程
- 1. 有漏洞的代码示例
- 2. 漏洞一步步爆发
- 第一步:黑客的输入
- 第二步:PHP的转义操作
- 第三步:MySQL的"错读"
- 第四步:SQL被注入
- 三、这个漏洞的风险有多大?
- 四、怎么防御?
- 1. 用`set_charset`同步字符集(最关键)
- 2. 用预处理语句(彻底杜绝注入)
- 3. 别用废弃的扩展
- 五、总结
⚠️本博文所涉安全渗透测试技术、方法及案例,仅用于网络安全技术研究与合规性交流,旨在提升读者的安全防护意识与技术能力。任何个人或组织在使用相关内容前,必须获得目标网络 / 系统所有者的明确且书面授权,严禁用于未经授权的网络探测、漏洞利用、数据获取等非法行为。
一、明明转义了,为啥还能注入?
先看个场景:程序员为了防SQL注入,老老实实用了PHP官方推荐的转义函数,结果还是被黑客注入了。问题就出在"字符集"这个容易被忽略的细节上——PHP和MySQL用了不同的"翻译手册",转义的保护作用被悄悄抵消了。
二、用代码还原漏洞过程
咱们用一段具体代码,看看黑客是怎么得手的。
1. 有漏洞的代码示例
<?php
// 连接数据库(PHP默认用GBK字符集处理)
$conn = mysqli_connect('localhost', 'user', 'pass', 'testdb');
if (!$conn) {die('连接失败: ' . mysqli_error($conn));
}// 程序员只告诉MySQL用UTF-8,却没告诉PHP(关键错误!)
mysqli_query($conn, "set names utf8"); // 获取用户输入(假设黑客输入:testuser%81' or sleep(3)-- -)
$name = $_GET['name']; // 用转义函数处理(PHP以为还在用GBK,按GBK规则转义)
$safe_name = mysqli_real_escape_string($conn, $name); // 拼接SQL语句
$sql = "select * from users where name='$safe_name'";
echo "执行的SQL: " . $sql; // 方便查看最终SQL// 执行查询(这里会触发注入)
$result = mysqli_query($conn, $sql);
mysqli_close($conn);
?>
2. 漏洞一步步爆发
第一步:黑客的输入
黑客在URL里输入:info?name=testuser%81' or sleep(3)-- -
这里的%81
是关键,它对应计算机里的字节0x81
(十六进制)。在GBK字符集里,0x81
属于"双字节字符的前半部分"(可以理解为"半个字")——GBK规定,双字节字符的前半部分范围是0x81-0xFE
(就像字典里的"偏旁部首",需要和后面的"笔画"组成完整的字)。
类似的"半个字"还有0xDF
(也就是%df
)、0x90
(%90
)等,只要在0x81-0xFE
范围内,作用都一样,黑客随便选一个就行。
第二步:PHP的转义操作
PHP的mysqli_real_escape_string
按默认的GBK规则转义:
- 输入里的单引号
'
(对应字节0x27
)是特殊字符,转义函数会在它前面加一个反斜杠\
(对应字节0x5C
),把'
变成\'
(实际存储的字节是0x5C 0x27
)。
这里有个容易误解的点:转义后我们看到的%81\\'
里有两个反斜杠\\
,但这只是"显示效果"。实际存储的只有一个反斜杠字节0x5C
,之所以显示成\\
,是因为反斜杠\
本身在字符串里是"特殊符号",需要用\\
才能正确显示。
简单说:%81'
转义后,实际字节是0x81 0x5C 0x27
(对应%81\'
),但显示时会变成%81\\'
,方便我们看清"这里有一个反斜杠"。
第三步:MySQL的"错读"
因为程序员用了set names utf8
,MySQL会用UTF-8规则解读收到的数据。这时候问题来了:
- UTF-8对"双字节字符"有自己的规定:前半部分必须在
0xC0-0xDF
范围内,且后半部分必须≥0x80
(比如0xC2 0xA1
是有效的UTF-8字符)。 - 而我们的
0x81
(前半部分)不在UTF-8的范围内,后面的0x5C
(反斜杠,字节值92)也小于0x80
,所以0x81 0x5C
在UTF-8里是"无效的字符组合"。
MySQL遇到无效组合时,会把它们拆成单个字节:0x81
被当成一个无效字符(可能显示成?
),0x5C
(反斜杠)被单独拎出来——这时候反斜杠原本的"保护作用"(保护单引号不闭合)就没了。
第四步:SQL被注入
最终拼接的SQL变成:
select * from users where name='testuser%81\\' or sleep(3)-- -'
但MySQL用UTF-8解读后,实际效果是:
select * from users where name='testuser?\' or sleep(3)-- -'
这里的单引号'
因为失去了反斜杠的保护,直接"打断"了前面的字符串,导致or sleep(3)-- -
变成有效的SQL命令(-- -
会注释掉后面多余的单引号)。于是MySQL执行sleep(3)
,页面卡顿3秒,黑客就知道注入成功了。
三、这个漏洞的风险有多大?
- 数据泄露:黑客可以通过注入获取数据库里的账号、密码、个人信息等;
- 数据篡改:修改、删除数据库中的数据(比如删订单、改余额);
- 服务器控制:如果数据库权限高,可能通过注入执行系统命令,控制整个服务器。
更危险的是,它利用了"官方推荐的转义函数",容易让程序员放松警惕,以为代码是安全的。
四、怎么防御?
核心原则:让PHP和MySQL用同一套"翻译手册"(字符集),并从根源上避免拼接SQL。
1. 用set_charset
同步字符集(最关键)
不要用mysqli_query("set names utf8")
,改用mysqli_set_charset
,它会同时告诉PHP和MySQL用相同的字符集:
// 正确做法:同步字符集
$conn = mysqli_connect('localhost', 'user', 'pass', 'testdb');
mysqli_set_charset($conn, 'utf8'); // 同时更新PHP和MySQL的字符集
这样转义函数和MySQL的解读规则一致,反斜杠不会失效。
2. 用预处理语句(彻底杜绝注入)
最安全的方式是用"预处理语句",把SQL结构和用户数据分开,让黑客的输入永远当不了命令:
// 预处理示例(推荐!)
$conn = mysqli_connect('localhost', 'user', 'pass', 'testdb');
mysqli_set_charset($conn, 'utf8');$name = $_GET['name'];
// 准备带占位符的SQL
$stmt = mysqli_prepare($conn, "select * from users where name=?");
// 绑定参数(s表示字符串类型)
mysqli_stmt_bind_param($stmt, 's', $name);
// 执行查询
mysqli_stmt_execute($stmt);
预处理语句里,?
会被严格当作数据处理,不管输入什么特殊字符,都不会变成SQL命令。
3. 别用废弃的扩展
像mysql_real_escape_string
(属于早已废弃的mysql
扩展)完全不能用,必须用mysqli
或PDO
扩展。
五、总结
转义函数不是万能的,它的安全依赖于PHP和MySQL的字符集一致。如果两边"翻译错了",哪怕是0x81
、0xDF
这样的"半个字",都能让反斜杠的保护失效,导致注入。
同步字符集是基础,预处理语句是王道——这两个做到了,绝大多数SQL注入都能防住。