Go实现WAF的核心是将过滤逻辑嵌入标准http.Handler链,通过中间件式Handler包装原始handler,在轻量检查(如SQL注入关键词、路径遍历)后放行;需注意双重编码解码、代理头可信解析、body流控制及白名单优先等关键细节。

Go 里用 http.Handler 拦截恶意请求最直接
Go 实现 WAF 的核心不是造轮子,而是把过滤逻辑塞进标准的 http.Handler 链。所有请求必经这里,改入口比在业务里到处加校验靠谱得多。
常见错误是试图在 http.HandleFunc 里做全部过滤——一旦路由嵌套或用了 gorilla/mux 这类库,就容易漏掉;正确做法是写一个中间件式 Handler,wrap 原始 handler:
func wafMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if isMalicious(r) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
-
isMalicious应只做轻量判断(如 SQL 注入关键词、路径遍历符号../、超长 header),别在这里解析 body 或解密 JWT - 注意
r.URL.Path已被 Go 自动解码,但攻击者可能发双重编码路径(如%252e%252e/),得自己再 decode 一次再检查 - 如果用了
http.StripPrefix,WAF 必须放在它之后,否则匹配的路径和实际路由不一致
检测 SQL 注入时别信正则,优先用白名单 + 关键字扫描
用正则匹配 SELECT.*FROM 或 UNION.*SELECT 看似简单,实际漏报率高、误报多,还容易被绕过(比如换行、注释、大小写混用)。
真正有效的做法是分层:先做基础字符过滤('、"、;、--、/*),再对参数值做关键字扫描(仅限未转义的原始输入),最后靠白名单约束关键字段(如 sort 参数只允许 name、created_at)。
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 不要扫描整个
r.Body,只扫r.FormValue和r.URL.Query()中的值;JSON 请求体需提前解析,且只检查已知字段 - 关键字列表必须小写统一处理,但注意有些数据库支持反引号或方括号标识符(如
`user`、[user]),这些也得覆盖 - Go 标准库的
url.QueryEscape不防注入,它只编码 URL,不清理语义;该用的是database/sql的参数化查询,WAF 只是第一道网
net/http 默认不读取大 body,WAF 里要手动控制
默认情况下,http.Request 的 Body 是惰性读取的。如果你在 WAF 中调用 r.ParseForm() 或 io.ReadAll(r.Body),会消耗 body 流,导致下游 handler 拿不到数据。
更麻烦的是,攻击者发一个 100MB 的恶意 POST body,没限制就会耗尽内存或拖慢服务。
- 用
r.ContentLength做前置拒绝:if r.ContentLength > 10*1024*1024 { http.Error(...) } - 需要读 body 时,用
io.LimitReader(r.Body, limit)包一层,再传给json.NewDecoder或form.ParseMultipartForm - 别在 WAF 中调用
r.ParseMultipartForm—— 它会把文件暂存到磁盘或内存,开销不可控;只解析 form 字段,文件上传由业务自己决定是否放行
Go WAF 在反向代理场景下容易漏掉真实 IP 和协议头
部署在 Nginx 或 Cloudflare 后面时,r.RemoteAddr 是代理的 IP,r.TLS 是空的,而真实客户端 IP 和 HTTPS 状态藏在 X-Forwarded-For、X-Forwarded-Proto 里。WAF 若忽略这些,IP 封禁和重定向逻辑就全失效。
- 只信任你配置的上游代理 IP 列表(如
10.0.0.0/8),从X-Forwarded-For取最右边非信任 IP;别无脑取第一个 -
X-Forwarded-Proto可被伪造,WAF 中做跳转判断前,必须结合r.TLS != nil或你配置的代理端口(如只允许 443 上来的请求标为 https) - Go 的
http.Request不自动解析X-Real-IP,得手动读取并验证;别依赖第三方中间件自动做这件事,它可能没校验来源可信度
WAF 的难点从来不在“怎么拦”,而在“拦得准、不误伤、不被绕”。Go 的简洁性反而容易让人低估协议细节和边界条件——比如 URL 解码顺序、body 流状态、代理链信任层级,这些地方错一点,整套防护就形同虚设。

















