目录
1.漏洞简介
2、AJP13 协议介绍
Tomcat 主要有两大功能:
3.Tomcat 远程文件包含漏洞分析
4.漏洞复现
5、漏洞分析
6.RCE 实现的原理
- 2020 年 2 月 20 日,公开CNVD 的漏洞公告中发现 Apache Tomcat 文件包含漏洞(CVE-2020-1938)。
- Apache Tomcat 是 Apache 开源组织开发的用于处理 HTTP 服务的项目。Apache Tomcat 服务器中被发现存在文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat上所有 webapp 目录下的任意文件。
- 该漏洞是一个单独的文件包含漏洞,依赖于 Tomcat 的 AJP(定向包协议)。AJP自身存在一定缺陷,导致存在可控参数,通过可控参数可以导致文件包含漏洞。AJP协议使用率约为 7.8%,鉴于 Tomcat 作为中间件被大范围部署在服务器上,该漏洞危害较大。
Servlet 容器可以理解为 Web 服务器的升级版。以 Tomcat 为例,Tomcat 本身可以不作为 Servlet 容器使用,仅仅充当 Web 服务器的角色,但是其处理静态资源请求的效率和速度远不及 Apache,所以很多情况下生产环境会将 Apache 作为 Web 服务器来接收用户的请求。静态资源由 Apache 直接处理,而 Servlet 请求则交由 Tomcat来进行处理。这种方式使两个中间件各司其职,大大加快了响应速度。
- 一是充当 Web 服务器,可以对一切静态资源的请求作出回应;常见的 Web 服务器有 Apache、Nginx、IIS 等
- 二是充当 Servlet 容器。常见的 Servlet 容器有 Tomcat、Weblogic、JBOSS 等
众所周知,用户的请求是以 HTTP 协议的形式传递给 Web 服务器。我们在浏览器中对某个域名或者 ip 进行访问时,头部都会有 http 或者 https 的表示,而 AJP 浏览器是不支持的,我们无法通过浏览器发送 AJP 的报文。AJP 这个协议并不是提供给用户使用的。


两个文件夹下载好后,存放入在同一个目录下,然后在源码中新增 pom.xml,并添加以下内容
4.0.0 org.apache.tomcat Tomcat8.0 Tomcat8.0 8.0 Tomcat8.0 java test java test org.apache.maven.plugins maven-compiler-plugin 2.3 UTF-8 1.8 1.8 junit junit 4.12 test org.easymock easymock 3.4 ant ant 1.7.0 wsdl4j wsdl4j 1.6.2 javax.xml jaxrpc 1.1 org.eclipse.jdt.core.compiler ecj 4.5.1
然后添加一个 Application,如下图所示
- 新增 Application 的配置信息。
- 在 Man class:中填入:org.apache.catalina.startup.Bootstrap。
- 在 VMoptions:中填入:-Dcatalina.home="apache-tomcat-8.5.34",并将 catalina.
- home 替换成 tomcat binary core 的目录。
- JDK 默认是 1.8,因为我安装的是 jdk1.8 版本。
- 启动过程中 Test 模块会报错,且为 TestCookieFilter.java,注释里面的测试内容即可。
- 然后访问 127.0.0.1:8080,如出现以下页面,则表示环境搭建成功,如下图所示。


RCE 如图 4-1 和图 4-2 所示


首先定位到类 org.apache.coyote.ajp.AjpProcessor。根据网上透漏的漏洞消息,得知漏洞的产生是由于 Tomcat 对 ajp 传递过来的数据的处理方式存在问题,导致用户可以控制
这 3 个参数,从而读取任意文件,甚至可以进行 RCE。
我们先从任意文件读取开始分析。环境使用 Tomcat 8.0.50 版本搭建,产生漏洞的原因并不在于 AjpProcessor.prepareRequest()方法。8.0.50 版本的漏洞点存在于AjpProcessor 的父类,即 AbstractAjpProcessor 抽象类的 prepareRequest()中,如下图 5-1 所示

在这里设置断点,然后运行 exp,查看此时的调用链,如下图所示

由于此次数据传输使用的是 AJP,经过 8009 口,并非我们常见的 HTTP,因此首先由内部类 SocketPeocessore 来进行处理。
处理完成后,经过几次调用交由 AbstractAjpProcessor.prepareRequest()方法,该方法是漏洞产生的第一个点,如图 5-3 所示

单步执行 request.setAttribute()方法,如图 5-4 和图 5-5 所示


这里我们可以看到,attributes 是一个 HashMap,将通过 AJP 传递过来的 3 个参数循环遍历存入这个 HashMap,如图 5-6 所示。

可以看到这里是一个 while 循环,直接来看循环完成后的结果,如图 5-7 所示

先来查看 exp 发出的数据包,如图 5-8 所示

通过使用 WireShark 抓包查看 AJP 报文的信息,其中有 4 个比较重要的参数如下。
URI:/asdf
javax.servlet.include.request_uri:/
javax.servlet.include.path_info: WEB-INF/Test.txt
javax.servlet.include.servlet_path:/ 通过 AJP 传来的数据需要交由 Servlet 进行处理,那么应该交由哪个 Servlet 呢? 通过阅读关于 Tomcat 架构的文章和资料得知,Tomcat$ CATALINA_BASE/conf/web.xml 配置文件中默认定义了两个 Servlet:一个是 DefaultServlet,如图 5-9所示;另一个是 JspServlet,如图 5-92 所示。


