Apache Tomcat AJP协议漏洞分析(CVE-2020-1938)

本篇文章主要针对于Apache Tomcat Ajp(CVE-2020-1938)漏洞进行源码分析和漏洞利用,顺便通过这个漏洞来学习JAVA代码审计。

AJP13协议介绍

AJP的全程是Apache JServ Protocol,支持AJP协议的Web容器包括Apache Tomcat,JBoss AS / WildFly和GlassFish。Tomcat一般性的作用是作为serverlet容器来加载动态资源, 它也可以作为类似于apache、nginx、IIS等web容器来处理静态资源的请求。

在Tomcat $CATALINA_BASE/conf/server.xml默认配置了两个Connector,分别监听两个不同的端口,一个是HTTP Connector 默认监听8080端口,一个是AJP Connector 默认监听8009端口。


8080端口配置
8009端口配置

HTTP Connector通信对象为普通用户,它接收来自用户的静态或动态请求,相当于是一个可以处理servlet和jsp的web容器,但是性能上是远远不能满足业务需求的。

AJP Connector通信对象为web服务器, 在web架构中考虑到性能等要素, 通常的做法是把动静态分离, 把静态资源请求给web服务器去做, servlet和jsp请求给tomcat来处理。当用户请求进来的时候首先遇到的是web服务器, web服务器判断请求的类型如果是servlet或jsp则通过AJP Connector来传递给Tomcat,这里web服务器和Tomcat之间的通信协议就叫做AJP协议。


漏洞环境搭建

操作系统:windows10

Tomcat: apache-tomcat-8.5.47-src

1. 将源代码导入至IDEA中方便调试,因为tomcat源代码是用ant编译打包的,如果我们想要使用mavend hua, 需要增加一个文件pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

 <modelVersion>4.0.0</modelVersion>
 <groupId>org.apache.tomcat</groupId>
 <artifactId>Tomcat8.0</artifactId>
 <name>Tomcat8.0</name>
 <version>8.0</version>

 <build>
  <finalName>Tomcat8.0</finalName>
  <sourceDirectory>java</sourceDirectory>
  <resources>
   <resource>
    <directory>java</directory>
   </resource>
  </resources>
  <testResources>
   <testResource>
    <directory>test</directory>
   </testResource>
  </testResources>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
     <encoding>UTF-8</encoding>
     <source>1.8</source>
     <target>1.8</target>
    </configuration>
   </plugin>
  </plugins>
 </build>

 <dependencies>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.12</version>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>org.easymock</groupId>
   <artifactId>easymock</artifactId>
   <version>3.4</version>
  </dependency>
  <dependency>
   <groupId>ant</groupId>
   <artifactId>ant</artifactId>
   <version>1.6.5</version>
  </dependency>
  <dependency>
   <groupId>wsdl4j</groupId>
   <artifactId>wsdl4j</artifactId>
   <version>1.6.2</version>
  </dependency>
  <dependency>
   <groupId>javax.xml</groupId>
   <artifactId>jaxrpc-api</artifactId>
   <version>1.1</version>
  </dependency>
  <dependency>
   <groupId>org.eclipse.jdt.core.compiler</groupId>
   <artifactId>ecj</artifactId>
   <version>4.4.2</version>
  </dependency>
 </dependencies>
</project>


2. 然后增加一个Application配置


Main Class: org.apache.catalina.startup.Bootstrap

VM options: -Dfile.encoding=UTF-8 -Dcatalina.home="D:\SourceCode\tomcat-study\apache-tomcat-8.5.47-src\catalina-home"

JRE: jdk1.8


3. 启动tomcat

日志中我们可以看到8080和8009这2个Connector被打开了


访问http://127.0.0.1:8080


漏洞分析

1. 此处用debug模式打开tomcat


根据网上大部分的文章所提到的那样,我们先找到org.apache.coyote.ajp.AjpProcessor这个类,通过IDEA中自带的find in path功能来找到AjpProcesser


通过源码分析AjpProcessor中的prepareRequest函数将ajp里面的内容取出来设置成request对象的Attribute属性,接下来我们找到以下这段代码,这段代码的意思是解码额外的属性,从其他人的分析文章中我们能知道注入点是存在于下面这个3个Attribute.

javax.servlet.include.request_uri

javax.servlet.include.path_info

javax.servlet.include.servlet_path

prepareRequest函数会处理这3个属性,在这里我们可以手动的控制这3个属性的值。

2. 这里我们给request.setAttribute(n, v );下断点, 然后启动POC脚本:python2 .\cve-2020-1938.py 127.0.0.1  -p 8009 -f WEB-INF/web.xml

Sep Over至request.setAttribute(n, v );处

Sep Over至request.setAttribute(n, v );处

在这里我们将3个值传入HashMap,这3个值就是我们通过AJP协议传入的值。我们可以通过wireshark抓包来查看AJP协议传入的参数。


我们把精心制作的AJP13协议请求组装好发送给tomcat后,Tomcat会把该请求交给servlet来处理,在Tomcat $CATALINA_BASE/conf/web.xml这个配置文件中默认定义了两个Servlet,一个是DefaultServlet,另一个JSPServlet。


DefaultServlet
JspServlet

在这里有一个很重要的参数刚才没提到,就是URI, 如果AJP13请求中指定的URI地址可以被找到的话,请求就走JspServlet,否则找不到的话, 就走DefaultServlet. 在模拟请求中,我们给的URI地址是一个随机地址,肯定无法被找到,所以当前请求走的是DefaultServlet路径。


3. 在这里我们下断点给java.org.apache.catalina.servlets.DefaultServlet.java文件中的doGet方法,因为协议走的Get请求。

下断点至serveResource处,且再次发送AJP请求。

找到传入的3个参数

4. Setp Info至getRelativePath, 分析下面这段代码。

如果这个参数RequestDispatcher.INCLUDE_REQUEST_URI非空的话,则

查看RequestDispatcher.INCLUDE_REQUEST_URI = javax.servlet.include_uri

所以servletPath和pathInfo就被赋予外置attribute,pathInfo = javax.servlet.include.path_info  && servletPath = javax.servlet.include.servlet_path.

我们在POC代码中定义的三个属性达到了WEB目录下任意文件读取的作用

javax.servlet.include.request_uri

javax.servlet.include.path_info

javax.servlet.include.servlet_path。


5. 完成pathInfo和servletPath值的获取后,将进行路径的拼接, 拼接的结果仍然是“/WEB-INF/web.xml”

接着step info, 跳回至serveResource方法,这里debug = 0所以跳过

继续单步调试, 这里的代码将获取资源文件

查看getResource代码, 发现validate函数处理了传进来的path, 这里不跟进到validate函数内部了,先跳过。

接着我们跳回至getResource函数,得到处理过的path = /WEB-INF/web.xml.


getResource函数结束, 得到最后返回的文件资源,可以看到我们获取到了/WEB-INF/web.xml这个本不应该得到的文件地址。

总结

这个漏洞的成因是因为AJP协议的核心参数可以被恶意修改,攻击者利用漏洞构造特定参数,读取服务器webapp/ROOT下的任意文件。

admin
admin管理员

上一篇:腾讯云语音识别云开发微信小程序
下一篇:CDN入门科普

留言评论

暂无留言