本篇文章主要针对于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端口。
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
代码语言:javascript复制<?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:SourceCodetomcat-studyapache-tomcat-8.5.47-srccatalina-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。
在这里有一个很重要的参数刚才没提到,就是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下的任意文件。
引用
1. https://paper.seebug.org/1142/
2. https://www.freebuf.com/column/234123.html
3. https://www.freebuf.com/column/227973.html
4. https://www.freebuf.com/news/232748.html
5. poc代码:https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi/blob/master/CNVD-2020-10487-Tomcat-Ajp-lfi.py