幽灵猫 Tomcat 文件读取 / 任意文件包含漏洞(CVE-2020-1938 / CNVD-2020-10487)

本贴最后更新于 270 天前,其中的信息可能已经渤澥桑田

学习该漏洞时我翻阅了大量的博客,但是感觉大部分没有说到点子上,所以写这篇博客,记录一下学习历程,接下来我会从代码和协议两个方面来分析这个漏洞。

一、漏洞简介

该漏洞是由长亭科技安全研究员发现的存在于 Tomcat 中的安全漏洞,由于 Tomcat AJP 协议设计上存在缺陷,攻击者通过 Tomcat AJP Connector 可以读取或包含 Tomcat 上所有 webapp 目录下的任意文件,例如可以读取 webapp 配置文件或源代码。此外在目标应用有文件上传功能的情况下,配合文件包含的利用还可以达到远程代码执行的危害。

影响版本:

** Apache Tomcat 6**

** Apache Tomcat 7 < 7.0.100**

** Apache Tomcat 8 < 8.5.51**

** Apache Tomcat 9 < 9.0.31**

二、漏洞原理:

首先还是了解这个漏洞要有一些基础知识

这个漏洞是 tomcat 的漏洞,所以我们要对 tomcat 的运行原理有一些概念。

tomcat 的大致体系结构如下:

img

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

Container :由四个自容器组件构成,分别是 Engine、Host、Context、 Wrapper。

Engine :Engine 容器,定义了一些基本的关联关系。

Host :Host 是 Engine 的字容器,Host 在 Engine 中代表一个虚拟主机,其作用 就是运行多个应用。

Context :Context 容器,拥有 Servlet 运行的基本环境,且负责管理其中的 Servlet 实例。 Wrapper :Wrapper 是最底层的容器,负责管理一个 Servlet。

Servlet :服务程序。

(1) Tomcat Connector(连接器)

首先来说一下 Tomcat 的 Connector 组件,Connector 组件的主要职责就是负责****接收客户端连接客户端请求的处理加工。每个 Connector 会监听一个指定端口,分别负责对请求报文的解析和响应报文组装,解析过程封装 Request 对象,而组装过程封装 Response 对象。

上面说了那么多,其实意思就是 Connector 是 tomcat 的入口,一个其他博主的例子,如果把 Tomcat 比作一个城堡,那么 Connector 组件就是城堡的城门,为进出城堡的人们提供通道。当然,可能有多个城门,每个城门代表不同的通道。而 Tomcat 默认配置启动,开了两个城门(通道):一个是监听8080 端口HTTP Connector,另一个是监听 8009 端口的 AJP Connector

Tomcat 组件相关的配置文件是在 conf/server.xml,配置文件中每一个元素都对应了 Tomcat 的一个组件(可以在配置文件中找到如下两项,配置了两个 Connector 组件):

    <Connector port="8080" protocol="HTTP/1.1"



               connectionTimeout="20000"



               redirectPort="8443" />
 <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

HTTP Connector 很好理解,通过浏览器访问 Tomcat 服务器的 Web 应用时,使用的就是这个连接器;

AJP Connector 是通过 AJP 协议和一个 Web 容器进行交互。在将 Tomcat 与其他 HTTP 服务器(一般是 Apache )集成时,就需要用到这个连接器。AJP 协议是采用二进制形式代替文本形式传输,相比 HTTP 这种纯文本的协议来说,效率和性能更高,也做了很多优化。

**两个 Connector 的原理如下图所示: **

img

我们的浏览器肯定是不支持 AJP 协议的,所以要在 AJP Connector 前面加一个 apache server 的反向代理,我们先将 http 请求交到反向代理服务器,反向代理服务器再通过 ajp 协议在 8009 端口向 tomcat 发出请求。

(2)Servlet(服务程序)

