CVE重要漏洞复现之Tomcat-CVE-2020-1983漏洞

Tomcat-CVE-2020-1983漏洞

教材内容

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

一、漏洞简介

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

影响范围:

  • 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 来处理这个请求。
  • 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端口,其中 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 -->    
<servlet-mapping>        
    <servlet-name>jsp</servlet-name>        
    <url-pattern>*.jsp</url-pattern>        
    <url-pattern>*.jspx</url-pattern>    
</servlet-mapping>
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。找到 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 ,也就分别对应了文件读取和文件包含漏洞。

文件读取漏洞

当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 的默认目录。若是想请求 webapps 目录下的其他目录,则可以设置为 ‘/指定目录/asdf’,如’’/manager/asdf’。

如要访问”webapps/ROOT/WEB-INF/web.xml”,首先将请求 url 为’/asdf’,再将javax.servlet.include.request_uri 值设置为’/‘(非空),javax.servlet.include.servlet_path 的值设置为 ‘/‘,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

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

image-20220315121052805

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

image-20220315121800810

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

image-20220315135434270

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

三、复现过程

一、漏洞概述

CVE-2020-1983是一个存在于Apache Tomcat中的文件包含漏洞。攻击者可以利用此漏洞读取或包含Tomcat服务器上所有webapp目录下的任意文件,如webapp配置文件、源代码等。

二、影响版本

该漏洞影响以下版本的Apache Tomcat:

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

三、漏洞原理

Tomcat在处理特定请求时存在漏洞,使得攻击者能够构造恶意请求来读取服务器上的任意文件。具体来说,当Tomcat接收到包含特殊构造的URL参数的请求时,它会将该参数中的内容作为文件路径来解析,并读取文件内容返回给客户端。如果攻击者能够构造出包含恶意的文件路径,他们就可以在服务器上执行任意代码。

服务器:Windows Server 2008 (IP:192.168.219.181)

Tomcat 版本:Apache Tomcat 8.5.47

攻击机:Kali (IP:192.168.219.134)

POC :CNVD-2020-10487-Tomcat-Ajp-lfi( https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi ).

3.1 任意文件读取

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

python CNVD-2020-10487-Tomcat-Ajp-lfi.py 服务器IP -p 8009 -f WEB-INF/web.xml

//   WEB-INF/web.xml 这个参数的值默认是在 tomcat/webapps/ROOT/  目录下的文件路径

image-20220309161851041

3.2 文件包含

利用文件包含漏洞实现命令执行

1.上传一个 木马shell,当然,可以命名为 txt 后缀,也可以使用其他的,使用命令打开计算器,内容如下:

由于实验环境懒得配置上传环境了,所以在在服务器的webapps下的ROOT目录下生成一个 shell.txt 文件

image-20220309180040911

image-20250129085505509image-20250129085520902

2.更改POC,将asdf后加上.jsp,如下:

image-20220310095036170

3.使用POC将生成的文件包含,执行命令。

python CNVD-2020-10487-Tomcat-Ajp-lfi.py 192.168.219.181 -p 8009 -f shell.txt

image-20220310105052382

Windows 服务器上的计算器被打开。

image-20220309175941589

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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值