0x00 前言

前段时间Jetty爆出了两个CVE,Vulhub也更新了该漏洞靶场,就简单地看下吧。

0x01 Jetty URI路径限制绕过漏洞(CVE-2021-28164)

漏洞原理

Jetty 9.4.37引入对RFC3986的新实现,而URL编码的.字符被排除在URI规范之外,这个行为在RFC中是正确的,但在servlet的实现中导致攻击者可以通过%2e来绕过限制,下载WEB-INF目录下的任意文件,导致敏感信息泄露。该漏洞在9.4.39中修复。

实际测试中发现,是能同时绕过限制来下载WEB-INF和META-INF两个目录下的任意文件的(前提是存在该目录)。

影响版本

  • 9.4.37.v20210219
  • 9.4.38.v20210224

环境搭建

参考Vulhub:https://github.com/vulhub/vulhub/tree/master/jetty/CVE-2021-28164

漏洞复现

正常访问页面如下:

尝试访问/WEB-INF/web.xml,返回404:

在Web路径前面添加URL编码的./即可权限绕过查看到web.xml的敏感信息:

1
/%2e/WEB-INF/web.xml

../也是可以的:

1
/noexist/%2e%2e/WEB-INF/web.xml

调试分析

在运行Jetty的命令中添加IDEA中远程调试的参数然后IDEA连接即可:

1
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar start.jar

下面看下一些关键点即可。

场景一:访问/WEB-INF/web.xml

在该漏洞版本中,可以看到Jetty Server在初始化HttpConnection类时,会将HTTP解析器遵从的是RFC7230_LEGACY:

往下调试,HttpURI类的parse()函数在处理URL路径时,在识别到;?#/等字符时会有一个检测URI的函数checkSegment()来对URI中是否存在..;%2e%2e%2e等特殊字符进行检测:

接着在Request类的setMetaData()函数是设置请求相关的方法类型、URI和请求路径信息等,其中会判断该URI是否存在上述的特殊字符,这里没有就跳过其中的代码逻辑:

设置完请求各项内容之后,往下就会调用到HTTPChannel中的action——Dispatch分发请求:

往下会看到会调用isProtectedTarget()函数检测URL路径是否为保护路径,是的话则直接将响应设置为404:

跟进isProtectedTarget()函数,这里保护目标含有/web-inf且忽略大小写和URL路径进行比较,我们访问的路径刚好匹配因此返回true:

再往后,就是返回404响应然后结束整个请求响应处理的过程了。

场景二:访问/%2e/WEB-INF/web.xml

直接调试到checkSegment()函数中,识别到%2e并标记了出来:

往下,在Request类的setMetaData()函数中进入了hasAmbiguousSegment()判断为真的逻辑中,其中compliance就是RFC7230_LEGACY、其中的sections并不包含NO_AMBIGUOUS_PATH_SEGMENTS这一项,因此直接放行往下执行进行path的URL解码然后设置到PathInfo中:

由于path以/./开头,绕过了isProtectedTarget()函数的检测,从而导致继续往下正常处理请求返回内容:

往下就是chain.doFilter()调用拦截器,然后就是doGet()发送请求获取响应内容回来:

OK,至此我们已经发现漏洞产生的根源所在就是Jetty遵循的RFC7230_LEGACY没有包含NO_AMBIGUOUS_PATH_SEGMENTS即RFC3986、从而并没有对识别到的%2e进行处理。

我们看到NO_AMBIGUOUS_PATH_SEGMENTS中链接的RFC3986的URL,其中就说到了会把URI路径中的...删除掉的操作:https://datatracker.ietf.org/doc/html/rfc3986#section-3.3

The path segments “.” and “..”, also known as dot-segments, aredefined for relative reference within the path name hierarchy. Theyare intended for use at the beginning of a relative-path reference(Section 4.2) to indicate relative position within the hierarchicaltree of names. This is similar to their role within some operatingsystems’ file directory structures to indicate the current directoryand parent directory, respectively. However, unlike in a filesystem, these dot-segments are only interpreted within the URI pathhierarchy and are removed as part of the resolution process (Section5.2).

补丁分析

这里简单看下9.4.40版本的代码修复情况。

在RFC7230_LEGACY的sections中,添加了NO_AMBIGUOUS_PATH_PARAMETERS:

接着在判断存在歧义字符的为true之后的那段代码逻辑中,由于URI没有歧义参数因此没法直接进入第三个判断条件逻辑直接报错,而是继续往下执行:

接着就判断URI存在歧义字符的话就直接调用URIUtil.canonicalPath()函数进行URI规范化处理,其是专门处理...字符的,从而杜绝了该漏洞:

