CVE-2020-1938 /CNVD-2020-10487漏洞调试

0x01 漏洞背景

CVE-2020-1938 是 Tomcat-Ajp 协议漏洞分析,Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器,按照Sun Microsystems提供的技术规范,实现了对Servlet和JavaServer Page(JSP)的支持。由于Tomcat本身也内含了HTTP服务器,因此也可以视作单独的Web服务器。

0x02 影响版本

  1. Apache Tomcat 9.x < 9.0.31
  2. Apache Tomcat 8.x < 8.5.51
  3. Apache Tomcat 7.x < 7.0.100
  4. Apache Tomcat 6.x

0x03 环境搭建

0x1 JDK 安装

cp -r  jdk-8u161-linux-x64.tar.gz   /usr/local/java
cd  /usr/local/java 
tar -zxvf jdk-8u161-linux-x64.tar.gz 

在/etc/profile文件中写入

export JAVA_HOME=/usr/local/java/jdk1.8.0_161
export JRE_HOME=/$JAVA_HOME/jre
export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin

执行生效
source /etc/profile

0x2 Tomcat 安装

tar -zxvf  apache-tomcat-8.5.30.tar.gz

在/etc/profile文件中写入

export CATALINA_HOME=/usr/local/tomcat/apache-tomcat-8.5.30
export CLASSPATH=.:$JAVA_HOME/lib:$CATALINA_HOME/lib
export PATH=$PATH:$CATALINA_HOME/bin

执行生效
source /etc/profile
启动
catalina.sh start

0x3 开启Tomcat调试

在tomcat bin 文件夹下的catalina.sh中添加如下代码

export JAVA_OPTS='-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005'
# OS specific support.  $var _must_ be set to either true or false.

Tomcat 调试原理
分为两种情况:

  1. 调试器开端口,远程JVM连接本地端口
  2. 远程JVM开放端口,调试器连接

总的来说,两个VM之间通过debug协议进行通信,然后以达到远程调试的目的。两者之间可以通过socket进行通信。其中,调试的程序常常被称为debugger, 而被调试的程序称为 debuggee。两种调试情况如下图所示:

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第1张图片

关于调试协议JDWP这里先不讲,等有机会在进行讲解。

0x4 添加tomcat源码

在调试过程中,intellij反编译代码会有出入,直接下载tomcat相对应版本的源代码
https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.30/src/apache-tomcat-8.5.30-src.zip

将文件夹中的java 添加到intellij中,就可以对着源代码进行调试了

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第2张图片

0x5 开启Intellij调试

设置Use module classpath 为项目根目录,设置调试端口等信息,如下图所示:

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第3张图片

0x03 知识补充

0x1 Tomcat Connector

Apache Tomcat服务器通过Connector连接器组件与客户程序建立连接,Connector表示接收请求并返回响应的端点。即Connector组件负责接收客户的请求,以及把Tomcat服务器的响应结果发送给客户。在Apache Tomcat服务器中我们平时用的最多的8080端口,就是所谓的Http Connector,使用Http(HTTP/1.1)协议.

在conf/server.xml文件里,他对应的配置为

    
			  

而 AJP Connector,它使用的是 AJP 协议(Apache Jserv Protocol)是定向包协议。因为性能原因,使用二进制格式来传输可读性文本,它能降低 HTTP 请求的处理成本,因此主要在需要集群、反向代理的场景被使用。

Ajp协议对应的配置为


    

下图是tomcat 服务器开放连接端口情况。

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第4张图片

0x2 Tomcat Servlet

在tomcat conf/web.xml中配置着tomcat的路由处理,主要有两个servlet分支

这个规则匹配/后没有后缀的

<servlet>
        <servlet-name>defaultservlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServletservlet-class>
        <init-param>
            <param-name>debugparam-name>
            <param-value>0param-value>
        init-param>
        <init-param>
            <param-name>listingsparam-name>
            <param-value>falseparam-value>
        init-param>
        <load-on-startup>1load-on-startup>
    servlet>
	
    <servlet-mapping>
        <servlet-name>defaultservlet-name>
        <url-pattern>/url-pattern>
    servlet-mapping>

这个规则匹配路径中有.jsp的路由

<servlet>
        <servlet-name>jspservlet-name>
        <servlet-class>org.apache.jasper.servlet.JspServletservlet-class>
        <init-param>
            <param-name>forkparam-name>
            <param-value>falseparam-value>
        init-param>
        <init-param>
            <param-name>xpoweredByparam-name>
            <param-value>falseparam-value>
        init-param>
        <load-on-startup>3load-on-startup>
    servlet>

    <servlet-mapping>
        <servlet-name>jspservlet-name>
        <url-pattern>*.jspurl-pattern>
        <url-pattern>*.jspxurl-pattern>
    servlet-mapping>

0x3 Tomcat 请求处理

该图介绍了Tomcat内部处理HTTP请求的流程
CVE-2020-1938 /CNVD-2020-10487漏洞调试_第5张图片

  1. 用户发送请求至8080端口,被Connector获取后,Connector中的Processor用于封装Request,Adapter用于将封装好的Request交给Container。
  2. Connector把该请求交给Container中的Engine来处理,并等待Engine的回应。
  3. Engine 获得请求localhost/test/index.jsp,匹配所有的虚拟主机Host。
  4. Engine搜索对应的主机,/test匹配到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

0x04 漏洞分析

