JSON/XML Unmarshal会引爆内存,因默认无限制读取整个Request.Body,攻击者发送大垃圾包会导致进程OOM;必须用http.MaxBytesReader在handler开头显式包装r.Body限流。

为什么json.Unmarshal和xml.Unmarshal会引爆内存
它们默认无限制读取整个Request.Body,哪怕攻击者发一个 512MB 的 JSON 垃圾包,Unmarshal会先全读进内存再解析——GC 来不及回收,进程直接 OOM。这不是“解析失败”,是根本没机会走到错误分支就崩了。
常见现象:panic: runtime out of memory、服务响应变慢后突然 500、top里 Go 进程 RSS 持续飙升到数 GB。
- HTTP/2 下更危险:多个小请求复用连接,每个都绕过你没设限的 Body 读取逻辑
-
r.ParseForm()同样不安全——含恶意大字段或空 multipart boundary 时照样爆内存 - 别信
Content-Length头:chunked 编码下它根本不存在,校验无效
必须用http.MaxBytesReader套在Request.Body上
它不是中间件,也不是自动生效的防护;是你手动加在 handler 开头的一道“流量闸门”。不显式包装,等于没防。
典型误用:http.MaxBytesReader(w, r.Body, limit) 但没赋值给 r.Body,等于白写。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 正确姿势:
r.Body = http.MaxBytesReader(w, r.Body, 5 * 1024 * 1024)(例如登录接口限 5MB) - 必须在
defer r.Body.Close()之前执行,否则可能 panic - 参数顺序不能错:
MaxBytesReader(w http.ResponseWriter, r io.ReadCloser, n int64),第一个是w,不是r - 如果 handler 里调
r.MultipartReader(),必须在这之前完成包装
区分MaxHeaderBytes和MaxBytesReader的防守边界
Header 和 Body 是两层独立攻击面,得分开控。只设一个,漏掉另一个就等于开门。
-
http.Server{MaxHeaderBytes: 8192}:只管 header 大小,防“1000 个 X-Forwarded-For”类攻击,建议设 4KB–8KB -
http.MaxBytesReader:只管 body 字节数,按接口语义设(如上传接口 50MB,配置接口 2MB) - 不要混用:header 超限会返回
431 Request Header Fields Too Large,body 超限则http: request body too large(由标准库内部触发)
流式解包时别踩io.ReadFull的坑
想“精确读 N 字节”就硬上 io.ReadFull?它在 EOF 时直接返回 io.ErrUnexpectedEOF,而最后一块数据不足 N 字节是常态,不是错误。
是否该用 ReadFull 只取决于协议定义:
- 加密块、固定头结构等要求每块必须满 → 用
io.ReadFull - 普通协议(如自定义二进制帧、JSON 流)→ 一律用
Read,检查返回值:n > 0 && err == io.EOF表示最后一块,数据有效 - 混用
ReadFull和Read在同一连接上,极易导致后续读取偏移错位、数据截断
真实防御的关键不在“能不能解包”,而在“敢不敢让未限流的字节流进解包逻辑”。所有解包入口前,必须有确定的字节上限,且这个上限要落在连接建立后的最早可干预点——不是 middleware 里 log 一下,而是 handler 开头第一行代码就重置 r.Body。

















