数据库驱动仅防护执行层,无法拦截db.raw()、字符串拼接等上游注入;中间件可守http层第一道防线,识别并阻断' or 1=1--等恶意载荷,但须与参数化查询共存,不可替代。

为什么不能只靠数据库驱动防SQL注入
Go 的 database/sql 包确实自带参数化查询能力,但它的防护边界仅限于「执行层」——只要你不调用 db.Query 或 db.Exec 时拼接字符串,它就管得住。可现实中,SQL 注入常发生在更上游:比如把用户输入直接塞进 db.Raw()、传给第三方 ORM 的原始 SQL 构造器、甚至在日志里拼接敏感字段后被误当命令执行。
中间件无法替代参数化查询,但它能守住第一道防线:提前识别高危输入模式,阻断明显恶意载荷(如 ' OR 1=1 --、; DROP TABLE),避免请求抵达数据库前就已埋雷。
- 不要依赖「框架默认安全」——Gin 本身不校验请求体内容
-
db.Raw()、fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", name)这类写法会绕过所有驱动层防护 - 中间件拦截的是 HTTP 层输入,不是 SQL 层执行,二者职责不同,必须共存
如何设计一个真正生效的混合拦截中间件
所谓「混合」,是指同时覆盖 SQL 注入和 XSS 的典型特征,但不做过度解析(比如不写 HTML 解析器),而是基于规则快速匹配 + 上下文感知输出编码。
关键点在于区分「输入检查」和「输出转义」:中间件只做输入侧拦截;输出编码必须在模板渲染或 JSON 返回前完成,不能由中间件统一包办。
- 对
GET查询参数、POST表单、JSON请求体中的字符串字段,逐个正则扫描:/(\b(SELECT|INSERT|UPDATE|DELETE|UNION|DROP|CREATE|ALTER)\b)|(--|#|\/\*|\*\/)|(;[[:space:]]*?([Dd][Rr][Oo][Pp]|[Cc][Rr][Ee][Aa][Tt][Ee]))/i - 对疑似 XSS 的输入(含
<script></script>、javascript:、onerror=、eval\(等),记录告警并返回400 Bad Request,不放行 - 不尝试「清理」输入(如删掉
)——清理可能破坏合法业务数据,拦截才是最小副作用策略 - 跳过二进制类型请求(
Content-Type: image/*、application/pdf),避免误判
中间件里最容易踩的三个坑
很多开发者写完中间件一测「好像能拦住几个测试用例」就上线,结果线上还是被扫出漏洞。问题往往出在边界处理上。
- 没处理 URL 编码绕过:攻击者发
%27%20OR%201%3D1%20--,中间件若只检查原始字节而不先解码,会漏掉 - 忽略大小写变种:
<script></script>、JaVaScRiPt:不匹配script就白写了正则 - 对 JSON 请求体只检查顶层字段:如果攻击载荷藏在嵌套对象如
{"user":{"profile":{"bio":"<script>..."}}}</script>,没递归遍历就形同虚设
建议用 json.RawMessage 先解析再递归检查字符串字段,别图省事用 bytes.Contains() 扫整个 body。
XSS 输出编码不能交给中间件做
中间件可以拦掉明显带脚本标签的输入,但绝不能在中间件里对响应体做 html.EscapeString() —— 因为输出上下文决定编码方式:HTML 内容要转义 ,JS 字符串里要转义 <code>\ 和引号,URL 参数要用 url.PathEscape()。中间件不知道你要把数据塞进哪儿。
正确做法是:中间件只负责输入拦截;模板引擎(如 html/template)自动做上下文感知编码;JSON 接口返回前用 json.Marshal()(它本身会转义双引号和控制字符);前端渲染动态内容时用 textContent 而非 innerHTML。
最常被忽略的一点:CSRF Token、用户昵称、富文本编辑器内容这三类数据,必须分策略处理——前者需签名验证,中间者需输出编码,后者得用白名单 HTML 过滤(如 bluemonday 库),混用一种方案必然出问题。











