拼接SQL字符串等于给黑客递刀子,因用户输入直接混入SQL结构导致注入;预编译通过分离结构与数据防范此风险,但需正确使用占位符、类型化设参及避免拼接式调用。

为什么拼接 SQL 字符串等于给黑客递刀子
因为用户输入的内容会直接混入 SQL 语句结构里,WHERE name = 'admin' OR '1'='1' 这类输入就能绕过登录校验。数据库不会区分“你是想查 admin 还是想执行逻辑或”,它只负责执行最终拼出来的语句。
预编译语句(Prepared Statement)把「SQL 结构」和「数据内容」彻底分开:结构由开发者写死,数据由数据库驱动安全注入。哪怕传入 ' OR '1'='1,它也只会被当做一个字符串值,不会参与语法解析。
- 不是所有数据库驱动都默认启用预编译——比如 MySQL 的 JDBC 驱动需要显式加
useServerPrepStmts=true参数才真正走服务端预编译 - ORM 框架如 MyBatis 的
#{}是预编译,${}是字符串拼接,后者照样有注入风险 - 存储过程内部如果用
CONCAT()或EXECUTE IMMEDIATE拼接 SQL,依然不安全
Java JDBC 中必须写的三步操作
光调 prepareStatement() 不够,漏掉关键步骤照样白搭。
- 用
Connection.prepareStatement(String sql)创建语句,SQL 中用?占位,不能出现任何用户变量名 - 对每个
?调一次setString()/setInt()等类型明确的方法,不要用setObject(1, value)模糊传参 - 必须调
executeQuery()或executeUpdate(),别用execute()—— 它可能绕过预编译路径(某些旧驱动下)
错误示例:String sql = "SELECT * FROM user WHERE id = " + userId; —— 这根本没进预编译流程。
Python 中 sqlite3 和 psycopg2 的参数占位符差异
不同驱动用的占位符符号不一样,写错就退化成字符串拼接。
-
sqlite3只认?(问号),不支持命名参数;psycopg2默认支持%s,也支持%(name)s - 别在
sqlite3.execute("SELECT * FROM t WHERE x = ?", (val,))后面漏掉逗号——(val)是 int,(val,)才是单元素 tuple -
psycopg2如果误写成"... WHERE x = %s" % user_input,哪怕用了%s符号,也是 Python 层字符串格式化,完全没触发数据库预编译
Node.js 里 mysql2 和 pg 的安全边界在哪
这两个库默认开启预编译,但有几个隐藏开关能让你瞬间破防。
-
mysql2的execute()方法自动预编译,但query()方法默认不启用——除非你传的是数组参数:conn.query("SELECT * FROM u WHERE id = ?", [id]) -
pg的client.query(text, params)是安全的,但若用client.query(text + " AND status = '" + status + "'"),哪怕前面用了参数,后面拼接部分仍可注入 - 连接池配置里如果开了
permitLocalhostFallback: true(某些测试环境默认开),可能降级到非预编译协议
最常被忽略的一点:前端传来的 id 是字符串,后端没做类型校验就直接塞进 WHERE id = ?,结果数据库把字符串当数字隐式转换,反而让 id=1 OR 1=1 这种构造绕过类型检查——预编译保不住逻辑漏洞。

















