preparedstatement防sql注入的核心在于sql结构与数据在数据库层面严格分离,预编译时仅解析模板,参数执行时作为纯数据处理;列名、表名等无法参数化部分须白名单校验。

预编译语句(PreparedStatement)和参数化查询是防止 SQL 注入最有效、最直接的手段——只要用对了,99% 的注入路径就被堵死了。
根本原因在于:SQL 结构和数据在数据库层面被严格分离。预编译时只解析 SQL 模板(如 SELECT * FROM users WHERE id = ?),问号占位符不参与语法解析;执行时传入的参数被当作纯数据处理,不会被拼进 SQL 字符串里,也就无法改变语句逻辑。
常见错误是误以为“加了单引号就安全”,比如手动拼接:"SELECT * FROM users WHERE name = ‘" + input + "’"——这种写法哪怕加了转义,也扛不住绕过技巧(如用十六进制、宽字节、注释符等)。
- Java 中必须用
Connection.prepareStatement()创建语句,再用setString()、setInt()等方法赋值,绝不能用Statement.execute()拼接字符串 - PHP 的
PDO::prepare()或mysqli_prepare()同理,绑定参数必须走bindValue()或bind_param(),不能用sprintf或字符串插值 - Node.js 的
mysql2库默认开启参数化,但若显式关掉options.multipleStatements: true,又拼接用户输入,照样中招
不是所有 SQL 都能参数化——列名、表名、排序字段、LIMIT 偏移量这些语法成分不能用问号占位。一旦动态生成这些部分,就必须白名单校验或硬编码映射。
-
ORDER BY ?是非法的,MySQL 会报错;正确做法是限定可选字段,比如只允许status、createdat,用if-else或映射表决定最终 SQL -
LIMIT ?, ?在 MySQL 中合法,但第一个参数(offset)必须是整数,且不能为负;如果来自用户输入,需先parseInt()并校验范围,否则可能触发类型隐式转换漏洞 - 动态表名(如分表场景)绝对不能用参数化,应通过正则
/^[a-z][a-z0-9_]{1,63}$/i严格过滤,或查配置中心白名单
不一定。ORM 只在“标准查询路径”下默认参数化,但很多框架留了裸 SQL 接口,一不小心就绕过防护。
- Django 的
raw()、extra()和cursor.execute()都不自动参数化,必须显式传参元组/字典,例如cursor.execute("SELECT * FROM users WHERE id = %s", [user_id]) - MyBatis 的
\({}是字符串替换,#{}才是预编译;写成WHERE name = \){name}就等于裸拼接 - Laravel 的
DB::select()支持参数数组,但若写成DB::select("SELECT * FROM users WHERE id = \(id")(变量插值),立刻失效
真正难的不是写对一行 prepare,而是确保整个代码库没有一个 execute("SELECT ... " + userInput) 这样的调用——它可能藏在日志埋点、导出功能、甚至旧版兼容代码里。上线前用 grep 扫描 execute(、query(、.format( 和 \) 插值,比等渗透测试发现更靠谱。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/260851.html