拼接SQL字符串必然导致SQL注入,因用户输入与SQL结构混同;参数化查询是唯一可靠解法,通过预编译语句骨架与数据绑定实现隔离;表名等无法参数化的部分须用服务端白名单校验。

拼接SQL字符串为什么一写就中招
因为数据库会把整条字符串当作可执行代码解析,而拼接操作把用户输入和SQL结构混在了一起。只要输入里带'、--、UNION、;这类字符,就可能提前闭合引号、注释掉校验逻辑,甚至追加恶意语句。
常见错误现象包括:登录页输admin' --直接免密进后台;搜索框填1' OR '1'='1返回全部数据;URL里传id=1; DROP TABLE users;导致删库——这些都不是“偶尔出问题”,而是只要拼接,就必然可被利用。
- 前端拼接更危险:源码公开,攻击者一眼看到表名、字段名、查询结构
- 后端拼接是真执行:用户输入直接变成SQL的一部分,数据库照单全收
- 用正则过滤
'或--没用:绕过方式太多(如%27、/**/、宽字节注入)
参数化查询是唯一靠谱的解法
参数化不是“加个引号”或“转义一下”,而是让数据库先编译语句骨架,再把值当纯数据绑定进去。SQL结构和数据彻底分离,输入再恶意也变不成代码。
不同语言写法差异不大,核心都是用占位符+绑定:
- Go:
db.Query("SELECT * FROM users WHERE id = ?", userID),不能用fmt.Sprintf拼id - Python(psycopg2):
cursor.execute("SELECT * FROM logs WHERE level = %s", (level,)) - Java(JDBC):
PreparedStatement stmt = conn.prepareStatement("UPDATE accounts SET balance = ? WHERE id = ?"); stmt.setDouble(1, newBalance); stmt.setInt(2, accountId); - Node.js(pg):
client.query("SELECT * FROM products WHERE category = $1", [category])
注意:?、$1、%s这些占位符位置不能动态替换,表名、字段名、排序方向等无法参数化的部分,必须走白名单校验,不能靠拼接。
哪些地方最容易偷偷拼接SQL
开发者常以为自己没拼,其实很多写法本质就是拼接,只是换了个马甲:
- ORM 的
.raw()或.query()方法:比如User.sequelize.query(`SELECT * FROM ${table}`)—— 表名变量照样危险 - f-string / 模板字符串:Python 的
f"WHERE status = '{status}'"、JS 的`WHERE name = '${name}'`,和手拼没区别 - 构建动态条件时用
+=拼 SQL 片段:比如根据参数决定是否加AND deleted = 0,最后再db.Exec(sqlStr) - 把用户输入塞进
ORDER BY或GROUP BY:如ORDER BY ${sortField},必须限定为预设字段列表
只要字符串里出现用户可控内容,又没走参数绑定,就等于给攻击者开了后门。
白名单只用于无法参数化的场景
表名、字段名、排序方向、聚合函数这些语法成分,数据库不支持参数化,只能靠白名单硬控制。
例如要支持按不同字段排序:
- 错:直接拼
ORDER BY ${inputField} - 对:定义允许字段列表
allowedFields = ["created_at", "updated_at", "score"],检查inputField是否在其中,不在就报错或默认字段
白名单必须在服务端做,前端传来的任何值都不能信;也不能只校验开头或后缀,得是完整精确匹配;更不能用黑名单(比如“禁止drop”),因为绕过太容易。
真正难的不是写对一行参数化查询,而是守住所有边界——尤其是那些“看起来只是拼个字段名”的地方,往往最致命。

















