Nexus Repository Manager 3 未授权目录穿越漏洞(CVE-2024-4956)
https://github.com/vulhub/vulhub/blob/master/nexus/CVE-2024-4956/README.zh-cn.md
https://ares-x.com/2020/04/20/IDEA远程调试Docker中程序的方法/
https://t.zsxq.com/MDrfR
漏洞类型:未授权任意文件读取(路径穿越)
POC
GET /%2F%2F%2F%2F%2F%2F%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd
两个问题
- 为什么要进行url编码,不能直接传入
../../../../../../../etc/passwd
吗? - 为什要在路径穿越利用中,需要构造这么多url编码后的
/
,即%2F
?
调试环境
docker ps
docekr exec -it container_id bash
pwd
docker cp container_id:/opt/sonatype ./sonatype
补丁分析
参考1ue师傅,照猫画虎。
临时修补措施
https://support.sonatype.com/hc/en-us/articles/29412417068819-Mitigations-for-CVE-2024-4956-Nexus-Repository-3-Vulnerability
删除了jetty的资源配置,不再使用jetty去挂载public目录下的资源文件。
在Jetty的配置文件中,
<Set name="resourceBase"><Property name="karaf.base"/>/public</Set>`
这一行的作用是将karaf.base
目录下的public
子目录设置为资源基目录,即从这个目录中提供静态资源。
- 移除资源配置:Jetty不再知道
public
目录的存在,也就无法从这个目录中提供任何静态资源。 - 取消挂载:
public
目录下的资源文件不再与任何URL路径关联。客户端请求这些资源时,Jetty将无法找到并提供它们。
简单来说,删除这行配置意味着Jetty不再负责处理和提供public
目录中的静态资源文件,可能需要其他方式来管理和提供这些资源(例如,使用不同的Web服务器或将静态资源打包到应用程序中)。
补丁包进行对比
https://github.com/sonatype/nexus-public
在IDEA里面ctrl+D即可进行二进制对比。
筛选*.java
,可以看出有3个java文件改动。
分别是
WebResourceServicelmpl.java
、
PublicFilesWebResourceBundle.java
RaptureWebResourceBundle.java
根据师傅们的提示,漏洞代码位于WebResourceServicelmpl.java
,看名字是关于web资源的业务逻辑的实现类。
这里1ue师傅说是jetty相关的漏洞,这里意思应该是漏洞与servlet相关,而nexus中托管servlet的容器是jetty。
Jetty是一个纯粹的基于Java的网页服务器和Java Servlet容器。
其中删除了如下代码,直接在此处断点,找调用链。
// 3) third, look into WAR embedded resources
if (resource == null) {
URL url;
try {
url = servletContext.getResource(path);
if (url != null && !isDirectory(url)) {
resource = new UrlWebResource(url, path, mimeSupport.guessMimeTypeFromPath(path));
log.trace("Found servlet-context resource: {}", resource);
}
}
catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
private boolean isDirectory(final URL url) {
if ("file".equals(url.getProtocol())) {
File file = new File(url.getFile());
return file.isDirectory();
}
return false;
}
idea搜索WebResourceServicelmpl
定位代码,进行远程调试
路由
关键代码调用
WebResourceServlet#doGet
WebResource resource = webResources.getResource(path); // 获取对应路径的资源
WebResourceServiceImpl#getResource
url = servletContext.getResource(path);
WebAppContext#getResource
Resource resource = WebAppContext.this.getResource(path);
resource = super.getResource(uriInContext);
Resource resource = _baseResource.addPath(path); //_baseResource是jetty.xml中配置的public目录
PathResource#addPath
if (URIUtil.canonicalPath(subPath) == null) //绕过
return "/".equals(subPath) ? this : new PathResource(this, subPath);
PathResource#PathResource
this.uri = URIUtil.addPath(parent.uri, childPath);
URIUtil#addPath
encodePath(buf, path, offset);
POC分析
- 直接传入
../../../../../../../etc/passwd
会怎么样?
400,1ue师傅说这是正常的,这里暂时存疑。
- 传入
..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd
呢?
jetty会进行一层解码,但是依旧400。
- 双重url编码,传入
..%252F..%252F..%252F..%252F..%252F..%252F..%252Fetc%252Fpasswd
依旧不行。
P牛:
为什么paylaod是这样的?
%2F%2F%2F%2F%2F%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd
- 首先这个payload可以通过jeety传递到后端,通过编码避免斜线后第一个字符出现
..
或者../
造成400. - 利用多级
///
绕过canonicalPath的检查。
URIUtil.canonicalPath 函数通常用于规范化 URI 路径。规范化路径的过程包括移除冗余的路径元素(如 . 和 ..),以及处理重复的斜杠 /。
与spring中的cleanPath()函数一样,在处理路径的时候将空的字符串也认为是一个目录。
也就是
canonicalPath("//../etc/passwd")
输出
/etc/passwd
/我是空字符,但是cleanPath()和canonicalPath()认为我是空目录/../etc/passwd
这里的../实际上是跨了一层空目录,通过了canonicalPath
的检查。
但系统认为//不是一个目录,会通过../跳到上级目录。
至此就导致了路径穿越+任意文件读取。
在后面拼接目录的时候尽管存在多级//
(canonicalPath眼中的空目录),但系统选择无视,而且file协议是支持的。注:file://中的//
也不是必要的
补丁绕过?
- 临时修补措施
去除掉jetty中public的配置,猜想在这一步由于获取不到_baseResource后直接抛出异常了。
- 删除了入口点,提供了别的接口访问。