go网络服务无法靠http.server内置超时参数防御slowwrite攻击,因为writetimeout仅作用于响应体写出阶段,而攻击发生在响应头发出后以极低速率接收响应体,导致goroutine持续占用且超时不触发。

Go网络服务无法靠http.Server内置超时参数防御SlowWrite攻击,因为写超时(WriteTimeout)只作用于响应体写出阶段,而SlowWrite在响应头已发、响应体低速分块写出时才生效——此时连接早已进入活跃状态,goroutine持续占用,WriteTimeout根本不会触发。
SlowWrite攻击到底卡在哪一环
攻击者建立正常HTTP连接,服务端成功返回200和响应头后,客户端故意以极低速率(如每分钟1字节)接收响应体。Go的net/http底层使用bufio.Writer缓冲写入,但只要对方不关闭连接、不触发TCP RST,conn.Write()就可能长期阻塞或反复超时重试,导致goroutine无法释放、文件描述符被长期占用。
- 不是
ReadTimeout能管的事:请求头和body已读完,连接进入“响应中”状态 -
WriteTimeout只限制单次Write()调用耗时,但攻击者让每次Write()都刚好在超时前写1字节,就能绕过 -
IdleTimeout对这种“非空闲但极慢”的连接完全无效
必须在net.Conn层做写操作级超时
标准http.Server不暴露底层conn写控制权,需自定义net.Listener或包装http.ResponseWriter,但更可靠的做法是直接在conn上设写deadline:
- 用
net.ListenConfig{Control: ...}在Accept时设置SetWriteDeadline初始值,例如time.Now().Add(30 * time.Second) - 每次向
conn写数据前,调用conn.SetWriteDeadline(time.Now().Add(5 * time.Second))重置——这是关键,否则一次超时后后续写永远失败 - 捕获
net.ErrWriteTimeout并主动conn.Close(),避免goroutine堆积 - 不要依赖
http.ResponseWriter的Write(),它不透传底层conn错误;改用http.Hijacker或自定义responseWriter实现带deadline的写
配合应用层熔断与连接数硬限
SlowWrite本质是资源耗尽型攻击,单靠超时不够,必须叠加守门机制:
- 用
atomic.Int64统计当前活跃连接数,在Accept()后立刻检查,超限时直接conn.Close()并记录日志——别等TLS握手完成 - 对已接受连接,启动独立goroutine监控其写行为:若连续N次
Write()耗时超过阈值(如3秒),视为可疑,主动断连 - 数据库/外部调用必须用
context.WithTimeout,防止SlowWrite拖垮下游;否则一个慢响应会连锁阻塞整个goroutine栈 - 禁用
Keep-Alive:http.Server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))清空HTTP/2支持,并在响应头加Connection: close
真正棘手的是:SlowWrite攻击没有固定流量特征,也难被CDN或WAF识别,它伪装成合法长连接。你设的每个WriteDeadline都得精确到毫秒级重置,且不能影响正常大文件下载——这要求你清楚区分“业务长响应”和“恶意慢写”,光靠超时参数远远不够。











