php sql注入漏洞解决方法

一个老客户的注入商城后台在去年冬天被拖了库。对方用的漏洞不是多高深的手法,就是解决在搜索框里输入了 ' or 1=1 -- ,然后加个排序参数,注入订单表就全出来了。漏洞排查的解决时候发现,他那个PHP代码里全是注入拼接字符串的SQL,连最基本的漏洞转义都没做。

这事的解决根源其实特别简单:你让用户输入的数据直接变成了SQL代码的一部分。PHP本身不背这个锅,注入是漏洞写代码的人把信任全交给了外部输入。解决思路也就一条——把数据和代码彻底分开。解决

第一招:预处理语句(PDO或MySQLi)

这是注入最干净的办法。用PDO的漏洞预处理,SQL结构先发给数据库编译好,解决用户输入只是作为参数传进去。就算输入里带着OR 1=1,数据库也只会把它当字符串处理,不会执行。

$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?"); $stmt->execute([$_POST['email']]);

就这么两行,比你写十行正则过滤都管用。我见过很多老代码,明明连数据库用的还是mysql_*函数,那玩意PHP7就删了,还在用等于把脖子伸出去让人砍。

第二招:强制类型和字符白名单

不是所有查询都能用预处理,比如动态排序字段。排序的列名拼不进占位符,这时候就做白名单校验。我习惯写个数组,把允许的列名列出来,用in_array判断一下,不在列表里就直接用默认值。数字类型更简单,is_numeric或者ctype_digit过滤,不通过就报错。

$allowed = ['id', 'created_at', 'price']; $orderBy = in_array($_GET['sort'], $allowed) ? $_GET['sort'] : 'id';

这种写法看着笨,但绝对安全。别嫌麻烦,漏洞往往就出在那些“应该没人会这么传”的字段上。

第三招:别信任何“万能过滤”

网上流传的addslashes防注入教程早该扔进垃圾桶了。这函数对字符集攻击基本没用,遇到GBK这种多字节编码,一个%bf%27就能绕过。还有那些自己写的正则过滤黑名单,你永远猜不到攻击者下一个变形是什么样。安全不是靠堵,是靠隔离。

再说个实操细节。PHP的PDO默认不是真的用预处理,它模拟出来的,得把PDO::ATTR_EMULATE_PREPARES设为false,才能真正让数据库端做参数绑定。很多人配了PDO却忘了这步,等于白干。

最后补一刀:数据库权限分层

就算代码写漏了,数据库权限还能兜底。商城网站连接数据库的账号,只给SELECT、INSERT、UPDATE权限,DELETE和DROP全砍掉。有人通过注入拿到数据也就罢了,至少删不了你的表。我见过不少小站,一个root权限连到底,被注入了直接连load_file读服务器配置文件,那才是真完蛋。

修漏洞这事,别想着一步到位。先把最关键的入口都改成预处理,再检查所有动态拼接的地方。代码上线前用sqlmap扫一遍自己站,比什么承诺都实在。PHP这语言门槛低,但安全底线一点都不低。