PreparedStatement能防删库清空表,因其预编译固定SQL结构,用户输入仅作纯数据绑定,不参与语法解析;即使传入"admin' OR '1'='1'; DROP TABLE users; --",也只被当字符串值处理。

直接用参数化查询,99% 的删库、清空表、拖库风险就断了。其他手段——比如过滤、转义、ORM包装——都是辅助或兜底,不能替代它。
为什么 PreparedStatement 和 bindValue() 能真正防删库
因为数据库在执行前就把 SQL 语句结构固定死了,用户输入只作为“值”传进去,不会参与语法解析。哪怕你传入 "admin' OR '1'='1'; DROP TABLE users; --",它也只会被当成 username 字段的一个字符串值,绝不会变成两条独立语句。
-
DROP TABLE、TRUNCATE、UNION SELECT这些关键字,在参数化场景下根本没机会被识别为指令 - MySQL 默认不支持多语句执行(
multipleStatements=true关闭时),所以拼接的分号毫无意义 - PostgreSQL/Npgsql 或 SQL Server 的参数绑定同样隔离数据与语法,机制一致,只是占位符写法不同(
:name或@param)
WHERE 条件里拼接用户输入是最高危操作
登录、搜索、详情页 ID 查询这些场景,最容易写出 "WHERE id = '" + input + "'" 这种代码。一旦放行,攻击者输 1' OR '1'='1 就能绕过条件,输 1'; DROP TABLE orders; -- 就可能直接删表(如果驱动开了多语句且权限过高)。
- 所有 WHERE 子句中的值,必须进参数,包括数字型 ID —— 不要
parseInt()后再拼字符串 - 不要信任前端传来的
id是数字就放松:后端仍要走setInt(1, id)或bindValue(':id', id, PDO::PARAM_INT) - 错误示例:
"SELECT * FROM logs WHERE level = '" . $_GET['level'] . "'"→ 正确应为"SELECT * FROM logs WHERE level = ?"+ 绑定
哪些地方参数化不了?必须白名单校验
列名、表名、排序字段(ORDER BY)、分表后缀这些语法成分,数据库不允许用 ? 或 :param 占位。它们一旦动态生成,就是注入高发区。
-
ORDER BY ?在 MySQL 中会报错;正确做法是限定可选字段,比如只允许status、created_at,用if-else映射成硬编码 SQL - 动态表名(如按月分表
logs_202609)必须用正则严格过滤:/^[a-z_][a-z0-9_]{1,63}$/i,或查配置中心白名单 -
LIMIT ?, ?合法,但第一个参数(offset)必须是整数且 ≥ 0,否则可能触发隐式类型转换漏洞
最常被忽略的一点:日志记录、审计 SQL、调试输出里如果手动拼接了用户输入,哪怕不执行,也可能被用于二次注入或信息泄露。只要字符串里混入了任何用户可控内容,就得警惕。

















