2-漏洞分析——tomcat AJP协议文件包含漏洞分析[CVE-2020-1938]

tomcat是Apache组织开发的中小型的JavaEE服务器,它实现了servlet,JSP等javaEE规范,可以提供web资源访问服务,tomcat主要提供了两种通信方式访问web资源:http协议和AJP协议。

tomcat服务器默认会在8009端口开放了一个AJP服务,客户端(浏览器)通过与tomcat服务器的8009端口建立AJP通信,可以访问服务器的web资源。但是tomcat的AJP协议在设计上存在一些缺陷,攻击者通过这点可以构造恶意的请求进行文件包含操作,从而读取tomcat服务器webapp目录下的任意文件。

漏洞环境:

apache-tomcat-8.0.50

apache-tomcat-8.0.50-src

漏洞复现:

tomcat的conf目录下有一个server.xml配置文件,该文件中有两个onnector,一个是用于处理http协议,另一个是处理AJP协议,tomcat服务器会根据客户端的请求使用对应的Connector来处理。

2-漏洞分析——tomcat AJP协议文件包含漏洞分析[CVE-2020-1938]

根据该漏洞的特点,可以通过网络工具扫描目标服务器8009端口来判断是否存在漏洞,我用nmap工具试了一下,可以扫出目标服务器的8009端口对应的ajp服务,nmap还是挺强大的。

2-漏洞分析——tomcat AJP协议文件包含漏洞分析[CVE-2020-1938]

启动tomcat服务器,然后执行poc读取tomcat服务器的目录/WEB-INF/web.xml文件,可以看到成功读取到web.xml文件,说明漏洞复现成功。

2-漏洞分析——tomcat AJP协议文件包含漏洞分析[CVE-2020-1938]

由于AJP协议是基于2-漏洞分析——tomcat AJP协议文件包含漏洞分析[CVE-2020-1938]

 整个通信过程可以分为三部分:建立tcp连接阶段,AJP通信阶段,释放tcp连接阶段,这里我们重点关注AJP通信过程。

Frame 61是客户端(浏览器)发送的第一个AJP数据包,选中Frame 61数据包查看其AJP数据包的详细2-漏洞分析——tomcat AJP协议文件包含漏洞分析[CVE-2020-1938]

 可以看到AJP请求的head部分中Method字段中指定了AJP协议的请求方式是GET,URI表示AJP请求的web资源,Version表示AJP请求的版本信息,最后是AJP请求的数据部分。

在整个AJP请求数据包中,这一部分是poc程序漏洞利用代码构造的恶意数据

2-漏洞分析——tomcat AJP协议文件包含漏洞分析[CVE-2020-1938]

漏洞分析:

当tomcat服务器收到AJP请求后会交由AjpProcessor类来处理,也就是说,AjpProcessor类是用于处理AJP协议的,但真正处理AJP请求数据的是AjpProcessor的父类org.apache.coyote.ajp.AbstractAjpProcessor。

AbstractAjpProcessor类中有一个process方法用于处理AJP请求,但是process方法内部逻辑过于复杂,无需深入分析,这里我们记住一点:该方法内部会循环调用一个prepareRequest方法处理AJP请求数据,如下所示:

2-漏洞分析——tomcat AJP协议文件包含漏洞分析[CVE-2020-1938]

 在prepareRequest方法内部中,调用了setAttribute方法将AJP请求中的数据部分封装到request请求中的attributes属性中,然后tomcat会将AJP请求分发给Servlet处理,而tomcat服务器的web.xml默认有两个Servlet,分别为处理JSP请求的JspServlet和处理所有请求的DefaultServlet。

由于POC发送的AJP请求的RUI字段的资源是jsp

2-漏洞分析——tomcat AJP协议文件包含漏洞分析[CVE-2020-1938]

根据tomcat服务器的web.xml文件中JSP请求的映射规则,这个请求会被JspServlet匹配到,那么这个AJP请求会分发给JspServlet处理。

2-漏洞分析——tomcat AJP协议文件包含漏洞分析[CVE-2020-1938]

