目录
1.漏洞简介
2、AJP13 协议介绍
Tomcat 主要有两大功能:
3.Tomcat 远程文件包含漏洞分析
4.漏洞复现
5、漏洞分析
6.RCE 实现的原理
1.漏洞简介
- 2020 年 2 月 20 日,公开CNVD 的漏洞公告中发现 Apache Tomcat 文件包含漏洞(CVE-2020-1938)。
- Apache Tomcat 是 Apache 开源组织开发的用于处理 HTTP 服务的项目。Apache Tomcat 服务器中被发现存在文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat上所有 webapp 目录下的任意文件。
- 该漏洞是一个单独的文件包含漏洞,依赖于 Tomcat 的 AJP(定向包协议)。AJP自身存在一定缺陷,导致存在可控参数,通过可控参数可以导致文件包含漏洞。AJP协议使用率约为 7.8%,鉴于 Tomcat 作为中间件被大范围部署在服务器上,该漏洞危害较大。
2、AJP13 协议介绍
Tomcat 主要有两大功能:
- 一是充当 Web 服务器,可以对一切静态资源的请求作出回应;常见的 Web 服务器有 Apache、Nginx、IIS 等
- 二是充当 Servlet 容器。常见的 Servlet 容器有 Tomcat、Weblogic、JBOSS 等
众所周知,用户的请求是以 HTTP 协议的形式传递给 Web 服务器。我们在浏览器中对某个域名或者 ip 进行访问时,头部都会有 http 或者 https 的表示,而 AJP 浏览器是不支持的,我们无法通过 浏览器发送 AJP 的报文。AJP 这个协议并不是提供给用户使用的。
- Tomcat$ CATALINA_BASE/conf/web.xml 默认配置了两个 Connector,分别监听两个不同的端口,一个是 HTTP Connector 默认监听 8080 端口,另一个是 AJP Connector 默认监听 8009 端口。
- HTTP Connector 主要负责接收来自用户的请求,包括静态请求和动态请求。有了 HTTP Connector,Tomcat 才能成为一个 Web 服务器,还可以额外处理 Servlet 和JSP。
- 而 AJP 的使用对象通常是另一个 Web 服务器,例如 Apache,这里以下图进行说明。
- AJP 是一个二进制的 TCP 传输协议。浏览器无法使用 AJP,而是首先由 Apache 与 Tomcat 进行 AJP 的通信,然后由 Apache 通过 proxy_ajp 模块进行反向代理,将其转换成 HTTP 服务器再暴露给用户,允许用户进行访问。
- 这样做的原因是,相对于 HTTP 纯文本协议来说,效率和性能更高,同时也做了很多优化。在某种程度上,AJP 可以理解为 HTTP 的二进制版,因加快传输效率被广泛应用。实际情况是类似 Apache 这样有 proxy_ajp 模块可以反向代理 AJP 协议的服务器很少,所以 AJP 协议在生产环境中也很少被用到。
3.Tomcat 远程文件包含漏洞分析
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.
org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.tomcat</groupId>
<artifactId>Tomcat8.0</artifactId>
<name>Tomcat8.0</name>
<version>8.0</version>
<build>
<finalName>Tomcat8.0</finalName>
<sourceDirectory>java</sourceDirectory>
<testSourceDirectory>test</testSourceDirectory>
<resources>
<resource>
<directory>java</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>test</directory>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>ant</groupId>
<artifactId>ant</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxrpc</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compiler</g
roupId>
<artifactId>ecj</artifactId>
<version>4.5.1</version>
</dependency>
</dependencies>
</project>
然后添加一个 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,如出现以下页面,则表示环境搭建成功,如下图所示。
4.漏洞复现
RCE 如图 4-1 和图 4-2 所示
5、漏洞分析
首先定位到类 org.apache.coyote.ajp.AjpProcessor。根据网上透漏的漏洞消息,得知漏洞的产生是由于 Tomcat 对 ajp 传递过来的数据的处理方式存在问题,导致用户可以控制
- “javax.servlet.include.request_uri”
- “javax.servlet.include.path_info”
- “javax. servlet.include.servlet_path”
这 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:/
通过阅读关于 Tomcat 架构的文章和资料得知, Tomcat$ CATALINA_BASE/conf/web.xml 配置文件中默认定义了两个 Servlet :一个是 DefaultServlet ,如图 5-9所示;另一个是 JspServlet ,如图 5-92 所示。
由于$ CATALINA_BASE/conf/web.xml 文件是 tomcat 启动时默认加载的,因此二个Servlet 会默认存放在 Servlet 容器中。当用户请求的 URI 不能与任何 Servlet 匹配时,会默认交由 DefaultServlet 来处理。DefaultServlet 主要用于处理静态资源,如 HTML、图片、CSS、JS 文件等,而且为了提升服务器性能,Tomcat 将对访问文件进行缓存。按照默认配置,客户端请求路径与资源的物理路径是一致的。
根据上述内容,结合发送数据包中的“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 所示
6.RCE 实现的原理
前面介绍过 Tomcat$ CATALINA_BASE/conf/web.xml 配置文件中默认定义了两个 Servlet 。上述任意文件读取利用了 DefaultServlet,而 RCE 则需要用到 JspServlet 。 默认情况下,JspServlet 的 url-pattern 为 .jsp 和 .jspx ,因此它负责处理所有 JSP 文件的请求。
- 根据 JSP 文件生成对应 Servlet 的 Java 代码(JSP 文件生成类的父类 org.apache.jasper.runtime.HttpJspBase——实现了 Servlet 接口)。
- 将 Java 代码编译为 Java 类。
- 构造 Servlet 类实例并且执行请求。
- RCE 本质是通过 JspServlet 来执行我们想要访问的.jsp 文件。
- RCE 的前提是,首先想办法将包含需要执行的命令的文件(可以是任意文件后缀,甚至没有后缀)上传到 webapp 的目录下,才能访问该文件;然后通过 JSP 模板的解析造成 RCE。
这里的“URI ”参数必须以“ .jsp ”结尾,但是该 JSP 文件可以不存在。其余 3 个参数与之前的没有区别,“ path_info ”参数对应的是我们上传的包含 JSP代码的文件。
接下来调用 serviceJspFile()方法,如图 6-6 所示