旧版PHP 5.6项目中,大量历史代码使用字符串拼接方式构造SQL语句,例如把用户输入直接写到查询里,这种做法在今天看来极易引发SQL注入。若项目暂时无法升级PHP版本或重构框架,最务实的做法是为PHP 5.6安装mysqli扩展,并将原有拼接查询改写为参数化查询。本文从环境准备、扩展安装、代码改造与常见坑点几个方面,详细说明修复路径。

一、为什么选mysqli而不是继续用mysql函数
PHP 5.6时代,原有的mysql扩展(如mysql_query)已被官方标记为废弃,它不仅缺少参数化查询的原生支持,而且无法有效防御注入。mysqli扩展是mysql的增强版,支持面向对象和过程式两种接口,最关键的是提供了prepare预处理机制,可以从数据库协议层面隔离代码与数据。
对于旧项目而言,改用mysqli并不需要引入第三方依赖,只要服务器上编译或启用该扩展即可。相比PDO,mysqli在PHP 5.6默认源码包中更容易开启,且语法改动量相对较小,适合只做安全补丁、不做全量重构的场景。
二、在PHP 5.6环境中安装mysqli扩展
如果使用的是Linux包管理安装的PHP,可以直接通过系统包管理器补充扩展。以CentOS系为例,通常执行如下命令即可:
yum install php56w-mysqlnd # 或者基于remi源 yum install php56-php-mysqli service httpd restart
若是源码编译的PHP 5.6,需要在编译时加上参数,或者进入源码ext/mysqli目录动态编译。典型流程如下:
cd php-5.6.x/ext/mysqli phpize ./configure --with-php-config=/usr/local/php/bin/php-config --with-mysqli=mysqlnd make && make install # 然后在php.ini中增加 extension=mysqli.so
配置完成后,通过phpinfo页面或命令行php -m | grep mysqli确认扩展已加载。只有扩展真正启用,后面参数化代码才能运行,否则会出现类或函数未定义的致命错误。
三、把拼接SQL改写为参数化查询
假设旧代码中存在如下明显注入风险的写法,直接将用户名和密码拼进语句:
<?php
$link = mysql_connect('127.0.0.1', 'root', 'pass');
mysql_select_db('test', $link);
$user = $_POST['user'];
$pass = $_POST['pass'];
$sql = "SELECT * FROM users WHERE name='$user' AND pwd='$pass'";
$res = mysql_query($sql);
?>使用mysqli后,应建立连接并通过prepare创建模板,再用bind_param绑定变量。改写示例如下:
<?php
$mysqli = new mysqli('127.0.0.1', 'root', 'pass', 'test');
if ($mysqli->connect_error) {
die('连接失败: ' . $mysqli->connect_error);
}
$user = $_POST['user'];
$pass = $_POST['pass'];
$stmt = $mysqli->prepare("SELECT id,name FROM users WHERE name=? AND pwd=?");
$stmt->bind_param('ss', $user, $pass);
$stmt->execute();
$stmt->bind_result($id, $name);
while ($stmt->fetch()) {
echo $id . ' ' . $name;
}
$stmt->close();
$mysqli->close();
?>在上面的代码中,问号占位符告诉MySQL哪些是数据边界,bind_param的ss表示两个参数均为字符串类型。即使用户传入' OR '1'='1,数据库也只会把它当作普通字符串去匹配,而不会变更SQL逻辑结构。
过程式写法也类似,适合不愿改为面向对象风格的老文件:
<?php
$link = mysqli_connect('127.0.0.1', 'root', 'pass', 'test');
$stmt = mysqli_prepare($link, "SELECT id FROM users WHERE name=?");
mysqli_stmt_bind_param($stmt, 's', $_POST['user']);
mysqli_stmt_execute($stmt);
mysqli_stmt_bind_result($stmt, $uid);
mysqli_stmt_fetch($stmt);
echo $uid;
?>四、避免宽字节与配置导致的绕过
即便用了参数化,若系统使用GBK等宽字节字符集且未正确设置,仍可能出现历史遗留问题。建议在连接后统一设定字符集:
<?php
$mysqli->set_charset('utf8');
// 或使用过程式
mysqli_set_charset($link, 'utf8');
?>同时应在php.ini中关闭危险选项,例如magic_quotes_gpc在PHP 5.6虽默认关闭,但部分老环境可能开启,它会造成数据被反复转义而扰乱绑定逻辑。另外,不要为了兼容而使用addslashes再拼接,那和参数化是两套思路,混用反而易错。
五、改造后的验证方式
修复完成后,可用简单payload做黑盒验证。在登录框用户名处填写admin' -- ,若旧拼接写法会注释掉后续条件,而参数化版本应提示账号不存在或正常走绑定比对,不会返回非预期数据。
也可在测试环境开启MySQL通用日志,观察实际发送的语句。参数化查询在日志中通常显示为Execute prepared statement,且变量作为独立参数传输,而不是拼进原始文本。确认这一点,即说明注入通道已被切断。
六、小结与后续建议
通过给PHP 5.6安装mysqli扩展并全面替换拼接查询,旧项目能以最低成本消除最普遍的SQL注入面。这一步虽不解决所有安全问题,但堵住了拖库的主要入口。
若资源允许,下一步应规划PHP版本升级与PDO迁移,获得更统一的异常处理与多数据库支持。但在当下,先用mysqli把参数化落地,是兼顾稳定与安全的实用方案。