唯一可靠防sql注入的方法是使用pdo::prepare()或mysqli::prepare()预处理语句,因其将sql结构与数据彻底分离;切勿手拼sql或依赖addslashes()、mysql_real_escape_string()等已废弃且不安全的函数。

mysql_query 本身不防注入,它只执行拼好的SQL字符串
mysql_query 是一个纯执行函数,它不做任何参数隔离或类型检查。你传给它的必须是完整、合法的SQL语句字符串——如果这个字符串里混入了用户输入且没处理,那恶意代码就直接进数据库了。
它不像 PDO::prepare 或 mysqli_prepare 那样把 SQL 结构和数据分开,也没有绑定参数机制。所有“过滤”“转义”都得靠开发者手动加,而一旦漏掉、写错、或遇到 LIKE / ORDER BY 等无法用引号包裹的上下文,就立刻失守。
- 它已被 PHP 官方废弃(自 7.0 起移除),连编译都不支持
- 不支持预处理,无法使用占位符(
?或:name) - 即使搭配
mysql_real_escape_string,也挡不住基于布尔/时间的盲注,更无法处理ORDER BY后的列名注入
为什么 mysql_real_escape_string 不能当“安全开关”用?
很多人以为只要调用了 mysql_real_escape_string 就万事大吉,其实它只解决单引号闭合问题,而且有严格前提:必须已建立 MySQL 连接,且字符集设置与数据库一致。否则转义失效,形同虚设。
更关键的是,它对非字符串上下文完全无效。比如下面这段看似“已转义”的代码:
$order = mysql_real_escape_string($_GET['sort']); $sql = "SELECT * FROM products ORDER BY $order";
攻击者传入 price ASC, (SELECT 1 FROM users WHERE username='admin')=1,照样能触发子查询——因为 mysql_real_escape_string 不会阻止逗号、括号、SELECT 关键字出现在非引号字段里。
遗留项目升级时最容易踩的坑
很多老项目从 mysql_* 切到 mysqli 或 PDO 时,只是机械替换函数名,比如把 mysql_query($sql) 改成 mysqli_query($conn, $sql),但没改拼接逻辑。结果还是在用字符串拼接构造 $sql,漏洞照旧。
- 别只换函数名,要重写查询逻辑:把变量全挪到
bind_param或execute()的参数数组里 -
mysqli的面向对象风格比过程式更易写出预处理结构,推荐优先用$mysqli->prepare() - 若必须动态列名或表名(如多租户分表),只能白名单校验,绝不可交给
real_escape_string或正则模糊匹配
现在还能看到 mysql_* 函数,说明项目已经停更多年
PHP 5.6 是最后一个支持 mysql_* 扩展的版本,2019 年就终止维护。今天还在跑这些函数的项目,大概率没打过安全补丁、没做过依赖审计、也没启用现代 PHP 的类型声明和 strict mode。这类系统往往还伴随 magic_quotes_gpc、register_globals 等早已淘汰的危险配置,修复不能只盯 SQL 注入,得整体评估技术债水位。
真正棘手的不是怎么改一行 mysql_query,而是改完之后,其他地方是否还藏着同样模式的拼接——比如日志记录、缓存 key 构造、甚至 shell_exec 的参数组装。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











