最可靠的方式是全程使用参数化查询,其他手段都是辅助或兜底;必须用pdo::prepare()或mysqli_prepare(),禁用字符串拼接,动态表名列名须白名单校验,数据库账号权限最小化,错误信息不暴露。

最可靠的方式是全程使用参数化查询,其他手段都是辅助或兜底。任何试图靠字符串过滤、关键词替换来防注入的做法,在真实攻击面前基本形同虚设。
PHP 中必须用 PDO::prepare() 或 mysqli_prepare()
直接拼接 $sql = "SELECT * FROM users WHERE id = $id" 是高危写法,哪怕加了 intval() 也不能覆盖所有场景(比如字符串字段、LIKE 查询)。PDO 和 mysqli 的预处理接口才是正解:
-
PDO推荐用命名参数:$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :name AND status = :status"); $stmt->execute(['name' => $user, 'status' => $status]); -
mysqli用问号占位:$stmt = $mysqli->prepare("SELECT email FROM users WHERE id = ?"); $stmt->bind_param("i", $id); $stmt->execute(); - 注意:
mysql_real_escape_string()已废弃且不安全,别再用;addslashes()完全无效,绕过方式太多
动态表名/列名必须走白名单校验
参数化查询对表名、列名、ORDER BY 字段不起作用。这些地方一旦需要动态拼接,就必须硬编码限定范围:
- 例如排序字段只允许
['id', 'created_at', 'name'],收到$_GET['sort']后先检查是否在该数组中,不在就拒绝或默认id - 表名同理,不能靠
str_replace()过滤点符号或反引号——攻击者可用 Unicode 零宽字符、多字节编码绕过 - 不要写
"SELECT * FROM {$table} WHERE ...",哪怕你“觉得”这个变量可控
数据库账号权限要卡死到最小
即使代码里漏了一处拼接,权限收紧也能拦住大部分破坏性操作:
- Web 应用连接数据库的账号,原则上只应有
SELECT、INSERT、UPDATE、DELETE权限,禁用DROP、CREATE、ALTER、LOAD_FILE、INTO OUTFILE - 不同模块用不同账号:读库用只读账号,后台管理用带写权限的账号,两者完全隔离
- 避免用
root或sa连接生产数据库,这类账号被攻破等于直接交出服务器控制权
错误信息绝不暴露给前端
开发环境可以开 display_errors,但上线必须关掉,否则 MySQL server version X.X.X、Unknown column 'xxx' in 'where clause' 这类提示会帮攻击者快速试探结构:
- 设置
display_errors = Off,log_errors = On,错误记进日志而非页面 - 自定义错误页返回统一提示,如“请求处理失败”,不区分是参数错、SQL 错还是超时
- ORM 如 Laravel Eloquent 默认不暴露 SQL,但若用了
DB::raw()或原生查询,仍需人工确保参数化
真正难防的不是单点漏洞,而是混合场景:比如前端传来的 JSON 字段里嵌套了 SQL 片段,后端又用 json_decode() 解析后直接拼进查询。这种链路里的任意一环松动,整条防线就垮了。防注入不是加个函数的事,是每个涉及用户输入与 SQL 交互的地方,都要主动确认“这里有没有可能被当代码执行”。










