SQL注入防御预编译,CMS程序代码改造
SQL注入防御预编译,CMS程序代码改造,核心就一句话:把用户输入和SQL语句结构分离,让数据库先编译再传参。
本文面向零基础站长,从环境确认到代码替换,再到最终验证,一步步带你完成改造,最终让CMS不再拼接用户输入到SQL里。
改造前先确认环境和CMS入口
开始动手前,先确认服务器上的PHP版本和数据库扩展。
预编译依赖PDO或MySQLi,老版本CMS可能还在用mysql_*函数,那套已经废弃且无法防注入。
登录服务器,执行:
php -v
php -m | grep -i pdo
php -m | grep -i mysqli
预期结果是PHP版本在7.0以上,且输出中包含pdo_mysql和mysqli。
如果没有,通过宝塔面板“软件商店”安装对应PHP扩展,或执行apt install php-mysql(Debian/Ubuntu)。
接着定位CMS的数据库操作文件。
常见CMS会把SQL查询封装在/include/db.class.php、/system/database.php或/libs/db.php。
用grep搜索拼接特征:
grep -rn "SELECT.*\$" /www/wwwroot/你的CMS目录 --include="*.php" | head -20
grep -rn "mysql_query\|mysqli_query" /www/wwwroot/你的CMS目录 --include="*.php" | head -20
如果结果里大量出现$sql = "SELECT * FROM users WHERE id = $id"这种写法,说明改造点就在这些文件。
将拼接SQL替换为PDO预编译
改造的核心动作是:用PDO的prepare()和execute()替代直接拼接。
下面以用户登录查询为例。
改造前典型代码:
$username = $_POST['username'];
$sql = "SELECT * FROM admin WHERE username = '$username'";
$result = mysql_query($sql);
改造后使用PDO:
$pdo = new PDO('mysql:host=127.0.0.1;dbname=cms;charset=utf8mb4', 'dbuser', 'dbpass');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare('SELECT * FROM admin WHERE username = :username');
$stmt->execute([':username' => $_POST['username']]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
注意:占位符用:username或?,参数通过execute()传入,绝不把变量写进SQL字符串。
如果CMS已有数据库连接类,优先改造该类中的query()方法,让它统一走预处理,这样改动面最小。
对于WHERE id IN (1,2,3)这类动态数量条件,不能直接绑一个数组,需要根据数组长度生成对应数量的占位符:
$ids = [1, 2, 3];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM news WHERE id IN ($placeholders)");
$stmt->execute($ids);
改造中容易踩的坑
第一,表名和字段名不能用占位符。
预编译只保护值,不保护SQL结构。
如果排序字段来自用户输入,必须用白名单校验:
$allowed = ['id', 'title', 'addtime'];
$order = in_array($_GET['order'], $allowed) ? $_GET['order'] : 'id';
$sql = "SELECT * FROM news ORDER BY $order";
第二,LIKE查询要处理好通配符。
不能写成LIKE '%:keyword%',正确做法是:
$stmt = $pdo->prepare('SELECT * FROM news WHERE title LIKE :kw');
$stmt->execute([':kw' => '%' . $keyword . '%']);
第三,老CMS可能同时存在多处数据库连接。
改造完一个文件不代表全部安全,需要全局搜索mysql_query和字符串拼接,逐文件替换。
第四,开启PHP错误显示会暴露路径。
改造期间建议在php.ini中设置display_errors = Off,避免报错信息被利用。
验证改造效果是否到位
改造完成后,用两种方式验证。
手工测试:在原来存在注入的输入框(如搜索框、文章ID参数)提交1' AND SLEEP(5)--,如果页面响应时间没有明显延迟,说明预编译生效。
日志检查:在MySQL中开启通用查询日志,观察实际执行的SQL是否还包含用户输入的原样拼接。
SET GLOBAL general_log = 'ON';
SHOW VARIABLES LIKE 'general_log_file';
查看日志文件,如果看到SELECT * FROM admin WHERE username = 'admin'这种带引号的值,且值来自参数绑定,说明改造正确。
也可以使用sqlmap对关键参数做一次扫描,确认不再报注入漏洞。
常见疑问
问:预编译能防御所有SQL注入吗?
不能。它防御的是值拼接类注入。如果SQL结构本身由用户输入决定(如表名、排序方向),仍需白名单校验。
问:CMS用了ORM框架还需要改造吗?
主流ORM默认使用参数绑定,但手写原生SQL的地方仍需检查。建议搜索项目中所有query()或execute()调用,确认参数是否绑定。
问:改造后网站变慢怎么办?
预编译通常比拼接SQL更快,因为数据库可以复用执行计划。如果感觉变慢,检查是否每次请求都新建PDO连接,建议复用连接或使用连接池。
改造完成后,建议保留一份改造前的代码备份,并在测试环境验证通过后再上线。
SQL注入防御预编译不是一次性的工作,后续新增功能也要坚持参数绑定,才能真正守住CMS的安全底线。