宽字节注入主因是character_set_client与php输入编码错位,需统一php文件、http请求、mysql连接三者编码为utf-8;禁用set names gbk,改用mysqli_set_charset()或pdo预编译,并校验输入编码。

宽字节注入在现代PHP项目里基本只出现在老系统或配置混乱的环境里,不是靠“加一层过滤”能解决的,核心是切断character_set_client和实际输入编码之间的错位链路。
为什么set names gbk会触发宽字节注入
当PHP执行set names gbk(或SET character_set_client = gbk)时,MySQL会把后续所有传入的SQL请求按GBK解析。而PHP内部字符串仍是UTF-8字节流——如果此时用户输入含%df%5c,MySQL会把这两个字节当作一个GBK汉字(運),导致%5c(即反斜杠\)被“吃掉”,绕过addslashes()的转义。
常见错误现象:
- 明明用了
addslashes(),单引号还是能闭合SQL -
mysql_real_escape_string()返回空或false,但没检查返回值 - 数据库连接后立刻执行
set names gbk,却没同步改PHP源文件编码
必须统一character_set_client与PHP文件编码
这不是“选一个就行”,而是三者必须严格一致:PHP源码文件编码、HTTP请求默认编码、MySQL连接层编码。否则character_set_client就成了攻击入口。
实操建议:
- PHP文件保存为UTF-8无BOM格式(编辑器可设,默认不带BOM)
- 连接MySQL时,用
mysqli_set_charset($conn, 'utf8mb4')替代set names utf8mb4(前者强制同步客户端/连接层/结果集三者) - 禁用
set names gbk、set character_set_client = gbk这类显式宽字节设置 - 检查
my.cnf中character_set_server是否为utf8mb4,避免服务端默认GBK
mysql_real_escape_string()已废弃,别再用
这个函数早在PHP 5.5.0就弃用了,7.0彻底移除。它依赖MySQL扩展(mysql_*),而该扩展本身就不支持UTF-8多字节字符(如emoji),且无法感知连接层字符集变化。
更危险的是:如果mysql_real_escape_string()失败(比如连接断开),它返回false,但很多老代码没判空,直接拼进SQL,反而制造新漏洞。
替代方案只有两个可靠选择:
- 用
PDO::prepare()+execute(),参数完全隔离,不拼接SQL - 用
mysqli::prepare(),同样走预编译路径 - 如果非得手动转义(极少数遗留场景),用
mysqli_real_escape_string(),且必须确保mysqli_set_charset()已成功调用
预编译语句不能代替字符集校验
有人以为用了PDO::prepare()就万事大吉,其实不然。如果PHP接收的原始输入本身就是GBK乱码(比如前端表单accept-charset="gbk"但PHP没做转换),那预编译只是安全地把乱码存进数据库——后续查出来还是乱码,业务逻辑可能出错,甚至触发二次漏洞(如JSON输出时编码崩塌)。
所以必须加一层输入编码兜底:
- 用
mb_check_encoding($_POST['name'], 'UTF-8')验证,不通过就拒收或抛异常 - 避免用
mb_detect_encoding()自动猜测,它对短字符串误判率高 - HTML页面强制声明
<meta charset="UTF-8">,并设置Content-Type: text/html; charset=UTF-8
真正麻烦的从来不是那行prepare()调用,而是从浏览器发出请求那一刻起,每个环节的编码都得有人盯着。漏掉任意一环,宽字节漏洞就可能以别的形式复活。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











