目录

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 服务器有 ApacheNginxIIS
  • 二是充当 Servlet 容器常见的 Servlet 容器有 TomcatWeblogicJBOSS
        Servlet 容器可以理解为 Web 服务器的升级版。以 Tomcat 为例, Tomcat 本身可以不作为 Servlet 容器使用,仅仅充当 Web 服务器的角色,但是其处理静态资源请求的效率和速度远不及 Apache ,所以很多情况下生产环境会将 Apache 作为 Web 服务器来接收用户的请求。静态资源由 Apache 直接处理,而 Servlet 请求则交由 Tomcat来进行处理。这种方式使两个中间件各司其职,大大加快了响应速度。
        众所周知,用户的请求是以 HTTP 协议的形式传递给 Web 服务器。我们在浏览器中对某个域名或者 ip 进行访问时,头部都会有 http 或者 https 的表示,而 AJP 浏览器是不支持的,我们无法通过 浏览器发送 AJP 的报文。AJP 这个协议并不是提供给用户使用的。
  • Tomcat$ CATALINA_BASE/conf/web.xml 默认配置了两个 Connector,分别监听两个不同的端口,一个是 HTTP Connector 默认监听 8080 端口,另一个是 AJP Connector 默认监听 8009 端口。
  • HTTP Connector 主要负责接收来自用户的请求,包括静态请求和动态请求。有了 HTTP ConnectorTomcat 才能成为一个 Web 服务器,还可以额外处理 Servlet 和JSP。
  • AJP 的使用对象通常是另一个 Web 服务器,例如 Apache,这里以下图进行说明。
Apache 服务器

 

  • AJP 是一个二进制TCP 传输协议。浏览器无法使用 AJP,而是首先由 Apache 与 Tomcat 进行 AJP 的通信,然后由 Apache 通过 proxy_ajp 模块进行反向代理,将其转换成 HTTP 服务器再暴露给用户,允许用户进行访问。
  • 这样做的原因是,相对于 HTTP 纯文本协议来说,效率和性能更高,同时也做了很多优化。在某种程度上,AJP 可以理解为 HTTP 的二进制版,因加快传输效率被广泛应用。实际情况是类似 Apache 这样有 proxy_ajp 模块可以反向代理 AJP 协议的服务器很少,所以 AJP 协议在生产环境中也很少被用到。

3.Tomcat 远程文件包含漏洞分析


        
首先从官网下载对应的 Tomcat 源码文件和可执行文件,如下图 所示
下载 Tomcat 源码文件和可执行文件

 

两个文件夹下载好后,存放入在同一个目录下,然后在源码中新增 pom.xml ,并添加以下内容
<?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 所示

图 4-1 RCE(一)

 

图 4-2 RCE(二)

 5、漏洞分析


首先定位到类 org.apache.coyote.ajp.AjpProcessor。根据网上透漏的漏洞消息,得知漏洞的产生是由于 Tomcat ajp 传递过来的数据的处理方式存在问题,导致用户可以控制

  1. “javax.servlet.include.request_uri
  2. javax.servlet.include.path_info
  3. javax. servlet.include.servlet_path”

3 个参数,从而读取任意文件,甚至可以进行 RCE

我们先从任意文件读取开始分析。环境使用 Tomcat 8.0.50 版本搭建,产生漏洞的原因并不在于 AjpProcessor.prepareRequest() 方法。 8.0.50 版本的漏洞点存在于AjpProcessor 的父类,即 AbstractAjpProcessor 抽象类的 prepareRequest() 中,如下图 5-1  所示
图 5-1 漏洞点分析

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

图 5-2 设置断点并运行 exp

 由于此次数据传输使用的是 AJP,经过 8009 口,并非我们常见的 HTTP,因此首先由内部类 SocketPeocessore 来进行处理。

处理完成后,经过几次调用交由 AbstractAjpProcessor.prepareRequest() 方法,该方法是漏洞产生的第一个点,如图 5-3  所示
图 5-3  漏洞产生的第一个点

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

图 5-4 单步执行 request.setAttribute()方法(一)

 

图 5-5  单步执行 request.setAttribute()方法(二)

 

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

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

图 5-7  while 循环完成后的结果

 

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

 通过使用 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 所示。
图 5-9 默认定义的 DefaultServlet

 

图 5-10 默认定义的 JspServlet

         由于$ CATALINA_BASE/conf/web.xml 文件是 tomcat 启动时默认加载的,因此二个Servlet 会默认存放在 Servlet 容器中。当用户请求的 URI 不能与任何 Servlet 匹配时,会默认交由 DefaultServlet 来处理。DefaultServlet 主要用于处理静态资源,如 HTML、图片、CSSJS 文件等,而且为了提升服务器性能,Tomcat 将对访问文件进行缓存。按照默认配置,客户端请求路径与资源的物理路径是一致的。

        我们看到请求的 URI 为“ /asdf ”,符合无法匹配后台任何 Servlet 的条件。这里需要注意的是,举例来说,我们请求一个“abc.jsp ”,但是后台没有“ abc.jsp ”,这不属于无法匹配任何 Servlet ,因为 .jsp 的请求会默认由 JspServlet 进行处理,如图 5-11 所示。
图 5-11  无法匹配任何 Servlet

 根据上述内容,结合发送数据包中的“URI:/asdf”这一属性,可以判断该请求是由 DefaultServlet 进行处理的。

        定位到 DefaultServlet doGet 方法,如图 5-12   所示。
图 5-12  定位到 DefaultServlet 的 doGet 方法

 doGet 方法中调用了 serveResource()方法。serveResource()方法调用了 getRelativePath()

方法来进行路径拼接,如图 5-13  所示
图 5-13 路径拼接

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

 

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

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

 

图 5-16  调用目录重定向方法

 

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

 

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

图 5-19 执行 StandardRoot.getResource()方法

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

图 5-20 正常请求流程

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

图 5-21 尝试目录穿越(一)

 所有原因都在于 RequestUtil.normalize()函数对我们传递进来的路径的处理方式。

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

    ·此处的目标是不允许传递的路径的开头为“/../”,且不允许同时出现两个连在一起的“/../”,所以我们最多只能读取到 webapp 目录,无法读取 webapp 以外的目录中的文件。

        要读取 webapp 目录下的其余目录内的文件,可以通过修改数据包中的“ URI ”参数来实现,如图 5-22  所示
图 5-22 修改 URI

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

图 5-23 成功拼接文件路径

 

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

 

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

 

6.RCE 实现的原理


        前面介绍过 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 类实例并且执行请求。
  1. RCE 本质是通过 JspServlet 来执行我们想要访问的.jsp 文件。
  2. RCE 的前提是,首先想办法将包含需要执行的命令的文件(可以是任意文件后缀,甚至没有后缀)上传到 webapp 的目录下,才能访问该文件;然后通过 JSP 模板的解析造成 RCE。
        查看本次发送的 AJP 报文的内容,如图 6 -1  所示
图 6-1 AJP 报文的内容

 

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

 

图 6-3  赋值给变量 jspUri

 

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

图 6-5  赋值给变量 pathInfo 并拼接(二)

        

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

图6-6  调用 serviceJspFile()方法

        首先生成 JspServletWrapper 对象,如图 6-7   所示
图 6-7 生成 JspServletWrapper 对象

 

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

图 6-8 调用 JspServletWrapper.service()方法

 

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

图 6-9  获取对应的 servlet

 

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

 

图 6-10 调用的 service 方法

 

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

 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