1. 项目背景与问题定位
最近在排查线上服务性能问题时,发现一个隐蔽但危害巨大的安全隐患——Slow HTTP DOS攻击。这种攻击方式特别针对基于Tomcat的SpringBoot应用,攻击者通过建立大量慢速HTTP连接耗尽服务器资源。不同于传统暴力型DOS,它只需要极低的带宽就能让服务瘫痪。
SpringBoot默认使用嵌入式Tomcat作为Web容器,虽然简化了部署,但也继承了Tomcat的全部特性,包括其潜在的连接管理缺陷。当恶意客户端故意拖慢请求速度时,Tomcat的worker线程会被长时间占用,最终导致正常请求无法处理。
2. 攻击原理深度解析
2.1 Slow HTTP攻击的三种典型形式
- Slowloris攻击:保持连接打开并缓慢发送HTTP头
- Slow POST攻击:声明大的Content-Length后缓慢发送body
- Slow Read攻击:以极慢速度读取服务器响应
以Slowloris为例,攻击者会:
- 建立数百个HTTP连接
- 每10-60秒发送一个不完整的请求头(如
X-a: b\r\n) - 不发送完整的
\r\n\r\n结束符 - 使连接始终处于"in progress"状态
2.2 Tomcat的线程模型缺陷
嵌入式Tomcat默认配置下:
server.tomcat.max-threads=200 # 默认工作线程数 server.tomcat.max-connections=10000 # 最大连接数当200个worker线程都被慢连接占用时,新请求将进入等待队列。如果队列也满了(默认100),Tomcat直接拒绝连接。
3. 防御方案设计与实现
3.1 连接超时控制
在application.properties中添加:
# 连接建立后等待请求头的最长时间(毫秒) server.tomcat.connection-timeout=20000 # 读取请求体的最长时间 server.tomcat.keep-alive-timeout=30000 # 最大头大小限制(字节) server.tomcat.max-http-header-size=8192关键点:connection-timeout要小于keep-alive-timeout,建议比例为2:3
3.2 线程池优化配置
# 根据业务特点调整(CPU密集型建议<=核心数,IO密集型可2*核心数) server.tomcat.max-threads=50 # 等待队列长度(不宜过大) server.tomcat.accept-count=20 # 最小空闲线程数 server.tomcat.min-spare-threads=53.3 使用Filter实现速率限制
创建SecurityFilter.java:
@WebFilter("/*") public class RequestThrottleFilter implements Filter { private RateLimiter limiter = RateLimiter.create(100); // 每秒100请求 @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException { if(!limiter.tryAcquire()) { ((HttpServletResponse)response).sendError(429); return; } chain.doFilter(request, response); } }4. 高级防护策略
4.1 启用Tomcat的ARP(Application Request Parsing)
在server.xml中配置:
<Connector port="8080" protocol="HTTP/1.1" parseBodyMethods="POST,PUT,PATCH" maxSwallowSize="2097152" />参数说明:
parseBodyMethods:指定需要解析body的方法maxSwallowSize:当客户端中断连接时,最大可忽略的未消费body大小
4.2 使用Web应用防火墙
推荐配置ModSecurity规则:
SecRule REQUEST_HEADERS:Content-Length "@gt 1000000" \ "id:1000,phase:1,deny,msg:'Possible Slow HTTP Attack'"4.3 连接来源验证
对可疑IP实施挑战-响应机制:
@RestController public class ChallengeController { @GetMapping("/challenge") public String challenge(@RequestParam String token) { if(!validateToken(token)) { throw new AccessDeniedException(); } return generateNewToken(); } }5. 测试验证方案
5.1 使用SlowHTTPTest工具测试
安装测试工具:
git clone https://github.com/shekyan/slowhttptest.git cd slowhttptest ./configure && make执行测试:
# Slowloris模式测试 ./slowhttptest -c 1000 -H -g -o slowloris -i 10 -r 200 -t GET -u http://localhost:80805.2 监控指标验证
关键监控项:
tomcat.threads.busy:忙碌线程数tomcat.threads.current:当前线程总数http.sessions.active.max:最大活跃会话数
建议配置告警规则:
alert: - name: ThreadExhaustion condition: tomcat.threads.busy / tomcat.threads.config.max > 0.9 duration: 1m6. 生产环境部署建议
6.1 分级防护策略
| 防护层级 | 措施 | 实施位置 |
|---|---|---|
| 网络层 | DDoS清洗、IP黑名单 | 防火墙/WAF |
| 协议层 | 连接超时控制 | Tomcat配置 |
| 应用层 | 速率限制、挑战响应 | Spring Filter |
6.2 动态调参策略
建议实现配置热更新:
@Configuration @RefreshScope public class TomcatConfig { @Value("${tomcat.timeout}") private int timeout; @Bean public TomcatServletWebServerFactory tomcatFactory() { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); factory.addConnectorCustomizers(connector -> { connector.setProperty("connectionTimeout", String.valueOf(timeout)); }); return factory; } }7. 常见问题排查
7.1 连接重置问题
现象:客户端频繁收到Connection reset异常
排查步骤:
- 检查
server.tomcat.connection-timeout是否过小 - 网络抓包分析TCP挥手过程
- 检查Keep-Alive配置是否冲突
7.2 线程泄漏诊断
使用jstack检测:
jstack -l <pid> | grep -A10 "http-nio-8080-exec"典型线程堆栈:
"http-nio-8080-exec-1" #20 daemon prio=5 os_prio=0 tid=0x00007f8e1c0e8000 nid=0x4df3 waiting on condition [0x00007f8e0a7e7000] java.lang.Thread.State: TIMED_WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000f8508f50> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)8. 性能优化平衡点
防护配置需要根据业务特点调整:
- API服务:可设置较短的超时(10-30s)
- 文件上传:需要适当延长body读取超时
- 长轮询应用:建议单独部署并特殊配置
监控调整公式:
理想超时时间 = P99响应时间 * 安全系数(建议1.5-2)9. 架构级解决方案
对于高安全要求场景,建议:
前端接入层:使用Nginx做反向代理,启用limit_req模块
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; location /api { limit_req zone=api_limit burst=20 nodelay; proxy_pass http://tomcat_backend; }服务网格方案:通过Istio实现全局限流
apiVersion: networking.istio.io/v1alpha3 kind: EnvoyFilter metadata: name: filter-throttle spec: filters: - listenerMatch: listenerType: GATEWAY filterName: envoy.filters.network.http_connection_manager filterType: NETWORK filterConfig: http_filters: - name: envoy.filters.http.local_ratelimit config: stat_prefix: http_local_rate_limiter token_bucket: max_tokens: 100 tokens_per_fill: 10 fill_interval: 1s
10. 终极防护方案
对于金融级安全要求,建议采用四层防护:
- 硬件防护:部署抗DDoS设备
- 协议优化:启用HTTP/2(强制帧传输)
- 容器定制:使用以下Tomcat优化版配置类:
public class HardenedTomcatFactory extends TomcatServletWebServerFactory { @Override protected void postProcessContext(Context context) { // 禁用危险方法 SecurityConstraint constraint = new SecurityConstraint(); constraint.setUserConstraint("CONFIDENTIAL"); SecurityCollection collection = new SecurityCollection(); collection.addPattern("/"); collection.addMethod("TRACE"); collection.addMethod("OPTIONS"); constraint.addCollection(collection); context.addConstraint(constraint); // 启用严格会话管理 context.setSessionTimeout(30); context.setSessionCookiePath("/"); context.setUseHttpOnly(true); } }