白名单必须采用预定义map精确比对而非正则匹配,因零宽空格、SQL注释等可绕过正则;动态SQL标识符需分层校验字段、方向、表名;IP白名单热更新须用atomic.Value封装不可变结构;响应字段过滤应使用专用struct显式赋值。

白名单必须用预定义 map 而非正则匹配
任何用 regexp.MustCompile(`^[a-zA-Z0-9_]+$`) 校验参数值的做法都等同于裸奔。零宽空格(\u200b)、SQL 注释(/**/)、Unicode 变体都能绕过,而正则根本识别不了。真正安全的写法只有一种:硬编码合法值集合,运行时做字符串精确比对。
常见错误是先 strings.TrimSpace 再 strings.ReplaceAll(s, "'", "''"),结果 score/**/ 仍能通过;更危险的是依赖 net.ParseIP 返回非 nil 就认为 IP 合法——127.0.0.1\u200b 能过解析,但后续 net.IPNet.Contains 会失败。
-
validSortFields := map[string]bool{"name": true, "email": true, "created_at": true},然后if !validSortFields[field] { return errors.New("invalid field") } - 动态拼接 SQL 标识符(如 ORDER BY 字段、表名)时,白名单要分层:字段、方向、表名三者各自独立校验,不能混为一谈
- 表名映射必须硬编码:
switch input { case "user": return "users_prod"; case "order": return "orders_archive" },别查INFORMATION_SCHEMA.TABLES动态取——这本身就要执行 SQL,且结果可能被污染
热更新必须用 atomic.Value 封装不可变结构
直接替换全局 map[string]bool 会导致并发读写 panic:concurrent map read and map write。热更新不是“换 map”,而是“换指针快照”。
把白名单封装成只读结构体:type Whitelist struct { ips []*net.IPNet; domains []string },所有字段不可变;用 var wl atomic.Value 存 *Whitelist 指针,更新时 wl.Store(newWL),读取时 wl.Load().(*Whitelist)。
立即学习“go语言免费学习笔记(深入)”;
- 预解析 CIDR:
net.ParseCIDR("192.168.0.0/16")在加载阶段完成,运行时只调ipnet.Contains(clientIP),避免每次请求都解析字符串 - 别在热更新逻辑里做耗时操作(如远程拉配置、解密),否则阻塞整个中间件链路
- 更新后建议加日志输出新规则总数,便于核对是否生效,但日志内容不要包含敏感值(如完整 IP 列表)
响应字段过滤必须用专用 struct 显式赋值
别碰 json:"-" 或 json:",omitempty" 做敏感字段过滤——它不是安全机制,只是序列化开关。一旦结构体字段被赋值,且没加 omitempty 或值非零,就会原样暴露。
常见错误是 handler 里手动删 map[string]interface{} 的 key,容易漏、难维护;或者把数据库模型(如 User)直接塞进 json.Marshal,这是最典型的泄露源头。
- 为每个接口定义专属响应 struct,只含该接口需要的字段,并显式从源数据拷贝:
UserDetailResponse{ID: u.ID, Name: u.Name, Email: u.Email} - 不同角色用不同 struct:
UserAdminResponse比UserPublicResponse多一个LastLoginAt字段,编译期可查、IDE 可跳转 - 若需支持客户端传
fields=id,name,email动态指定返回字段,用map[string]any+ 白名单校验,而非黑名单——白名单更易维护、不易遗漏
路径与 IP 解析必须走可信代理链
c.ClientIP() 不可靠,它默认只信任本地回环,线上环境几乎必然失效。真实 IP 必须按可信代理链逐级解析 X-Forwarded-For 或 X-Real-IP,且需显式配置可信网段。
若走多层代理(CDN → Nginx → Go),应取 X-Forwarded-For 最左非信任地址,并配置 engine.SetTrustedProxies([]string{"192.168.0.0/16", "203.208.60.0/24"})。否则攻击者伪造头即可绕过过滤。
- 用
c.Request.Header.Get("X-Real-IP")前,确认 Nginx 已设置proxy_set_header X-Real-IP $remote_addr - 文件路径校验必须在拼接前完成:
filepath.Clean→filepath.Join→filepath.Rel初筛 →filepath.EvalSymlinks→ 再filepath.Rel二次校验 - 永远不用
strings.HasPrefix(absPath, allowedRoot)做路径校验——/a和/ab/c会误判;必须用filepath.Rel确认相对路径不以..开头


















