参数化查询占位符必须匹配驱动:MySQL用?,PostgreSQL用$1,写错会导致参数被当字面量处理或报错;动态标识符需白名单校验,ORM原生SQL仍须参数化。

用 database/sql 做参数化查询时,占位符写错会直接失效
Go 的 database/sql 本身不解析 SQL,占位符语义由驱动决定。MySQL 驱动用 ?,PostgreSQL 驱动用 $1、$2,SQLite 两者都支持但行为不一致。写错占位符不会报错,而是把参数当字面量传入,等价于字符串拼接。
- MySQL 场景下写
$1→ 查询永远无结果,或意外匹配到字面值$1的记录 - PostgreSQL 场景下写
?→ 驱动报错sql: expected 0 arguments, got 1或静默忽略参数 - 跨数据库迁移时,硬编码占位符会导致运行时行为突变,建议封装统一的
QueryBuilder抽象层,或直接用 GORM 这类屏蔽差异的 ORM
别在 GORM 里用 Raw() 或 Session().Exec() 拼接用户输入
GORM 默认安全,但它的原生 SQL 接口是“逃生舱口”——一旦打开,所有防护自动失效。常见错误是以为加了 Where() 就安全,结果在 Raw() 里又把变量塞进 SQL 字符串。
- ❌ 错误:
db.Raw("SELECT * FROM users WHERE name = '" + name + "'").Find(&users) - ✅ 正确:
db.Where("name = ?", name).Find(&users)或db.Raw("SELECT * FROM users WHERE name = ?", name).Find(&users) - 动态表名/字段名无法参数化,必须白名单校验:比如只允许
name、email、created_at出现在ORDER BY子句中 - 用
gosec扫描时,规则G201会标记所有Raw()调用,需人工确认是否带参数化
html/template 不等于万能盾,富文本和 JS 上下文要额外处理
html/template 只对 HTML 内容做转义,且只在模板渲染阶段生效。如果用户输入被拼进 JavaScript 字符串、CSS 属性、URL 参数或 innerHTML,它完全不起作用。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- ❌ 危险:
<script>var name = "{{.UserName}}";</script>—— 当UserName是"); alert(1); //时,JS 执行逃逸 - ✅ 安全做法:用
js.Marshal编码后插入:<script>var name = {{.UserNameJSON}};</script>,其中UserNameJSON是json.Marshal后的字符串 - 富文本必须净化,不能靠
template.HTML放行:用bluemonday白名单策略,禁用script、onerror、javascript:等一切执行向量 - HTTP 响应头仍需设置:
Content-Security-Policy: default-src 'self'是最后一道防线,防止 inline script 执行
输入验证不能替代输出编码,类型检查只是辅助手段
很多人以为 strconv.Atoi 转整数就防住了 SQL 注入,这是错觉。攻击者可以绕过类型检查,比如在 UUID 字段传入 1' OR '1'='1(字符串合法),或利用 JSON API 传嵌套结构触发未覆盖的解析路径。
立即学习“go语言免费学习笔记(深入)”;
- 输入验证目标是“快速拒绝明显非法输入”,不是“构造安全边界”
- ID 类型检查后仍要走
WHERE id = ?,不能拼成"WHERE id = " + idStr - 邮箱、手机号等格式校验建议用正则+长度限制,但不要过度设计:比如邮箱正则太复杂反而导致 DoS(回溯爆炸)
- 真正关键的是:所有输出到外部系统(SQL、HTML、JS、HTTP 头)的地方,都必须按目标上下文做对应编码,不能复用同一套“清洗逻辑”
最易被忽略的一点:安全不是单点开关。一个请求可能同时经过 SQL 查询、HTML 渲染、JS 初始化、CSP 头设置四个环节,任一环节漏掉上下文适配,整条链路就失效。写代码时得时刻问自己:这串数据接下来要去哪?谁会解释它?

















