CVE-2021-34429源码分析与漏洞复现
漏洞概述
漏洞名称:Jetty 路径解析多重编码绕过漏洞
漏洞编号:CVE-2021-34429
CVSS 评分:5.3
影响版本:
- Jetty 9.4.37 - 9.4.42
- Jetty 10.0.1 - 10.0.5
- Jetty 11.0.1 - 11.0.5
修复版本: - Jetty ≥ 9.4.43
- Jetty ≥ 10.0.6
- Jetty ≥ 11.0.6
漏洞类型:路径遍历/敏感信息泄露
CVE-2021-34429 是 Eclipse Jetty 服务器因 RFC 3986 合规性改造引入的路径解析逻辑缺陷。攻击者通过构造包含 Unicode 编码点段(如 %u002e
)或空字符(如 %00
)的恶意 URI,绕过 ContextHandler
的安全校验,直接访问 WEB-INF
或 META-INF
目录下的敏感文件(如 web.xml
、classes
等),导致应用配置、数据库凭证等敏感信息泄露。
此漏洞是 CVE-2021-28164 的绕过变种,利用多重编码和空字符截断绕过修复补丁。
技术细节与源码分析
漏洞成因
Jetty 在 9.4.37 版本为符合 RFC 3986 规范,将 URI 解析顺序改为 先规范化后解码:
canonicalPath()
先处理明文点段(.
/..
),但忽略编码形式(如%2e
、%u002e
);decodePath()
后执行解码,将%u002e
转为.
,生成有效点段路径;- 安全校验滞后:
isProtectedTarget()
在规范化后执行,未检测解码后的真实路径。
关键源码分析
(1)路径解析入口(HttpURI.parse()
)
代码定位:org.eclipse.jetty.http.HttpURI#parse
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()
仅过滤明文 .
/..
,无法识别 %u002e
(Unicode 编码点)。
(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等编码形式}
缺陷:输入 /%u002e/WEB-INF/web.xml
经此函数后路径不变,因 %u002e
未被识别为点段。
(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;}
绕过原理:校验时 target
为 /%u002e/WEB-INF/web.xml
,不匹配 /WEB-INF
前缀,但解码后实际路径为 ./WEB-INF/web.xml
。
漏洞复现
环境搭建
1.使用 Vulhub 环境启动漏洞靶机
docker-compose up -d
2.访问访问 http://target:8080,确认服务正常运行
攻击步骤
1.直接访问/WEB-INF/web.xml将会返回404页面
2.使用/%u002e/WEB-INF/web.xml
来绕过限制下载web.xml:
其他利用 Payload
Payload | 技术原理 |
---|---|
/.%00/WEB-INF/web.xml | 空字符截断规范化逻辑 |
/a/b/..%00/WEB-INF/web.xml | 路径遍历 + 空字符组合绕过 |
实际影响
- 敏感信息泄露:
web.xml
(数据库密码)、classes/
(业务逻辑源码); - 攻击成本低:无需认证,仅需构造 URL;
- 横向渗透基础:泄露的配置可能暴露内网接口或密钥。
修复方案
官方修复(Jetty ≥ 9.4.43)
补丁核心:调整路径处理顺序 + 增强校验:
- 先解码后规范化:
// HttpURI.parse() 修改后 _decodedPath = decodePath(rawURI); _path = canonicalPath(_decodedPath); // 先解码后规范化
- 拒绝危险字符:检测编码点段(
%u002e
)、空字符(%00
),直接返回 400 Bad Request。
临时缓解措施
- 重写规则拦截(
jetty.xml
):
作用:将含编码点段的请求重定向至无效路径。<Call name="addRule"> <Arg> <New class="org.eclipse.jetty.rewrite.handler.RewriteRegexRule"> <Set name="regex">.*/(?:\.+/)+.*</Set> <Set name="replacement">/WEB-INF/Not-Found</Set> </New> </Arg> </Call>
- 权限控制:确保
WEB-INF
目录权限禁止非授权访问。
漏洞启示与防御框架
路径解析漏洞防御模型
最佳实践:
- 输入过滤层:拦截含
%u002e
、%00
的请求; - 解码规范化层:强制先解码后规范化;
- 业务校验层:对比规范化前后路径一致性。
附:Jetty 路径漏洞修复史
漏洞 绕过方式 修复版本 修复核心 CVE-2021-28164 %2e
编码点段≥ 9.4.39 先解码后规范化 CVE-2021-28169 双重编码( %2557
)≥ 9.4.40 禁用二次解码 CVE-2021-34429 Unicode 编码 ≥ 9.4.43 拒绝危险字符
参考链接
- CVE-2021-34429 官方通告(Eclipse Jetty)
- 漏洞原理与源码分析(阿里云先知社区)