Servlet 意为服务程序,也可简单理解为是一种用来处理网络请求的一套规范。主要作用是给上级容器(Tomcat)提供 doGet()和 doPost()等方法,其生命周期实例化、初始化、调用、销毁受控于 Tomcat 容器。有个例子可以很好理解:想象一下,在一栋大楼里有非常多特殊服务者 Servlet,这栋大楼有一套智能系统帮助接待顾客引导他们去所需的服务提供者(Servlet)那接受服务。这里顾客就是一个个请求,特殊服务者就是 Servlet,而这套智能系统就是 Tomcat 容器。

tomcat 默认配置了两个 Servlet,一个是 Jsp Servlert,另一个是 Default Servlet,第一个是专门负责处理 jsp 类型的文件的,除了 jsp 文件的全都交给 Default Servlet 处理。Tomcat 中 Servlet 的配置是在****conf/web.xml 下面。

<!-- The default servlet for all web applications, that serves static    -->



    <!-- resources.  It processes all requests that are not mapped to other   -->



    <!-- servlets with servlet mappings. -->



    <servlet>



        <servlet-name>default</servlet-name>



        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>



        ......



        ......



    </servlet>



    <!-- The JSP page compiler and execution servlet, which is the mechanism  -->



    <!-- used by Tomcat to support JSP pages.  Traditionally, this servlet    -->



    <!-- is mapped to the URL pattern "*.jsp". -->



    <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>

由以上配置文件也可以看出,如果是 jsp 文件的请求,回交给 Jsp Servlert,其他的都交给 Default Servlet 进行处理。

(3)tomcat 内部处理流程

在这里简单介绍一下处理流程就好,便于理解后续漏洞的分析。

img

  1. **用户点击网页内容,请求被发送到本机端口 8080,被 Connector 获得(**Connector 中的 Processor 用于封装 Request,Adapter 用于将封装好的 Request 交给 Container)。
  2. Connector 把该请求交给 Container 中的 Engine 来处理,并等待 Engine 的回应。
  3. Engine 获得请求 localhost/test/index.jsp,匹配所有的虚拟主机 Host。
  4. Engine 匹配到名为 localhost 的 Host(即使匹配不到也把请求交给该 Host 处理,因为该 Host 被定义为该 Engine 的默认主机),名为 localhost 的 Host 获得请求/test/index.jsp,匹配它所拥有的所有的 Context。Host 匹配到路径为/test 的 Context(如果匹配不到就把该请求交给路径名为" "的 Context 去处理)。
  5. *path="/test"的 Context 获得请求/index.jsp,在它的 mapping table 中寻找出对应的 Servlet。Context 匹配到 URL PATTERN 为.jsp 的 Servlet,对应于 JspServlet 类(**匹配不到指定 Servlet 的请求对应 DefaultServlet 类)。
  6. Wrapper 是最底层的容器,负责管理一个 Servlet。构造 HttpServletRequest 对象和 HttpServletResponse 对象,作为参数调用 JspServlet 的 doGet()或 doPost(),执行业务逻辑、数据存储等程序。
  7. Context 把执行完之后的 HttpServletResponse 对象返回给 Host。
  8. Host 把 HttpServletResponse 对象返回给 Engine。
  9. Engine 把 HttpServletResponse 对象返回 Connector。
  10. Connector 把 HttpServletResponse 对象返回给客户 Browser。

在介绍漏洞之前还要配一下 tomcat 源码分析的环境,大家可以根据这篇博文Tomcat 源码编译(IDEA)tomcat 编译ww0peo 的博客-CSDN 博客

**来进行配置,配置的版本是 apache-tomcat-8.5.46,**配置好了之后就可以愉快的进行漏洞的代码分析了。

根据上文的 Tomcat 处理请求流程,请求首先到达 Connector,Connector 内使用 AjpProcessor 解析 Socket,将 Socket 中的内容封装到 Request 中。然后经过一系列的处理,到达 Servlet。根据上文我们可以了解到,tomcat 默认配置两个 Servlet,一个是 Default Servlet,另一个是 Jsp Servlet,而这这里就分别对应了文件读取漏洞和任意代码执行漏洞。

1.文件读取漏洞

