Go的database/sql不防注入,必须用占位符传值(MySQL/SQLite用?,PostgreSQL用$1)、白名单校验标识符,禁用字符串拼接、过滤转义和multiStatements。

必须用占位符,不能拼字符串
Go 的 database/sql 本身不防注入——它只提供接口,安全与否全看你写不写拼接逻辑。只要出现 "SELECT * FROM users WHERE name = '" + name + "'" 或 fmt.Sprintf("WHERE id = %d", id) 这类代码,就已失守。
正确做法是把值全部交给驱动处理:MySQL/SQLite 用 ?,PostgreSQL 用 $1、$2。驱动会在协议层做参数绑定,数据库收到的永远是独立数据帧,不是可执行语句片段。
-
db.Query("SELECT * FROM orders WHERE status = ? AND user_id = ?", "shipped", userID)✅ -
db.Query("SELECT * FROM orders WHERE status = '" + status + "'")❌(哪怕 status 是硬编码字符串,也别养成习惯) - PostgreSQL 驱动认
$1,写?会报pq: syntax error at or near "?" - MySQL 驱动只认
?,写$1直接报sql: expected 0 arguments, got 1
动态表名或列名怎么办
占位符只支持值(WHERE 条件、INSERT 字段值),不支持标识符(表名、列名、ORDER BY 后字段)。这时候参数化失效,必须靠白名单校验。
比如用户传参 ?sort=price,你不能直接拼成 "ORDER BY " + sortParam,而要显式判断:
- 定义合法字段:
validSortCols := map[string]bool{"price": true, "name": true, "created_at": true} - 查表前先验证:
if !validSortCols[sortParam] { return errors.New("invalid sort column") } - 再拼接:
query += " ORDER BY " + sortParam(此时 sortParam 已可信) - 表名同理:从配置或枚举中取值,而非解析 URL 或 JSON 字段
警惕 ORM 的 Raw 方法和字符串拼接陷阱
GORM、sqlx 等库默认安全,但一旦调用 Raw() 或 Session.Raw(),就退出了参数化保护区。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
常见误用:
-
db.Raw("SELECT * FROM " + tableName + " WHERE id = ?", id).Scan(&u)—— 表名未校验,tableName若来自用户输入,立刻中招 -
db.Table(tableName).Where("id = ?", id).Find(&u)✅(Table()内部做了白名单或限制,前提是tableName是受控值) -
sql.RawBytes不是“安全拼接容器”,它只是扫描二进制字段时的缓冲类型,拿它拼 SQL 和直接字符串拼接没区别 - 禁用
multiStatements=true:DSN 中若含此参数,攻击者可在参数里塞admin'; DROP TABLE users; --,MySQL 会真执行
别信过滤、转义、正则“消毒”
对输入做 strings.Replace(email, "'", "''", -1) 或删分号、去注释符,完全无效。SQL 注入本质是**改变查询结构**,不是破坏字符串边界。
例如用户输 admin' OR '1'='1,即使你删掉单引号,变成 admin OR 1=1,在某些上下文中仍可能被解释为布尔条件;更危险的是 admin' -- 或 admin' UNION SELECT ...,这些根本绕过字符级过滤。
唯一可靠路径是:值走占位符,标识符走白名单,其余一律拒收。日志里也别打原始 SQL 字符串——打模板加参数说明,否则你会误以为“没拼接就安全”,实际可能漏掉 Raw() 或动态拼接点。

















