在Go语言项目里,GORM作为常用的ORM框架简化了数据库操作流程,但如果使用不当,依然会出现SQL注入漏洞。最典型的错误写法就是直接拼接原始SQL字符串,把用户输入的内容直接嵌入到SQL语句中,给恶意攻击留下可乘之机。

SQL注入在GORM中的产生原因
SQL注入的核心是攻击者通过构造特殊输入,改变原本SQL语句的逻辑。在GORM中如果直接使用Raw方法拼接包含用户输入的字符串,就会触发这个问题。比如下面的错误示例:
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
ID uint
Name string
Age int
}
func main() {
dsn := "user:password@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("连接数据库失败")
}
// 模拟用户输入,恶意输入可能包含SQL片段
userInput := "张三' OR '1'='1"
// 错误写法:直接拼接SQL字符串
var users []User
db.Raw("SELECT * FROM users WHERE name = '" + userInput + "'").Scan(&users)
fmt.Println(users)
}
上面的代码中,如果用户输入是张三' OR '1'='1,拼接后的SQL会变成SELECT * FROM users WHERE name = '张三' OR '1'='1',条件永远成立,会查出所有用户数据,造成数据泄露。
使用参数化API规避SQL注入
GORM提供了多种参数化传参的方式,会把用户输入的内容当作纯参数处理,不会和SQL语句本身拼接,从根源上避免注入问题。
1. 参数化查询
使用Raw方法时,用?作为占位符,把参数单独传入,而不是直接拼接字符串:
func safeQuery(db *gorm.DB, userName string) ([]User, error) {
var users []User
// 使用?占位符,参数单独传入
result := db.Raw("SELECT * FROM users WHERE name = ?", userName).Scan(&users)
if result.Error != nil {
return nil, result.Error
}
return users, nil
}
这种方式下,无论用户输入什么特殊字符,都会被当作name字段的查询值,不会篡改SQL逻辑。
2. 链式调用参数化条件
GORM的链式调用方法本身默认支持参数化,比如Where、First等方法,直接传入查询条件和参数即可:
func queryByChain(db *gorm.DB, userName string, minAge int) ([]User, error) {
var users []User
// Where方法参数化,多个条件用多个参数传入
result := db.Where("name = ? AND age > ?", userName, minAge).Find(&users)
if result.Error != nil {
return nil, result.Error
}
return users, nil
}
3. 参数化更新和删除
更新和删除操作同样要避免拼接SQL,使用参数化方式:
func updateUserAge(db *gorm.DB, userID uint, newAge int) error {
// 更新操作参数化
result := db.Model(&User{}).Where("id = ?", userID).Update("age", newAge)
return result.Error
}
func deleteUser(db *gorm.DB, userID uint) error {
// 删除操作参数化
result := db.Where("id = ?", userID).Delete(&User{})
return result.Error
}
常见错误写法对比
下面是错误写法和正确写法的对比表格:
| 操作类型 | 错误写法(有注入风险) | 正确写法(参数化) |
|---|---|---|
| 查询 | db.Raw("SELECT * FROM users WHERE name = '" + name + "'") | db.Raw("SELECT * FROM users WHERE name = ?", name) |
| 条件查询 | db.Where("age > " + ageStr) | db.Where("age > ?", age) |
| 更新 | db.Exec("UPDATE users SET age = " + ageStr + " WHERE id = " + idStr) | db.Exec("UPDATE users SET age = ? WHERE id = ?", age, id) |
注意事项
- 不要为了图方便直接拼接用户输入到SQL字符串中,哪怕输入看起来是可信的,也可能被篡改。
- 如果需要执行复杂SQL,始终使用占位符
?传参,不要拼接字符串。 - 对于表名、字段名等不能参数化的部分,如果需要动态传入,要做严格的白名单校验,只允许传入预期的表名或字段名。
只要始终使用GORM提供的参数化API,避免原始SQL拼接,就能有效规避SQL注入风险,保障数据库操作的安全性。