Apache Tomcat 任意文件读取漏洞复现与分析
漏洞原因:
由于Tomcat对ajp传递过来的数据的处理存在问题,导致我们可以控制“javax.servlet.include.request_uri”,“javax.servlet.include.path_info”,“javax.servlet.include.servlet_path”,这三个参数,从而读取任意文件。
漏洞环境:
Tomcat 8.0.50版本搭建,
http://archive.apache.org/dist/tomcat/tomcat-8/v8.0.50/
下载对应的Tomcat源码文件,和可执行文件。
注意:版本需一致,如下图所示:
访问http://localhost:8080/出现下图即搭建成功。
漏洞复现:
成功执行exp非授权访问到指定文件
漏洞分析:
tomcat默认的conf/server.xml中配置了2个Connector,一个为8080的对外提供的HTTP协议端口,另外一个就是默认的8009 AJP协议端口,AJP 协议可以理解为 HTTP 协议的二进制性能优化版本,它能降低 HTTP 请求的处理成本,因此主要在需要集群、反向代理的场景被使用。如下图所示:
当tomcat服务启动后,查看端口可以发现8009端口已经处于监听状态:
在接收ajp请求的时候会调用org.apache.coyote.ajp.AjpProcessor来处理ajp消息,由于环境是tomcat 8.0.50版本搭建的,产生漏洞的点并不在AjpProcessor.prepareRequest()方法,tomcat8.0.50版本的漏洞点存在于AjpProcessor的父类,AbstractAjpProcessor这个抽象类的prepareRequest()中。我们在此处下断点
Debug模式启动tomacat-8.0.50版本,然后对指定ip、指定端口运行exp,
此时的调用链如下:
走的是8009端口,经过几次调用交由AbstractAjpProcessor.prepareRequest(),该方法就是漏洞产生的第一个点。
跟进去request.setAttribute()方法看一下,发现是HashMap,意思是将我们通过AJP协议传递过来的三个参数循环遍历存入这个HashMap。
接着看request.setAttribute()发现运行了三次
While循环的结果是将三个参数存到Request对象的attributes属性下:
通过AJP协议传来的数据最终还是要交由Servlet来进行处理的,查看了ajp协议的介绍,配置Apache以提供静态Web应用程序文件。所以由DefaultServlet来处理,DefaultServlet主要用于处理静态资源,如HTML、图片、CSS、JS文件等,而且为了提升服务器性能,Tomcat对访问文件进行缓存。按照默认配置,客户端请求路径与资源的物理路径是一致的。
定位到DefaultServlet的doGet方法,doGet方法里面又调用了serveResource()
跟进去serveResource(),发现调用了getRelativePath()方法来进行路径拼接,
跟进去发现如果跟进去的数据有request_uri,就对path_info 、servlet_path进行赋值,如果发送的数据包中没有request_uri,就会走else后面的两行代码进行赋值。
接下来就是对路径的拼接了,这里可以看到如果传递数据时不传递servlet_path,则result在进行路径拼接时就不会讲“/”拼接在“WEB-INF/web.xml”的头部,最后拼接的结果仍然是“WEB-INF/web.xml”
接下来返回到DefaultServle.serveResource(),紧接着判断path变量长度是否为0,为0则调用目录重定向方法
往下走,开始读取指定的资源文件了
跟进去发现getResource()方法中又调用了一个很重要的方法validate()方法并将path作为变量传递进去进行处理
继续跟入
我们可以看到正常请求最后return的result的路径就是我们文件所在的相对路径。
当我们尝试使用WEB-INF/../../Test.txt来读取webapps以外的目录中的文件时。可以看到此时返回的result就是null了,而且会抛出异常。
因为RequestUtil.normalize()这个函数对我们传递进来的路径处理上做了判断
我们传入的路径是“/WEB-INF/../../Test.txt”,首先程序会判断我们的路径中是否存在“/../”,自然是存在的且索引是8大于0,所以第一个if 判断不会成功,也就不会跳出while循环,此时处理我们的路径,截取“/WEB-INF/..”以后的内容,然后在用String,indexOf()函数判断路径里是否有“/../”,显然还是有的,且索引为零,符合第二个if判断的条件,return null。
此处的目的就是 不允许传递以“/../”为开头的路径,且不允许同时出现两个连在一起的“/../” 所以我们最多只能读取到webapps目录,无法读取webapps以外的目录中的文件。
程序最终拼接出我们所指定文件的绝对路径,并作为返回值进行返回
接下来就是回到getResource()函数进行文件读取了
跟进去
任意文件读取的调用链
漏洞利用方式:
由以上漏洞分析可知:
- 利用DefaultServlet实现任意文件下载。
当url请求未在映射的url列表里面,会通过tomcat默认的DefaultServlet,根据上面的三个属性来读取文件,通过serveResource方法来获取资源文件:
通过getRelativePath来获取资源文件路径:
- 通过jspservlet实现任意后缀文件包含
网站应用提供文件上传的功能,攻击者可以先向服务端上传一个内容含有恶意 JSP 脚本代码的文件(上传的文件本身可以是任意类型的文件,比如图片、纯文本文件等),然后利用 Ghostcat 漏洞进行文件包含,从而达到代码执行的危害。
当url(比如http://xxx/xxx/xxx.jsp)请求映射在org.apache.jasper.servlet.JspServlet这个servlet的时候,也可以通过上述三个属性来控制访问的jsp文件,如下图所示:
控制路径之后就能以jsp解析该文件,所以只需要一个内容可控的文件即可实现rce。
漏洞修复:
1.临时禁用AJP协议端口,在conf/server.xml配置文件中注释掉
<Connector port="8009"protocol="AJP/1.3"redirectPort="8443" />
2.配置ajp配置中的secretRequired跟secret属性来限制认证
3.官方下载最新版下载地址:
https://tomcat.apache.org/download-70.cgi
https://tomcat.apache.org/download-80.cgi
https://tomcat.apache.org/download-90.cgi
https://github.com/apache/tomcat/releases
相关链接:
AJP协议规范:https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html
参考链接:
Tomcat AJP协议漏洞分析与利用:https://www.freebuf.com/column/227973.html
Ghostcat 是存在于 Tomcat 中的高危文件读取/包含漏洞:https://www.chaitin.cn/zh/ghostcat