环境搭建
使用官方的docker镜像
docker pull sonatype/nexus3:3.68.0-java8
使用命令 docker inspect + id 查看镜像的启动命令,可以通过 INSTALL4J_ADD_VM_PARAMS 来添加启动参数
修改参数,添加调试端口
docker run -d -p 8081:8081 -p 5005:5005 --name nexus_3.68.0 -e INSTALL4J_ADD_VM_PARAMS="-Xms2703m -Xmx2703m -XX:MaxDirectMemorySize=2703m -Djava.util.prefs.userRoot=/nexus-data/javaprefs -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005" sonatype/nexus3:3.68.0-java8
分析
根据官方的临时修复方案,删除(basedir)/etc/jetty/jetty.xml
<Set name="resourceBase"><Property name="karaf.base"/>/public</Set>
这一行
整个xml
<New id="NexusHandler" class="org.sonatype.nexus.bootstrap.jetty.InstrumentedHandler">
<Arg>
<New id="NexusWebAppContext" class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="descriptor"><Property name="jetty.etc"/>/nexus-web.xml</Set>
<Set name="resourceBase"><Property name="karaf.base"/>/public</Set>
<Set name="contextPath"><Property name="nexus-context-path"/></Set>
<Set name="throwUnavailableOnStartupException">true</Set>
<Set name="configurationClasses">
<Array type="java.lang.String">
<Item>org.eclipse.jetty.webapp.WebXmlConfiguration</Item>
</Array>
</Set>
</New>
</Arg>
</New>
public 下的资源可以直接进行访问,比如此时访问
http://127.0.0.1:8081/robots.txt
‘
org.sonatype.nexus.internal.webresources.WebResourceServlet
会对请求进行处理,这里获取pathinfo
并传入org.sonatype.nexus.internal.webresources.WebResourceServiceImpl#getResource
getResource
方法又会调用org.eclipse.jetty.webapp.WebAppContext#getResource
这里继续调用org.eclipse.jetty.server.handler.ContextHandler#getResource
,然后就到了漏洞最关键的部分,这里的baseResource
就是之前在jetty.xml里定义的web下的/public
目录,所以临时修复也在这,如果删除baseResource
的定义,这里返回null,也就不会走到后续流程里,避免了漏洞。
所以,我们继续跟进addPath
方法,这里会调用URIUtil.canonicalPath
,canonicalPath
是关键
构造路径穿越
我们以 /../../../../../../etc/passwd
为例,来看看处理的逻辑,但是在请求中发现请求/../../../../../../etc/passwd
返回400
canonicalPath 规范化路径
在断点过程中,发现在jetty层会主动调用一次URIUtil.canonicalPath
方法,来看看逻辑
看第一部分,刚开始,把slash
属性设置为true,然后遍历,如果遇到/
,就把slash设置为true,然后判断/
后面的内容,如果是.
,则跳出循环,此时i为1,往下走,如果是其他值,就把slash
设置为false。
我们看到跳出循环,接着看关键的部分,这里会将路径中的第一个 '.'
之前的部分添加到 canonical
中。这里就是/
,然后处理canonical
,把dots设置为1,用来标记.
的数量,++i,此时i为2
然后判断下一位,如果依然为.
,此时dots
设置为2,接着continue,下一位是/
,所以调用doDotsSlash
此时,判断canonical
的长度只有1,所以doDotsSlash
方法返回true,然后就返回null了,所以会出现400
不过,当//..//..//..//..//..//..//etc/passwd
,再进判断canonical
为 //
,等于2,不会返回true,并且还会去掉一个/
,最终返回
/../../../../../../etc/passwd
,所以,回到addPath
方法,new PathResource
,把/public
目录和路径进行拼接
最终在UrlWebResource
里读取文件
发请求的时候记得编码。