跳到主要内容

SQL 安全

问题

Go 中如何防御 SQL 注入?使用 ORM 就不用考虑安全了吗?

答案

SQL 注入原理

// ❌ 字符串拼接 → SQL 注入
query := fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", userInput)
// 如果 userInput = "'; DROP TABLE users;--"
// 实际执行:SELECT * FROM users WHERE name = ''; DROP TABLE users;--'

防御方式

1. 参数化查询(最关键)

// ✅ database/sql 占位符
db.Query("SELECT * FROM users WHERE name = ?", userInput)

// ✅ sqlx
sqlx.Get(db, &user, "SELECT * FROM users WHERE id = $1", id)
重要

永远不要fmt.Sprintf 拼接 SQL。Go 的 database/sql 使用 prepared statement,参数和 SQL 语句分开发送,从根本上杜绝注入。

2. GORM 安全注意事项

GORM 的 WhereFind 等方法默认参数化,但某些写法仍有风险

// ✅ 安全:参数化
db.Where("name = ?", userInput).Find(&users)
db.Where(&User{Name: userInput}).Find(&users)

// ❌ 危险:字符串拼接传入 GORM
db.Where(fmt.Sprintf("name = '%s'", userInput)).Find(&users)

// ⚠️ 小心 Raw / Exec
db.Raw("SELECT * FROM users WHERE name = ?", userInput) // ✅ 用占位符
db.Raw("SELECT * FROM users WHERE name = '" + input + "'") // ❌

3. 动态列名/表名

列名和表名不能用占位符,需要白名单校验

// 动态排序字段白名单
var allowedColumns = map[string]bool{
"name": true, "created_at": true, "price": true,
}

func SafeOrderBy(column string) string {
if allowedColumns[column] {
return column
}
return "id" // 默认值
}

db.Order(SafeOrderBy(userInput) + " DESC").Find(&products)

4. LIKE 查询转义

// 转义 LIKE 中的特殊字符 %, _
func EscapeLike(s string) string {
s = strings.ReplaceAll(s, "\\", "\\\\")
s = strings.ReplaceAll(s, "%", "\\%")
s = strings.ReplaceAll(s, "_", "\\_")
return s
}

db.Where("name LIKE ?", "%"+EscapeLike(userInput)+"%")

安全检查清单

检查项做法
SQL 拼接全部用参数化查询替换
ORM Raw()确保用占位符
动态列名白名单校验
LIKE 查询转义 %_
错误信息不暴露 SQL 细节给用户
权限应用用最小权限数据库账号

常见面试问题

Q1: database/sql 的占位符为什么能防注入?

答案:底层使用 Prepared Statement,SQL 语句和参数分两次发送给数据库。数据库先编译 SQL 模板,再绑定参数值。参数只会作为数据处理,不会被解析为 SQL 指令。

Q2: GORM 用了 ORM 还需要关注 SQL 注入吗?

答案需要。GORM 的链式方法内部做了参数化,但以下场景仍有风险:

  • Raw() / Exec() 手写 SQL 时拼接字符串
  • Where() 传入拼接好的字符串而非占位符
  • Order() / Group() 中直接使用用户输入(这些不支持占位符)

相关链接