Tomcat漏洞修复升级------被遗忘的中间件安全
安全通告来了
等保测评报告下发。
高危漏洞:Tomcat 多个漏洞,包括:
- CVE-2024-XXXX:Tomcat 信息泄露
- CVE-2024-YYYY:Tomcat 请求走私
- 总计:45个漏洞需要修复
修复方案:从 Tomcat 8 升级到 Tomcat 9.0。
听起来很简单?但我们在政务系统上跑了6年的 Tomcat 8,上面挂了 30+ 个应用,直接升级?
没那么简单。
第一次升级
目标
Tomcat 8.5 → Tomcat 9.0.x
问题1:过滤器不兼容
严重: Exception starting filter [encodingFilter]
java.lang.AbstractMethodError
原因:Tomcat 9 重构了 Filter 接口,旧版过滤器不兼容。
解决 :重写自定义过滤器,不再继承旧版 BaseFilter,直接实现 javax.servlet.Filter 接口。
java
// 旧代码(Tomcat 8)
public class EncodingFilter extends BaseFilter {
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) {
req.setCharacterEncoding("UTF-8");
chain.doFilter(req, res);
}
}
// 新代码(Tomcat 9)
public class EncodingFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
req.setCharacterEncoding("UTF-8");
chain.doFilter(req, res);
}
}
问题2:server.xml 配置变更
Tomcat 9 废弃了部分配置项:
xml
<!-- Tomcat 8 旧配置 -->
<Connector port="8080" protocol="HTTP/1.1"
URIEncoding="UTF-8"
compression="on"
compressableMimeType="text/html,text/xml,text/plain" />
<!-- Tomcat 9 新配置 -->
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
URIEncoding="UTF-8"
compression="on"
compressibleMimeType="text/html,text/xml,text/plain" />
注意 :compressableMimeType 改成了 compressibleMimeType(拼写修正)。
问题3:TLS 协议变更
Tomcat 9 默认禁用 TLS 1.0/1.1,只启用 TLS 1.2/1.3。
但我们的旧客户端只支持 TLS 1.0。
xml
<!-- 兼容旧客户端 -->
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
SSLEnabled="true"
sslProtocols="TLSv1.2,TLSv1.1,TLSv1"
ciphers="TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,...">
教训:安全升级不能一刀切,需要考虑生态兼容性。
第二次升级
背景
漏洞扫描又来了,这次报了 45个漏洞。
目标:Tomcat 8.5 → Tomcat 9.0.x(再次尝试)
新问题:编码问题
tomcat9 编码问题
现象:页面中文乱码。
排查:
- 检查 URI 编码:
URIEncoding="UTF-8"已配置 - 检查请求体编码:
request.setCharacterEncoding("UTF-8")已调用 - 检查 Tomcat 9 默认编码:Tomcat 9 默认使用
UTF-8,但旧应用使用ISO-8859-1
根因 :Tomcat 8 默认 URI 编码是 ISO-8859-1,Tomcat 9 改成了 UTF-8。
java
// Tomcat 8 行为
request.getParameter("name") // 如果传的是中文,可能乱码
// Tomcat 9 行为
request.getParameter("name") // 默认 UTF-8 解码
解决 :显式配置 URIEncoding="UTF-8",并排查所有应用层编码处理。
降级
回滚记录
tomcat9有内容不兼容,降级到tomcat7.99
原因:某个第三方组件只在 Tomcat 8 上验证过,Tomcat 9 上出现类加载器问题。
java.lang.NoClassDefFoundError: javax/ws/rs/ext/MessageBodyReader
根因:Tomcat 9 使用新的模块化类加载器,旧组件的 META-INF/services 机制失效。
决策:降级到 Tomcat 8.5(即日志中的 "7.99",实际是 8.5)。
经验:中间件升级要考虑全链路兼容性,特别是第三方闭源组件。
第三次修复
最新记录
tomcat安全漏洞整改
这次换了个策略------不升级大版本,只打补丁。
补丁方案------详细操作步骤
第一步:确认当前版本
bash
${CATALINA_HOME}/bin/version.sh
# 确认当前为 Tomcat 8.5.xx
第二步:下载当前大版本的最新patch
去 Tomcat 官网下载当前 8.5.x 的最新版本:
bash
wget https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.100/bin/apache-tomcat-8.5.100.tar.gz
第三步:只替换二进制文件,不动应用
bash
# 停止Tomcat
${CATALINA_HOME}/bin/shutdown.sh
# 备份(全量备份保底)
cp -r ${CATALINA_HOME} ${CATALINA_HOME}_bak_$(date +%Y%m%d)
# 只替换bin、lib目录,不动webapps、conf、logs
cp -r apache-tomcat-8.5.100/bin/* ${CATALINA_HOME}/bin/
cp -r apache-tomcat-8.5.100/lib/* ${CATALINA_HOME}/lib/
# 启动
${CATALINA_HOME}/bin/startup.sh
webapps目录不动(里面是应用),conf里的server.xml、web.xml等尽量保留原有配置。
第四步:安全加固配置
版本升级只是第一步,配置加固同样重要:
4.1 关闭 AJP 连接器(AJP 是漏洞重灾区,如 CVE-2020-1938 "Ghostcat"):
xml
<!-- conf/server.xml ------ 注释掉AJP -->
<!-- <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> -->
<!-- 隐藏版本号 -->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
URIEncoding="UTF-8"
server="Apache" />
4.2 删除默认应用(防止暴露版本信息和后台入口):
bash
rm -rf ${CATALINA_HOME}/webapps/docs
rm -rf ${CATALINA_HOME}/webapps/examples
rm -rf ${CATALINA_HOME}/webapps/manager
rm -rf ${CATALINA_HOME}/webapps/host-manager
4.3 安全响应头(conf/web.xml 或应用 Filter):
xml
<filter>
<filter-name>HttpHeaderSecurityFilter</filter-name>
<filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>xssProtectionEnabled</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>antiClickJackingEnabled</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>blockContentTypeSniffingEnabled</param-name>
<param-value>true</param-value>
</init-param>
</filter>
4.4 禁用目录列表 (conf/web.xml,listings 设为 false)
4.5 限制 HTTP 方法(只允许 GET/POST,禁止 PUT、DELETE、TRACE、OPTIONS):
xml
<security-constraint>
<web-resource-collection>
<web-resource-name>Restricted Methods</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>TRACE</http-method>
<http-method>OPTIONS</http-method>
</web-resource-collection>
<auth-constraint />
</security-constraint>
第五步:验证
bash
# AJP是否关闭
netstat -tlnp | grep 8009 # 应该无输出
# 版本号是否隐藏
curl -I http://localhost:8080/
# 目录列表是否禁用
curl http://localhost:8080/some-nonexistent-path/ # 应返回403/404
# 漏洞扫描
# 用安全扫描工具(Nessus/OpenVAS)重新扫描确认漏洞已修复
性能综合评估:patch版本升级后,启动时间、内存占用、响应时间、并发能力均无明显变化------patch版本本质上只改了 bug 修复,不影响性能。
升级版本 vs 安全加固效果对比:
| 措施 | 解决的问题 | 效果 |
|---|---|---|
| 升级到最新patch | 修复已知CVE | 覆盖大量历史漏洞 |
| 关闭AJP | AJP协议漏洞 | 消除最危险的攻击面 |
| 隐藏版本号 | 信息泄露 | 增加攻击难度 |
| 删除默认应用 | 信息泄露、管理入口暴露 | 减少攻击面 |
| 安全响应头 | XSS、点击劫持 | 防御常见Web攻击 |
| 禁用目录列表 | 敏感文件暴露 | 防止源码/配置泄露 |
| 限制HTTP方法 | 不安全方法 | 减少攻击向量 |
为什么最终选择打补丁而不是升级?
| 方式 | 升级大版本 | 打补丁 |
|---|---|---|
| 风险 | 高(API不兼容) | 低(接口不变) |
| 时间 | 2-3天(含回归测试) | 2-3小时 |
| 效果 | 彻底修复 | 临时修复 |
| 长期维护 | 好 | 差(后续还有漏洞) |
现实选择:业务不能停,只能选低风险方案。
线程限制问题
除了漏洞,Tomcat 还有性能相关的问题:
Tomcat 最大线程限制
人脸识别离线版本测试及压力测试、接口定义、tomcat最大线程限制
场景:人脸认证服务部署在 Tomcat 上,并发认证时响应变慢。
排查:Tomcat 默认最大线程数 200,活体检测算法处理慢(300ms/次),当并发超过 200 时,请求排队。
优化:
xml
<Connector port="8080" protocol="HTTP/1.1"
maxThreads="500"
acceptCount="100"
maxConnections="1000"
connectionTimeout="30000" />
教训:中间件默认配置是按通用场景设计的,特定业务需要针对性调优。
升级操作手册
1. 准备工作
bash
# 1. 备份当前配置
cp -r $CATALINA_HOME/conf $BACKUP_DIR/tomcat-conf-$(date +%Y%m%d)
cp -r $CATALINA_HOME/webapps $BACKUP_DIR/tomcat-webapps-$(date +%Y%m%d)
cp -r $CATALINA_HOME/lib $BACKUP_DIR/tomcat-lib-$(date +%Y%m%d)
# 2. 记录当前版本
$CATALINA_HOME/bin/version.sh
# 3. 导出所有配置项
$CATALINA_HOME/bin/catalina.sh configtest 2>&1
2. 配置文件迁移清单
yaml
# Tomcat 升级配置迁移清单
files_to_migrate:
- conf/server.xml
- conf/web.xml
- conf/context.xml
- conf/catalina.properties
- conf/logging.properties
- bin/setenv.sh (自定义JVM参数)
check_items:
- 自定义阀门(Valve)兼容性
- JNDI数据源配置
- Realm配置
- 连接器(Connector)配置
- 类库冲突检查
3. 应用兼容性检查清单
yaml
app_compatibility:
servlet_api: 3.1 → 4.0
jsp_api: 2.3 → 2.4
el_api: 3.0 → 4.0
websocket_api: 1.1 → 2.0
breaking_changes:
- javax.servlet → jakarta.servlet (Tomcat 10+)
- 默认URI编码变更
- 类加载器行为变更
- JSP编译方式变更
4. 回滚方案
yaml
rollback_plan:
condition: "任一应用出现兼容性问题"
steps:
1. 停止Tomcat服务
2. 恢复备份的conf目录
3. 恢复备份的webapps目录
4. 恢复备份的lib目录
5. 启动旧版本Tomcat
6. 验证所有应用正常
rollback_time: < 30分钟
漏洞修复清单
| 漏洞数 | 修复方式 | 耗时 | 结果 |
|---|---|---|---|
| 10+ | 升级到9.0 | 3天 | 部分应用兼容性问题 |
| 45 | 升级到9.0 | 2天 | 编码问题,需修复 |
| - | 降级到8.5 | 1天 | 第三方组件不兼容 |
| 若干 | 打补丁 | 3小时 | 完成修复 |
经验教训
1. 中间件升级要"慢"
- 不要跳版本:8 → 9,中间最好验证 8.5 最新补丁版
- 不要全量:先升级一个非核心应用试水
- 不要限期:留够回归测试时间
2. 兼容性测试要"全"
需要测试的方面:
- 所有应用:30+个应用逐个验证
- 所有功能:登录、查询、导出、打印
- 所有第三方:报表引擎、工作流引擎、消息中间件
- 所有客户端:浏览器版本、操作系统、手机型号
3. 漏洞修复要"快"
- 等保要求:高危漏洞 7天内 修复
- 中危漏洞:30天内 修复
- 低危漏洞:90天内 修复
4. 架构优化要"远"
长期来看,更好的方案是:
yaml
long_term_plan:
# 方案1:使用嵌入式Tomcat(Spring Boot)
- 应用自带Tomcat,独立升级
- 每个应用互不影响
# 方案2:使用Nginx反向代理 + 多版本Tomcat
- Nginx统一入口
- 不同应用使用不同Tomcat版本
# 方案3:迁移到云原生
- 容器化部署
- 中间件独立升级
- 蓝绿发布
最后的话
Tomcat 就像家里的水管------平时没人关注,但一旦漏水,整个房子都要遭殃。
Tomcat 升级的三大悖论:
- 安全悖论:越早升级越安全,但越早升级兼容性越差
- 维护悖论:越老的版本漏洞越多,但越老的版本越稳定
- 管理悖论:漏洞修复要求快,但政务系统变更要求慢
最终建议:
- 生产环境:大版本不动,只打安全补丁
- 测试环境:逐步验证新版本
- 新项目:直接用 Spring Boot 嵌入式 Tomcat
- 旧项目:等系统重构时一并升级