**当 url 未匹配到 Servlet 时,回给到 Default Servlet 处理。 **

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

img

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

img

然后回到子类,查询 doGet 方法。 通过 serveResource 方法来获取资源。

img

** 在 serveResource 方法中,它定义了我们获取资源的路径**

img

跟踪进去,

img

** 我们发现了三个重要的参数,**

INCLUDE_REQUEST_URI,   INCLUDE_PATH_INFO,   INCLUDE_SERVLET_PATH

** 发现这三个参数可以控制我们请求资源的路径,继续跟踪,发现这三个属性都有默认值**

static final String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri";
static final String INCLUDE_CONTEXT_PATH = "javax.servlet.include.context_path";
static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info";

** 这里就是我们利用的重点,我们可以通过发送数据包来伪造这几个默认值,从而控制路径,到后面我会用 wireshark 抓包看一下。**

由上面截图中的代码可知,当 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。

回到 serveResource 方法,获取到路径后创建资源的实例并且获取资源。

img

** 跟踪进去 getResource 方法,(位于 org.apache.catalina.webresources.StandardRoot.java 中)**

img

** 我们发现上述代码调用了 validate 进行判断,继续跟着**

img

在这里有主要用了 normalize 方法来进行校验,其主要功能如下:

1.判断 path 中是否有'**',有就转换成'/';**

2.判断 path 是否以'/'开头,若不是则在前面加上'/';

3.判断 path 是否以'/.'或'/..'结尾,若是则结尾加一个'/';

4.判断 path 是否含有'//',有则替换成'/';

5.判断 path 是否含有'/./',若有直接截断并重新拼接,如'/./abc'变为'/abc'

6.判断 path 是否含有'/../',若有则直接返回 null;

首先补充一个知识,当我们访问 tomcat 时,它会默认访问取 webapps 下的默认文件,而在这里对相对路径的符号进行了限制,所以我们利用该漏洞只能访问 webapps 下的文件。

在验证完成之后 ,会继续将资源返回到输出流中,再交由 tomcat 的内部进行处理。

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

若是想请求 webapps 目 录下的其他目录,则可以设置为 ‘/指定目录/asdf’,如’’/manager/asdf’。会访问到 webapps/manager/下的目录,具体的文件看我们构造的参数。

如要访问”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’。

poc 的下载地址为GitHub - YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi: Tomcat-Ajp 协议文件读取漏洞

但是在 python3.7.9 的版本下运行会报错,这里我修改了 poc,修改的 poc 放在了tomcat_Ghostcat/ at main · fengdianxiong/tomcat_Ghostcat · GitHub 上,大家可以自取。

img

成功获取文件 。

我们再来看一下对应的数据包:

img

发现在 poc 的数据包中携带了我们上面三个重要的参数,从而实现文件读取。

2.任意代码执行漏洞

这个漏洞稍微有一点点鸡肋,需要提前先自己上传木马文件,然后利用文件包含漏洞来实现代码执行。

由上文可知,我们知道当请求 jsp 文件时,会走 Jsp Servlet 来进行处理。

首先来到 JspServlert.java(位于 org.apache.jasper.servlet.JspServlet.java),经过 service 方法处理,由标红部分可以看出,jspUri 由 servlet_path 和 path_info 拼接而成。

img

然后进入到 serviceJspFile 方法中,方法利用 jspUri 生成 JspServletWrapper 实例,再到调用实例的 service() 方法。

img

获取到对应的 Servlet

img

调用该 Servlet 的 service 方法

img

请求执行,从而造成文件包含。

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

在复现时,要修改一下 poc 的参数,然后在 webapps 目录下的 ROOT 下(apache-tomcat-8.5.46-source\catalina-home\webapps\ROOT),自己创建一个 1.txt,

内容为

<%Runtime.getRuntime().exec("calc.exe");%>

img

然后再次利用 poc,弹出计算器。

img

三、修复建议

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

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

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

  • 安全

    安全永远都不是一个小问题。

    191 引用 • 813 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...