Gin默认不拦截\x00–\x1f控制字符,因其不做HTTP语义层过滤,而是将原始body交由json.Unmarshal等底层解析器处理;后者仅对\x09、\x0a、\x0d外的控制字符报错,但绕过解析(如io.ReadAll读body)会导致脏数据进入业务逻辑。

为什么 Gin 默认不拦 \x00–\x1f 控制字符?
Gin 本身不做 HTTP 语义层的非法字符过滤,它把原始 c.Request.Body 直接交给 json.Unmarshal 或 form.PostForm 处理。而 Go 标准库的 encoding/json 在解析时对 \x00、\x08、\x1f 等控制字符(除 \x09、\x0a、\x0d 外)会报 invalid character 错误;但如果你绕过解析——比如用 io.ReadAll 手动读 body、或处理文件上传/转发 raw payload——这些脏数据就直接进了业务逻辑。
中间件里怎么安全扫描并拦截非法字符?
必须在任何绑定逻辑(c.ShouldBindJSON、c.ShouldBind)消费 body 前完成扫描。否则 body 流已耗尽,后续绑定失败。推荐用 c.GetRawData() 一次性读取并重置 body,避免多次读取冲突。
- 只扫描前 64KB,防大文件阻塞(
io.LimitReader(c.Request.Body, 65536)) - 遍历每个
rune,用switch r排查 \x00–\x1f 中的非法值(保留 \x09、\x0a、\x0d) - 发现非法字符立即调用
c.AbortWithError(400, fmt.Errorf("invalid control character")) - 不要用
strings.ContainsAny或正则匹配 byte,中文/emoji 会被切碎
过滤逻辑该放在哪一层?
必须放在全局或分组级中间件中,不能靠 handler 里手动校验。因为非法字符可能出现在任意字段:JSON body、form-data 的 text 字段、甚至 query string 的 value(如 /search?q=abc\x01def)。若只在 handler 里检查 c.Param 或 c.Query,漏掉 body 就等于放行。
- 对 JSON 请求:检查
c.GetRawData()后的字节流 - 对 form-data:需先调用
c.Request.ParseMultipartForm,再遍历c.Request.PostForm的每个 value - 对 query 参数:用
c.Request.URL.RawQuery扫描(注意已解码) - 所有路径都必须用
[]rune遍历,禁用[]byte操作
容易被忽略的 Unicode 和边界问题
零宽字符(如 \u200B、\uFEFF)、BOM、代理对(surrogate pairs)不会触发 json.Unmarshal 报错,但可能破坏日志、存储或下游系统。它们不属于 \x00–\x1f,但属于常见“隐形非法字符”。
-
utf8.ValidString(s)应在清洗前调用,否则后续range s可能 panic - 用户昵称、评论等字段建议白名单:只允许
a-z A-Z 0-9 _ - .+ 中文/emoji(用unicode.IsLetter/unicode.IsMark判断) - 文件名提取必须走
filepath.Base,且先url.PathUnescape再清洗,否则%00会逃逸 - 日志输出前加
fmt.Printf("%q", s),不然看不见隐藏字符











