直接拼接变量危险,因数据库无法区分代码与数据,攻击者可借' or '1'='1等输入篡改sql逻辑;必须用参数化查询(如?或$1)绑定值,表名等结构元素须白名单校验。

SQL 查询中直接拼接变量为什么危险
Go 里用 fmt.Sprintf 或字符串拼接把变量塞进 SQL 字符串,看似简单,实则几乎必然引入 SQL 注入漏洞。比如 username := "admin' OR '1'='1",拼进 "SELECT * FROM users WHERE name = '" + username + "'" 就会绕过认证。数据库驱动(如 database/sql)本身不解析 SQL 字符串里的占位符,它只认预处理语句中的 ? 或命名参数(取决于驱动)。
正确做法:用 database/sql 的 Query / Exec 自动绑定参数
Go 标准库不支持在 SQL 字符串里写 {name} 或 $1 这类“占位符模板”,而是把参数交给驱动在执行时安全绑定。你写的 SQL 字符串里只放驱动认可的占位符符号,其余全由 Query 等函数处理:
rows, err := db.Query("SELECT name, email FROM users WHERE id = ? AND status = ?", userID, "active")
不同驱动占位符不同,必须匹配:
- MySQL 驱动(
github.com/go-sql-driver/mysql)用? - PostgreSQL 驱动(
github.com/lib/pq)用$1,$2等位置参数 - SQLite3 驱动(
github.com/mattn/go-sqlite3)支持?,?NNN,:name,@name—— 但database/sql接口只保证?兼容
别自己替换字符串,也别用 fmt.Sprintf 拼 SQL —— 那样等于绕过整个安全机制。
想用命名参数?得看驱动是否原生支持
database/sql 接口本身不定义命名占位符,所以 WHERE name = :name 或 WHERE name = $name 能不能用,完全取决于你用的驱动。例如:
-
lib/pq支持$1,$2,但不支持$name(需用pq.NamedParam配合额外包) -
sqlc工具生成的代码常用:name,但它是在编译期把命名参数转成位置参数,运行时仍走标准?或$1流程 - 没有驱动能让你在运行时自由混用
?和:id—— 绑定顺序和数量必须严格对应
如果硬要写命名风格,建议统一用 ? + 文档注释说明字段含义,比依赖驱动特有语法更可移植。
哪些情况真需要字符串格式化?仅限非参数部分
只有 SQL 结构本身动态时才需拼接,比如表名、列名、ORDER BY 字段、IN 子句长度——这些无法用参数绑定。这时必须白名单校验或正则过滤:
allowedTables := map[string]bool{"users": true, "orders": true}
if !allowedTables[table] {
return errors.New("invalid table name")
}
query := fmt.Sprintf("SELECT * FROM %s WHERE deleted = ?", table)
注意:table 没进参数列表,所以必须人工确保安全;而 deleted = ? 后面的值仍走参数绑定。混淆这两类内容是常见错误源头。
最易被忽略的是:哪怕用了参数绑定,如果 SQL 拼接部分(如表名)没校验,整条语句依然不安全。参数绑定只保护值,不保护语法结构。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











