直接拼接字符串等于打开sql注入大门,必须用?或$1占位符进行参数化查询,表名、列名、order by等结构部分须白名单校验,in子句需动态生成占位符,scan须严格匹配类型和null处理。

直接拼接字符串等于打开SQL注入大门
只要代码里出现 "WHERE name = '" + name + "'" 或 fmt.Sprintf("SELECT * FROM users WHERE id = %d", id),就已存在明确的注入风险——哪怕参数来自内部配置、枚举或看似“可信”的来源。Go 的 database/sql 不会帮你拦截这种拼接,驱动层也只负责执行,不校验 SQL 结构是否被篡改。
常见错误现象包括:查询返回了不该返回的全部记录(如 ' OR '1'='1)、插入时字段值莫名多出单引号、sql.ErrNoRows 却实际查到数据、甚至执行了意外的 DROP TABLE(若 MySQL DSN 含 multiStatements=true)。
- 永远不要信任任何输入源——URL 参数、表单字段、配置文件、环境变量,一律视作不可信
-
strings.Replace或正则过滤单引号、分号等“特殊字符”完全无效,攻击者用注释符--、括号嵌套、布尔盲注等方式绕过 - 白名单校验只适用于动态标识符(如
ORDER BY字段名),不能替代参数化查询
IN 查询必须展开占位符,不能直接传 slice
写 "WHERE id IN :ids" 并把 []int{1,2,3} 塞进 map[string]interface{}{"ids": ids},sqlx.NamedQuery 会把它当做一个整体绑定,导致语法错误或空结果——因为 PostgreSQL 要 IN ($1, $2, $3),MySQL 要 IN (?, ?, ?),而不会自动展开。
正确做法只有两种:
- 用
sqlx.In辅助函数生成问号占位符串 +db.Rebind转为目标驱动格式:query, args, _ := sqlx.In("SELECT * FROM users WHERE id IN (?)", ids)query = db.Rebind(query)rows, _ := db.Queryx(query, args...) - 手动构造命名参数(适合少量固定长度场景):
query := "SELECT * FROM users WHERE id IN (:id1, :id2, :id3)"
再逐个赋值args["id1"] = ids[0]等
ORDER BY / LIMIT / OFFSET 必须白名单校验后拼接
db.Query("SELECT * FROM users ORDER BY ?", "name") 会 panic:sql: expected 0 arguments, got 1。SQL 标准规定这些子句无法参数化,驱动层不支持运行时绑定。
可行方案是预定义合法值并严格校验:
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
- 建立白名单 map:
validSortFields := map[string]bool{"name": true, "created_at": true, "updated_at": true} - 检查输入是否在白名单内:
if !validSortFields[sortField] { return errors.New("invalid sort field") } - 再拼接:
query += " ORDER BY " + sortField + " " + orderByDirection(orderByDirection同样需白名单限制为"ASC"或"DESC")
注意:拼接前必须确保 sortField 和 orderByDirection 是白名单中明确存在的字符串,不能做任何字符串替换或截断处理。
Struct 字段必须显式声明 db tag,否则静默为零值
用 sqlx.StructScan 或 sqlx.Get 时,如果 struct 字段名与数据库列名不一致,且没加 db:"column_name" tag,Go 会跳过该字段赋值,对应字段保持零值(""、0、nil),不报错也不提示。
典型误用:
type User struct { Name string } // 数据库列为 user_name,但没写 db:"user_name"- 查询返回了
Name为空字符串,调试半天才发现是映射失败 - INSERT 时漏掉
db:"created_at"导致时间字段写入 0001-01-01
建议所有字段都显式标注:type User struct { ID int `db:"id"` Name string `db:"name"` CreatedAt time.Time `db:"created_at"` },避免隐式行为带来的不确定性。
最易被忽略的是:字段名大小写、下划线/驼峰转换规则依赖具体驱动和数据库方言,PostgreSQL 默认小写,MySQL 对大小写敏感性取决于配置——统一靠 db tag 显式控制,别依赖默认行为。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










