cve-2020-1938 tomcat任意文件读取or包含(任意文件读取分析)

昨天下午,偶然在某群看到了cnvd的官网通告,知道了这个漏洞,当时还在和朋友戏言别又是nginx rce那种水洞,但直到了晚上安恒的初步分析文出来后,才确定这个漏洞可以在默认配置的tomcat环境下实现任意文件读取,也可以结合文件上传进行rce来getshell。并且影响版本范围比较大,在fofa上用dork protocol=”ajp”可以发现大概有40多w的网站受到影响。于是,一等exp公布到手,便开始了漫漫的调试的过程。在调试之前,我们先思考三个问题:

1
2
3
1.文件读取和包含是否有目录限制,能否跨目录读取或者包含任意后缀的文件
2.以前对文件包含漏洞的理解和接触更多是php,java的文件包含漏洞实现原理和过程是怎样的
3.servlet的映射流程是怎样的

接下来,我们从两个不同的漏洞利用方法分别来说明这三个问题。

0x01 任意文件读取

通过idea启动tomcat,并运行exp,下断点,程序会先进入/org/apache/coyote/ajp/AjpProtocol.class createProcessor方法,进行进程初始化,其中processor.tmpMB,processor.bodyMessage值都为null
upload successful

upload successful

接下来进入ajp协议的socket连接初始化过程,然后按照流程进入值得我们关注的方法-prepareRequest(),该方法为ajp设置发送请求报文及格式的方法,在/org/apache/coyote/ajp/AbstractAjpProcessor.class line469,会根据attributeCode的值的情况会请求的不同属性设置
不同的值。跟进attributeCode值为10的情况会将读this.tmpMB缓冲区两次的值分别赋予n,v变量。并且会根据n值是否等于AJP_LOCAL_ADDR、AJP_REMTE_PORT、AJP_SSL_PROTOCOL去依次给请求不同属性的值设置成v。如果n值均不等于上面3个值,那会通过this.request.setAttribute将n的值赋予请求属性,属性的值设置成v。因此,这里n,v其实可以理解为变量名和变量名的值。让我们看看poc,我们可控的3个属性分别是什么:
upload successful

很明显,这里其实就给请求依次赋予了javax.servlet.include.request_uri、javax.servlet.include.path_info、javax.servlet.include.servlet_path的属性,并且值为/、/web-inf/web.xml、/,所以这里就是我们的可控点,属性名和属性值均可以完全控制,所以我们可以通过控制这3个属性的值去发送相应的ajp请求。
upload successful

upload successful

接下来进入到parseHost获取到host为localhost,会进入到this.adapter.service对请求进行封装,在/org/apache/coyote/ajp/AjpProcessor.class line 128:
upload successful

跟进adapter.sevice():
upload successful

跟进org.apache.catalina.connector.Request,其实该类实现的 HttpServletRequest 接口,而HttpServletRequest接口继承的ServletRequest接口。所以这里的service方法会对request进行servlet的映射。我们访问的url为/asdf,根据servlet的映射规则/asdf为静态资源,在除了特殊配制的情况下一般都会去到defalutservlet进行请求处理。
defalutservlet的包名在web.xml中
upload successful

跟进org.apache.catalina.servlets.DefaultServlet,并且请求的方式为get方式,所以关注doGet()方法
upload successful

再跟进this.serveResource,path变量通过 this.getRelativePath获取:
upload successful

this.getRelativePath会将javax.servlet.include.servlet_path和javax.servlet.include.path_info的值拼接在一起作为path。最终组合成WEB-INF/web.xml/。
upload successful

upload successful

接下来path走进this.resources.lookupCache,熟悉tomcat的可以知道,这是tomcat defaultservlet处理静态资源采用的缓存机制。服务端通过resource.lookupCache(path)从服务端缓存中读取资源(仅限于web应用)获得CacheEntry,如果请求资源不存在CacheEntry.exists为false,则返回404。
upload successful

接下来会走进this.copy对返回的内容进行io流读文件操作,将获取的文件内容读出来显示在浏览器上。
upload successful

跟进copy函数,典型的文件io流操作。
upload successful

这就是整个将/web-INF/web.xml读取出来的过程。刚才经过分析我们得知,path是由javax.servlet.include.servlet_path和javax.servlet.include.path_info的值拼接在一起组合而成,那么我们这里把javax.servlet.include.servlet_path改为../../../../../../../../,把javax.servlet.include.path_info改为etc/passwd试试,看能不能跨目录读取到内容。
一路跟进,一直走到getRelativePath返回的值都为../../../../../../../../../../../../etc/passwd
upload successful

但是走到this.resources.lookupCache时,Cache entry.Exists=false,然后就会抛出500异常。因为this.resources.lookupCache就限定了读取的静态资源文件必须为web应用目录下的,所以无法通过../去跨目录。
upload successful

综上,任意文件读取的流程为:

设置ajp请求属性->通过n,v设置请求属性和属性的值->请求静态资源文件,进入defalutservlet映射->拼接path->通过this.resources.lookupCache获得读取的文件缓存->发送get请求->文件读取,如有获取文件内容,则通过io流读取静态资源内容

打赏
  • © 2020 sommous

老板,来杯卡布奇诺~

支付宝
微信