防御方法

  • 升级到9.4.39及以上版本;

  • 或者更新start.d/http.ini包含以下内容来启用HttpCompliance模式RFC7230_NO_AMBIGUOUS_URIS:

    1
    jetty.http.compliance=RFC7230_NO_AMBIGUOUS_URIS

0x02 Jetty Servlets URI路径限制绕过漏洞(CVE-2021-28169)

漏洞原理

Jetty Servlets中的ConcatServlet、WelcomeFilter类存在多重解码问题,当应用到这两个类之一时,攻击者就可以利用双重URL编码绕过限制来访问WEB-INF目录下的敏感文件,造成敏感信息泄露。

影响版本

  • 9.x系列 <= 9.4.40
  • 10.x系列 <= 10.0.2
  • 11.x系列 <= 11.0.2

环境搭建

参考Vulhub:https://github.com/vulhub/vulhub/tree/master/jetty/CVE-2021-28169

漏洞复现

正常访问是个Example页面:

查看页面源码,其中link标签的href属性值是使用到了ConcatServlet类来优化静态文件的加载:

1
<link rel="stylesheet" href="/static?/css/base.css&/css/app.css">

基于这种访问方式尝试直接访问WEB-INF下的文件是会返回404的:

1
/static?/WEB-INF/web.xml

对W进行双重URL编码则成功绕过限制访问得到敏感文件:

1
/static?/%2557EB-INF/web.xml

这里看到确实设置了ConcatServlet类来优化静态文件加载。

针对WelcomeFilter类的测试可以参考官网GitHub的代码:https://github.com/eclipse/jetty.project/pull/6261/commits/c704b8100a1ccc36f4bb8b80a96f3375dde8d182#diff-70d52a090f69fbcbb6fb9d0899c514474c25c4ea79263f81cbf0e87e3c103bd5

调试分析

在运行Jetty的命令中添加IDEA中远程调试的参数然后IDEA连接即可:

1
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar start.jar

之前在关于CVE-2021-28164的文章中已经简单调试过Jetty的主要路由处理过程了,后面就只看本次漏洞的关键点。

直接在ConcatServlet类的doGet()函数中下断点。

访问/static?/WEB-INF/web.xml

在断点中看到,先获取请求查询内容即URI中?号之后/WEB-INF/web.xml,接着根据&即做参数切分,再逐个参数进行操作(URL解码和...等字符处理),最后判断处理后的path值是否以/WEB-INF//META-INF/开头,是的话直接返回404:

可以看到,还想用/.//../等方式已经行不通了,但是URL解码那块代码逻辑还需要继续跟进分析。

访问/static?/%2557EB-INF/web.xml

调试到调用URIUtil.decodePath()函数进行解码的地方:

调试发现只进行了一次URL解码操作:

看下关键的处理逻辑,就是逐个获取请求参数的字符,当遇到%时会对其后面两位字符进行URL解码并替换结果,但是可以看到仅仅替换了一次,这就是漏洞根源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Utf8StringBuilder builder = null;
int end = offset + length;
for (int i = offset; i < end; i++)
{
char c = path.charAt(i);
switch (c)
{
case '%':
if (builder == null)
{
builder = new Utf8StringBuilder(path.length());
builder.append(path, offset, i - offset);
}
if ((i + 2) < end)
{
char u = path.charAt(i + 1);
if (u == 'u')
{
// TODO remove %u support in jetty-10
// this is wrong. This is a codepoint not a char
builder.append((char)(0xffff & TypeUtil.parseInt(path, i + 2, 4, 16)));
i += 5;
}
else
{
builder.append((byte)(0xff & (TypeUtil.convertHexDigit(u) * 16 + TypeUtil.convertHexDigit(path.charAt(i + 2)))));
i += 2;
}
}
else
{
throw new IllegalArgumentException("Bad URI % encoding");
}

break;

case ';':
...

default:
if (builder != null)
builder.append(c);
break;
}
}

补丁分析

参考官方在9.4.41版本的修复commit:https://github.com/eclipse/jetty.project/pull/6261/commits/c704b8100a1ccc36f4bb8b80a96f3375dde8d182

ConcatServlet类的修复方法就是将path替换为part,即使用原始路径字符串作为分发器就会再次进行URL解码:

防御方法

升级到9.4.41, 10.0.3, 11.0.3及以上版本。

0x03 参考

https://github.com/eclipse/jetty.project/security/advisories/GHSA-v7ff-8wcx-gmc5

https://github.com/eclipse/jetty.project/security/advisories/GHSA-gwcr-j4wh-j3cq