该漏洞通过AJP协议端口触发,正是由于上文所述,Ajp协议的请求在Tomcat内的处理流程与我们上文介绍的Tomcat处理HTTP请求流程类似。我们构造两个不同的请求,经过tomcat内部处理流程,一个走default servlet(DefaultServlet),另一个走jsp servlet(JspServlet),可导致的不同的漏洞。

0x1 文件读取漏洞

# 请求url
req_uri = '/asdf'

# AJP协议请求中的三个属性
javax.servlet.include.request_uri = '/'
javax.servlet.include.path_info = 'WEB-INF/web.xml'
javax.servlet.include.servlet_path = '/'

在搭好的环境上,设置断点

Step 1 AjpProcessor->service()->prepareRequest()

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第6张图片

进入prepareRequest函数,该函数处理Requests请求,重点在这一块

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第7张图片

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第8张图片

在该case分支request.setAttribute(n,v )函数是解析函数,通过三次循环可见下图效果

file

file

file

通过三个循环将attributes变量赋值成如下所示:

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第9张图片

随后将请求传给CoyoteAdapter,对request进行封装,将请求抓发给Container:

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第10张图片

通过多级反射和调用,到达Servlet路由分发

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第11张图片


Step 2 DefaultServlet-> service() -> doGet()

从CoyoteAdapter到达doGet的调用链
CVE-2020-1938 /CNVD-2020-10487漏洞调试_第12张图片

Servlet 的service函数根据请求的方法,调用不同的处理函数如下图匹配Get 方法:
1588562393145.pngCVE-2020-1938 /CNVD-2020-10487漏洞调试_第13张图片


Step 3 getRelativePath()

从get函数进入后执行serveResource函数
file

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第14张图片

其中有个关键的函数getRelativePath

 protected String getRelativePath(HttpServletRequest request, boolean allowEmptyPath) {
     
        String servletPath;
        String pathInfo;
        if (request.getAttribute("javax.servlet.include.request_uri") != null) {
     
            pathInfo = (String)request.getAttribute("javax.servlet.include.path_info");
            servletPath = (String)request.getAttribute("javax.servlet.include.servlet_path");
        } else {
     
            pathInfo = request.getPathInfo();
            servletPath = request.getServletPath();
        }
        StringBuilder result = new StringBuilder();
        if (servletPath.length() > 0) {
     
            result.append(servletPath);
        }
        if (pathInfo != null) {
     
            result.append(pathInfo);
        }
        if (result.length() == 0 && !allowEmptyPath) {
     
            result.append('/');
        }
        return result.toString();
    }

从代码中可以看出request.getAttribute的变量正好是我们POC中的变量,POC中的参数代入getRelativePath()方法,RequestDispatcher.INCLUDE_REQUEST_URI的值为’/’,不为空。pathInfo和servletPath参数的值拼接成result,getRelativePath()方法将result返回,返回内容为:’/WEB-INF/web.xml’。

Step 4 getResource() -> validate() -> normalize()

serveResource()方法继续往下,可以看到这行代码:

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第15张图片

该函数功能从字面意思上就是获取内容

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第16张图片

进入了valiate方法,该方法为路径检测方法,其中主要调用了normalize方法,重点关注该方法:

         if (normalized.endsWith("/.") || normalized.endsWith("/..")) {
     
            normalized = normalized + "/";
            addedTrailingSlash = true;
        }
        // Resolve occurrences of "//" in the normalized path
        while (true) {
     
            int index = normalized.indexOf("//");
            if (index < 0) {
     
                break;
            }
            normalized = normalized.substring(0, index) + normalized.substring(index + 1);
        }
        // Resolve occurrences of "/./" in the normalized path
        while (true) {
     
            int index = normalized.indexOf("/./");
            if (index < 0) {
     
                break;
            }
            normalized = normalized.substring(0, index) + normalized.substring(index + 2);
        }
        // Resolve occurrences of "/../" in the normalized path
        while (true) {
     
            int index = normalized.indexOf("/../");
            if (index < 0) {
     
                break;
            }

过滤掉了请求中的路径穿越符号 /…/,也就导致了该漏洞只能读取webapps目录下的文件。

Step5 ServletOutputStream.write()

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第17张图片

经过Tomcat内部流程处理,经过Tomcat的Container和Connector,最终返回给客户端。

特别注意

关键参数req_uri决定了我们可以读取webapps下其他目录的文件。当为’asdf’时无法匹配到webapps下的路径,所以路由到tomcat默认的ROOT目录;二来是为了让tomcat将请求流到DefaultServlet,从而触发漏洞。

真实文件 请求路径
webapps/manager manager/asdf
webapps/ROOT /asdf

0x2 文件包含漏洞

该漏洞的触发与上个相似,只不过servlet的分支不同,该漏洞走的是JspServlet,所有的jsp文件的路由
将漏洞利用代码修改为

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第18张图片

Step 1 JspServlet -> service() -> serviceJspFile()

从CoyoteAdapter进行分发,这里分发到jspservlet路由,因此我们要在jspservlet上下断点

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第19张图片

CVE-2020-1938 /CNVD-2020-10487漏洞调试_第20张图片

0x05 后续

  1. 自己编写ajp协议
  2. tomcat web服务器交互原理
  3. java 反射调用链分析

0x06 参考链接

  1. https://xz.aliyun.com/t/7683
  2. https://www.anquanke.com/post/id/199448#h2-7
  3. https://www.freebuf.com/column/227973.html
  4. https://www.jianshu.com/p/f902ac5d29e4

你可能感兴趣的:(CVE-2020-1938 /CNVD-2020-10487漏洞调试)