我们看到请求的 URI 为“/asdf”,符合无法匹配后台任何 Servlet 的条件。这里需要注意的是,举例来说,我们请求一个“abc.jsp”,但是后台没有“abc.jsp”,这不属于无法匹配任何 Servlet,因为.jsp 的请求会默认由 JspServlet 进行处理,如图 5-11 所示。由于$ CATALINA_BASE/conf/web.xml 文件是 tomcat 启动时默认加载的,因此二个Servlet 会默认存放在 Servlet 容器中。当用户请求的 URI 不能与任何 Servlet 匹配时,会默认交由 DefaultServlet 来处理。DefaultServlet 主要用于处理静态资源,如 HTML、图片、CSS、JS 文件等,而且为了提升服务器性能,Tomcat 将对访问文件进行缓存。按照默认配置,客户端请求路径与资源的物理路径是一致的。

定位到 DefaultServlet 的 doGet 方法,如图 5-12 所示。根据上述内容,结合发送数据包中的“URI:/asdf”这一属性,可以判断该请求是由 DefaultServlet 进行处理的。

doGet 方法中调用了 serveResource()方法。serveResource()方法调用了 getRelativePath()
方法来进行路径拼接,如图 5-13 所示

这里就是将传入的 path_info、servlet_path 进行复制的地方。request_uri 用来做判断,如果发送的数据包中没有 request_uri,就会执行 else 后面的两行代码进行赋值。这会导致漏洞利用失败,如图 5-14 所示

接下来是对路径的拼接。这里可以看到,如果传递数据时不传递 servlet_path,则 result 在进行路径拼接时不会将“/”拼接在“WEB-INF/web.xml”的头部。最后拼接的结果仍然是“WEB-INF/web.xml”,如图 5-15 所示

返回 DefaultServle.serveResource()。然后判断 path 变量长度是否为 0,为 0 则调 用目录重定向方法,如图 5-16 所示。

下面的代码开始读取指定的资源文件,如图 5-17 和图 5-18 所示:


执行 StandardRoot.getResource()方法,如图 5-19 所示

getResource()方法中调用了很重要的 validate()方法,并将 path 作为变量传递进去进行处理。这里会涉及不能通过“/../../”的方式来读取 webapp 目录的上层目录中的文件的原因。首先是正常请求流程,如图 5-20 所示。

我们可以看到正常请求后 return 的 result 路径就是文件所在的相对路径。 当我们尝试使用 WEB-INF/../../Test.txt 来读取 webapp 以外的目录中的文件时, 可以看到此时返回的 result 是 null,而且会抛出异常,如图 5-21 所示

所有原因都在于 RequestUtil.normalize()函数对我们传递进来的路径的处理方式。
关键的点就在下面的截图代码中。我们传入的路径是“/WEB-INF/../../Test.txt”, 首先程序会判断路径中是否存在“/../”,答案是包含且索引大于 8,所以第一个 if 判断不会成功,也不会跳出 while 循环。此时处理我们的路径,截取“/WEB-INF/..”以后的内容。然后 String,indexOf()函数判断路径中是否包含“/../”,答案是包含且索引为零,符合第二个 if 判断的条件,返回 null,如图 5-21 所示

·此处的目标是不允许传递的路径的开头为“/../”,且不允许同时出现两个连在一起的“/../”,所以我们最多只能读取到 webapp 目录,无法读取 webapp 以外的目录中的文件。
要读取 webapp 目录下的其余目录内的文件,可以通过修改数据包中的“URI”参数来实现,如图 5-22 所示

程序最终会拼接出我们所指定文件的绝对路径,并作为返回值返回,如图 5-23 所示。

接下来回到 getResource()函数进行文件读取,如图 5-24 所示

以下是任意文件读取的调用链,如图 5-25 所示

前面介绍过 Tomcat$ CATALINA_BASE/conf/web.xml 配置文件中默认定义了两个 Servlet。上述任意文件读取利用了 DefaultServlet,而 RCE 则需要用到 JspServlet。 默认情况下,JspServlet 的 url-pattern 为.jsp 和.jspx,因此它负责处理所有 JSP 文件的请求。JspServlet 主要完成以下工作:
- 根据 JSP 文件生成对应 Servlet 的 Java 代码(JSP 文件生成类的父类 org.apache.jasper.runtime.HttpJspBase——实现了 Servlet 接口)。
- 将 Java 代码编译为 Java 类。
- 构造 Servlet 类实例并且执行请求。

这里的“URI”参数必须以“.jsp”结尾,但是该 JSP 文件可以不存在。 其余 3 个参数与之前的没有区别,“path_info”参数对应的是我们上传的包含 JSP代码的文件。定位到 JspServlet.Service()方法,如图 6-2 所示。


然后,将“path_info”参数对应的值取出并赋值给“pathInfo”变量,然后与“jspUri” 进行拼接,如图6-4 和图 6-5 所示


接下来调用 serviceJspFile()方法,如图 6-6 所示


然后调用 JspServletWrapper.service()方法,如图6-8 所示

获取对应的 servlet,如图 6-9 所示。

调用该 servlet 的 service 方法,如图 6-10 所示

接下来解析上传文件中的 Java 代码。至此,RCE 漏洞原理分析完毕。调用链如下图所示
