Go的http.Server默认无法抵御Slow POST攻击,因其Read操作阻塞等待完整请求体,每个慢请求独占goroutine且无连接数限制;必须手动配置ReadHeaderTimeout、使用MaxBytesReader限流、禁用ParseForm自动读取,并结合Nginx等前置防护。

Go 的 net/http 服务本身不内置 Slow POST 防御机制,必须手动配置超时与读取行为,否则攻击者用极低速发送大 Content-Length 请求体,就能长期占用 goroutine 和连接资源。
为什么默认 http.Server 无法抵御 Slow POST
Go 的 http.Server 在读取请求体时,默认会阻塞等待完整数据到达(或 EOF),只要客户端维持 TCP 连接并缓慢发包,Read 就不会返回。这会导致:
- 每个慢请求独占一个 goroutine,直到超时或读完
- 没有全局连接数限制,攻击者可轻易耗尽内存和 goroutine 数量
-
http.Request.Body是惰性读取的,ParseForm、ParseMultipartForm等操作会触发实际读取,此时才真正暴露风险
必须设置的三个关键超时参数
仅靠 http.Server.ReadTimeout 不够——它只限制从连接建立到读完请求头的时间,对 Slow POST 无效。你需要组合使用以下三项:
-
http.Server.ReadHeaderTimeout:控制从连接建立到收到完整请求头(含双\r\n\r\n)的最大时间,建议设为5 * time.Second -
http.Server.ReadTimeout:**不推荐单独依赖**,它包含请求头 + 请求体读取总时间,但无法区分是头慢还是体慢 - 在 handler 中主动控制请求体读取:用
http.MaxBytesReader包裹r.Body,并配合context.WithTimeout控制单次Read调用
示例:
func slowPostHandler(w http.ResponseWriter, r *http.Request) {
// 限制整个请求体读取不超过 10 秒
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
<pre class="brush:php;toolbar:false;">// 限制最多读取 5MB,防恶意大 body
limitedBody := http.MaxBytesReader(w, r.Body, 5*1024*1024)
r.Body = http.MaxBytesReader(w, limitedBody, 5*1024*1024)
// 使用带超时的 reader(需自定义 io.Reader 或用第三方库如 golang.org/x/net/contextio)
// 更简单做法:直接用 io.CopyN + context,或改用 streaming 方式处理
}
避免 ParseForm 自动触发慢读
r.ParseForm() 和 r.FormValue() 会隐式调用 r.Body.Read,且无超时控制。这是最常被忽略的陷阱:
- 若请求是
POST且含application/x-www-form-urlencoded,ParseForm会一次性读完全部 body - 攻击者设
Content-Length: 10000000,再每 5 秒发 1 字节,ParseForm就卡住 5 小时 - 解决方法:禁用自动解析,改用
io.LimitReader(r.Body, maxBodySize)手动读,或提前校验r.ContentLength是否合理
检查方式:
if r.ContentLength > 5*1024*1024 {
http.Error(w, "Request Entity Too Large", http.StatusRequestEntityTooLarge)
return
}
生产环境还需补充的防护层
单靠 Go 应用层配置不够稳健,尤其面对大规模 Slow POST 时:
- 前置反向代理(如 Nginx)设置
client_body_timeout和client_max_body_size,在入口处拦截 - 启用内核级连接限制:
net.core.somaxconn、net.ipv4.tcp_fin_timeout配合连接跟踪模块 - 监控活跃连接数和平均读取延迟,当
http.Server.ConnState中StateNew或StateActive持续高于阈值时告警 - 不要复用
http.DefaultServeMux,避免中间件意外绕过超时逻辑
最易被忽略的一点:Go 的 http.Server 没有类似 Tomcat 的 maxConnections 或 Nginx 的 limit_conn 机制,必须靠外部组件或自研连接计数器补足。