然后JspServlet会调用service方法处理AJP请求:

    @SuppressWarnings("deprecation") // Use of JSP_FILE to be removed in 8.5.x     @Override     public void service (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 		 		//判断jspFile是否为null         String jspUri = jspFile;          if (jspUri == null) {             // JSP specified via <jsp-file> in <servlet> declaration and             // supplied through custom servlet container code             String jspFile = (String) request.getAttribute(Constants.JSP_FILE);             if (jspFile != null) {                 jspUri = jspFile;                 request.removeAttribute(Constants.JSP_FILE);             }         }         if (jspUri == null) { 		 			//从request域中获取servlet_path             jspUri = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);             if (jspUri != null) { 				//从request域中获取path_info                 String pathInfo = (String) request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO); 				//将servlet_path和path_info拼接成uri路径                 if (pathInfo != null) {                     jspUri += pathInfo;                 }             } else {                 jspUri = request.getServletPath();                 String pathInfo = request.getPathInfo();                 if (pathInfo != null) {                     jspUri += pathInfo;                 }             }         }          if (log.isDebugEnabled()) {             log.debug("JspEngine --> " + jspUri);             log.debug("t     ServletPath: " + request.getServletPath());             log.debug("t        PathInfo: " + request.getPathInfo());             log.debug("t        RealPath: " + context.getRealPath(jspUri));             log.debug("t      RequestURI: " + request.getRequestURI());             log.debug("t     QueryString: " + request.getQueryString());         }          try {             boolean precompile = preCompile(request); 			//然后调用serviceJspFile方法             serviceJspFile(request, response, jspUri, precompile);         } catch (RuntimeException e) {             throw e;         } catch (ServletException e) {             throw e;         } catch (IOException e) {             throw e;         } catch (Throwable e) {             ExceptionUtils.handleThrowable(e);             throw new ServletException(e);         }      }

 service方法会判断jspFile是否为null,如果jspFile为null的话,就会调用getAttribute方法从request域的attribute属性中获取JSP资源路径(INCLUDE_SERVLET_PATH和INCLUDE_PATH_INFO这两个常量的值是可控的),最后得到一个这样的uri路径:/WEB-INF/web.xml 。

这两个常量的定义如下:

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

接着调用了一个preCompile方法,该方法会获取request请求中的参数部分,大多数情况下都会返回false

    boolean preCompile(HttpServletRequest request) throws ServletException {  		//获取request请求中的参数部分         String queryString = request.getQueryString(); 		//参数为空则直接返回false         if (queryString == null) {             return (false);         } 		 		//......      }

将得到的uri路径传给了serviceJspFile方法参数jspUri

	private void serviceJspFile(HttpServletRequest request , HttpServletResponse response , String jspUri , boolean precompile)throws ServletException, IOException { 		//根据uri路径获取JSP         JspServletWrapper wrapper = rctxt.getWrapper(jspUri);         if (wrapper == null) {             synchronized(this) {                 wrapper = rctxt.getWrapper(jspUri);                 if (wrapper == null) { 					 					//检查uri指定的jsp资源是否存在                     if (null == context.getResource(jspUri)) {                         handleMissingResource(request, response, jspUri);                         return;                     } 					//存在就解析jsp资源                     wrapper = new JspServletWrapper(config, options, jspUri,rctxt);                     rctxt.addWrapper(jspUri,wrapper);                 }             }         }          try {             wrapper.service(request, response, precompile);         } catch (FileNotFoundException fnfe) {             handleMissingResource(request, response, jspUri);         }      }

serviceJspFile方法主要是根据参数jspUri指定的jsp资源的路径解析JSP并进行展示,getResource方法会根据jspUri中的uri路径判断指定的文件是否存在,如果存在就会解析该路径指定的资源。

如何通过文件包含漏洞来达到RCE执行目的?

前提是必须上传一个嵌入RCE代码的JSP文件到目标tomcat服务器上,当我们访问这个JSP文件时,JspServlet会将指定的JSP文件转换成java代码的.class文件,然后执行该字节码文件时就会执行RCE代码弹出计算机(具体原理可参考JspServlet源码执行原理)。

2-漏洞分析——tomcat AJP协议文件包含漏洞分析[CVE-2020-1938]

新建一个123.jsp文件插入java代码上传到目标tomcat服务器的WEB-INF目录下

<%--   Created by IntelliJ IDEA.   User: yl   Date: 2021/8/8   Time: 9:51   To change this template use File | Settings | File Templates. --%>  <%@ page contentType="text/html;charset=UTF-8" language="java" %>   <%-- 嵌入RCE代码 --%> <% Runtime.getRuntime().exec("calc.exe"); %>

成功弹出计算机

2-漏洞分析——tomcat AJP协议文件包含漏洞分析[CVE-2020-1938]

 除了JspServlet之外,DefaultServlet也可以产生任意文件包含漏洞,这两种方式的利用思路大致相同,这里就不再继续分析了,感兴趣的同学可以自行分析。

漏洞修复:

1. 在server.xml中注释掉8009端口的Connector,禁用AJP协议

2. 升级最新版本

版权声明:玥玥 发表于 2021-08-13 10:22:58。
转载请注明:2-漏洞分析——tomcat AJP协议文件包含漏洞分析[CVE-2020-1938] | 女黑客导航