Gin 默认不防大请求体导致 OOM,因其框架本身不限制请求体大小,r.Body 为原始 io.ReadCloser,若未在任何读取前用 http.MaxBytesReader 包装,攻击者发送超大请求(如2GB POST)将直接耗尽内存;常见失效原因是中间件提前消耗 r.Body(如调用 ParseForm),使后续限流无效;推荐统一使用 http.MaxBytesHandler 全局拦截,或按接口手动设置 MaxBytesReader 并配合 ParseMultipartForm 控制内存缓存部分。

为什么 Gin 默认不防大请求体导致 OOM
Gin 框架本身 不做任何请求体大小限制,r.Body 是原始的 io.ReadCloser,直接交给 handler 处理。攻击者发一个 2GB 的 POST /login,Gin 就会尝试把整个 body 读进内存——无论你用 c.ShouldBindJSON() 还是 io.ReadAll(c.Request.Body),只要没提前拦截,就会触发 Out of Memory,进程被系统 kill。
必须在读取前套 http.MaxBytesReader,顺序错了就失效
常见错误是:中间件里先调了 r.ParseForm() 或 c.Request.MultipartReader(),此时 r.Body 已被消耗,后续再 r.Body = http.MaxBytesReader(...) 完全没用。
- 所有限流逻辑必须放在 任何 r.Body 读取操作之前
- 推荐统一在
http.Server.Handler层包裹:http.MaxBytesHandler,它在路由匹配前就拦截,返回 413 - 若需接口级差异化(如
/upload允 100MB、/login仅 2MB),则在路由 handler 开头手动赋值:c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, limit) -
ParseMultipartForm(maxMemory)不是总大小限制,它只控制内存缓存部分;真正有效的组合是:MaxBytesReader限总量 +ParseMultipartForm(8 控制内存缓冲上限
上传场景下绕过框架绑定时的坑
很多开发者为兼容老客户端或特殊格式,会跳过 c.ShouldBindJSON(),改用 json.NewDecoder(c.Request.Body).Decode()。这时如果没手动套 http.MaxBytesReader,限流配置形同虚设。
- 必须显式包装:
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 5(5MB) - 注意
http.MaxBytesReader第二个参数是http.ResponseWriter,不是gin.Context,别传错 - 如果用了自定义
gin.Engine实例且替换了gin.DefaultWriter,要确认底层是否仍支持写入状态码(否则 413 可能无法发出)
配合超时与连接池才能堵住全部泄漏点
光限大小不够。大流量攻击常伴随慢速连接、长连接耗尽、数据库连接池打满等连锁反应。
-
http.Server.ReadTimeout和ReadHeaderTimeout必须设(如 5s),防 slowloris 类攻击 - 数据库连接池要设硬上限:
db.SetMaxOpenConns(50),避免 goroutine 堆积等待连接 - 对上传类接口,合并分片时禁用全内存拼接,改用
io.Copy流式写入磁盘,避免临时 buffer 占用巨量堆内存 - 启用
pprof并监控goroutine数和heap_inuse,OOM 前通常有 goroutine 泄漏或 buffer 持久化迹象


















