占位符写错等于未防护:mysql必须用?,postgresql必须用$1、$2,混用不会编译报错但导致参数被当字面量处理,等价于sql拼接;动态标识符须白名单校验,gorm raw()等原生接口需严格参数化。

database/sql参数化查询占位符写错等于没防
MySQL驱动只认?,PostgreSQL驱动只认$1、$2,写反了不会编译报错,但参数会被当普通字符串传入——等价于拼接SQL。比如在MySQL里写"WHERE name = $1",查询永远无结果,或意外匹配到字段值恰好是"$1"的记录;PostgreSQL里写?则直接报sql: expected 0 arguments, got 1或静默丢弃参数。
跨数据库迁移时硬编码占位符,会导致运行时行为突变。建议:要么统一用GORM这类屏蔽差异的ORM,要么封装QueryBuilder抽象层,把占位符逻辑收口。
- 检查当前驱动:MySQL用
github.com/go-sql-driver/mysql,PostgreSQL用github.com/lib/pq - 别在测试代码里用
fmt.Sprintf拼SQL——gosec规则G202会标记所有fmt.Sprintf含SQL字样的调用 -
Scan和StructScan不防注入,它们只管结果解析,注入发生在Query那一刻
GORM的Raw()不是后门,是逃生舱口
Raw()本身安全,危险的是把它当成“可以放心拼接”的借口。只要用户输入进了SQL字符串字面量,防线就已失守。
常见错误:db.Raw("SELECT * FROM users WHERE name = '" + name + "'").Find(&users)——哪怕前面加了Where(),Raw()仍独立承担风险。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 正确做法:
db.Raw("SELECT * FROM users WHERE name = ?", name).Find(&users) - 动态表名、字段名、
ORDER BY子句无法参数化,必须白名单校验,例如只允许"name"、"email"、"created_at"出现在排序字段中 - gosec扫描规则G201会标记所有
Raw()调用,需人工逐条确认是否带参数化
html/template只保HTML上下文,JS/CSS里照样XSS
html/template对{{.UserName}}自动转义成<script></script>,但它只在HTML元素内容中生效。一旦变量进到<script></script>、href、src、style或innerHTML,它完全不起作用。
危险写法:<script>var name = "{{.UserName}}";</script>——输入"); alert(1); //直接逃逸执行。
- 安全做法:
<script>var name = {{.UserNameJSON}};</script>,其中UserNameJSON是json.Marshal后的字符串 - 富文本不能靠
template.HTML放行,必须用bluemonday白名单净化,禁用script、onerror、javascript:等执行向量 - HTTP响应头仍需设置
Content-Security-Policy: default-src 'self',这是防止inline script执行的最后一道防线
动态标识符白名单校验最容易被跳过
参数化只支持值(WHERE条件、INSERT值),不支持标识符(表名、字段名、ORDER BY后列名)。标准SQL规定这些必须在查询编译时确定,db.Query("SELECT * FROM ?","users")会panic。
有人试图用strings.Replace过滤单引号或分号,这完全无效——SQL注入靠的是改变查询结构,不是破坏字符串。
- 白名单应硬编码在代码里,而不是配置文件或数据库中,避免被篡改
- 排序方向(
ASC/DESC)也需校验,只允许"ASC"或"DESC",禁止"ASC; DROP TABLE users;" - 多语句执行(
multiStatements=true)默认关闭,但检查DSN,生产环境务必确保未显式开启
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










