目录
1.漏洞简介
2、AJP13 协议介绍
Tomcat 主要有两大功能:
3.Tomcat 远程文件包含漏洞分析
4.漏洞复现
5、漏洞分析
6.RCE 实现的原理
- 2020 年 2 月 20 日,公开CNVD 的漏洞公告中发现 Apache Tomcat 文件包含漏洞(CVE-2020-1938)。
- Apache Tomcat 是 Apache 开源组织开发的用于处理 HTTP 服务的项目。Apache Tomcat 服务器中被发现存在文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat上所有 webapp 目录下的任意文件。
- 该漏洞是一个单独的文件包含漏洞,依赖于 Tomcat 的 AJP(定向包协议)。AJP自身存在一定缺陷,导致存在可控参数,通过可控参数可以导致文件包含漏洞。AJP协议使用率约为 7.8%,鉴于 Tomcat 作为中间件被大范围部署在服务器上,该漏洞危害较大。
- 一是充当 Web 服务器,可以对一切静态资源的请求作出回应;常见的 Web 服务器有 Apache、Nginx、IIS 等
- 二是充当 Servlet 容器。常见的 Servlet 容器有 Tomcat、Weblogic、JBOSS 等
众所周知,用户的请求是以 HTTP 协议的形式传递给 Web 服务器。我们在浏览器中对某个域名或者 ip 进行访问时,头部都会有 http 或者 https 的表示,而 AJP 浏览器是不支持的,我们无法通过 浏览器发送 AJP 的报文。AJP 这个协议并不是提供给用户使用的。
4.0.0
org.apache.tomcat
Tomcat8.0
Tomcat8.0
8.0
Tomcat8.0
java
test
java
test
org.apache.maven.plugins
maven-compiler-plugin
2.3
UTF-8
1.8
junit
junit
4.12
test
org.easymock
easymock
3.4
ant
ant
1.7.0
wsdl4j
wsdl4j
1.6.2
javax.xml
jaxrpc
1.1
org.eclipse.jdt.core.compiler
ecj
4.5.1
然后添加一个 Application,如下图所示
- 新增 Application 的配置信息。
- 在 Man class:中填入:org.apache.catalina.startup.Bootstrap。
- 在 VMoptions:中填入:-Dcatalina.home="apache-tomcat-8.5.34",并将 catalina.
- home 替换成 tomcat binary core 的目录。
- JDK 默认是 1.8,因为我安装的是 jdk1.8 版本。
- 启动过程中 Test 模块会报错,且为 TestCookieFilter.java,注释里面的测试内容即可。
- 然后访问 127.0.0.1:8080,如出现以下页面,则表示环境搭建成功,如下图所示。
RCE 如图 4-1 和图 4-2 所示
首先定位到类 org.apache.coyote.ajp.AjpProcessor。根据网上透漏的漏洞消息,得知漏洞的产生是由于 Tomcat 对 ajp 传递过来的数据的处理方式存在问题,导致用户可以控制
这 3 个参数,从而读取任意文件,甚至可以进行 RCE。
我们先从任意文件读取开始分析。环境使用 Tomcat 8.0.50 版本搭建,产生漏洞的原因并不在于 AjpProcessor.prepareRequest() 方法。 8.0.50 版本的漏洞点存在于AjpProcessor 的父类,即 AbstractAjpProcessor 抽象类的 prepareRequest() 中,如下图 5-1 所示
在这里设置断点,然后运行 exp,查看此时的调用链,如下图所示
由于此次数据传输使用的是 AJP,经过 8009 口,并非我们常见的 HTTP,因此首先由内部类 SocketPeocessore 来进行处理。
处理完成后,经过几次调用交由 AbstractAjpProcessor.prepareRequest() 方法,该方法是漏洞产生的第一个点,如图 5-3 所示
单步执行 request.setAttribute()方法,如图 5-4 和图 5-5 所示
这里我们可以看到, attributes 是一个 HashMap ,将通过 AJP 传递过来的 3 个参数循环遍历存入这个 HashMap ,如图 5-6 所示。
可以看到这里是一个 while 循环,直接来看循环完成后的结果,如图 5-7 所示
先来查看 exp 发出的数据包,如图 5-8 所示
通过使用 WireShark 抓包查看 AJP 报文的信息,其中有 4 个比较重要的参数如下。
URI:/asdf
javax.servlet.include.request_uri:/
javax.servlet.include.path_info: WEB-INF/Test.txt
javax.servlet.include.servlet_path:/
通过阅读关于 Tomcat 架构的文章和资料得知, Tomcat$ CATALINA_BASE/conf/web.xml 配置文件中默认定义了两个 Servlet :一个是 DefaultServlet ,如图 5-9所示;另一个是 JspServlet ,如图 5-92 所示。
由于$ CATALINA_BASE/conf/web.xml 文件是 tomcat 启动时默认加载的,因此二个Servlet 会默认存放在 Servlet 容器中。当用户请求的 URI 不能与任何 Servlet 匹配时,会默认交由 DefaultServlet 来处理。DefaultServlet 主要用于处理静态资源,如 HTML、图片、CSS、JS 文件等,而且为了提升服务器性能,Tomcat 将对访问文件进行缓存。按照默认配置,客户端请求路径与资源的物理路径是一致的。
根据上述内容,结合发送数据包中的“URI:/asdf”这一属性,可以判断该请求是由 DefaultServlet 进行处理的。
doGet 方法中调用了 serveResource()方法。serveResource()方法调用了 getRelativePath()
方法来进行路径拼接,如图 5-13 所示
这里就是将传入的 path_info 、 servlet_path 进行复制的地方。 request_uri 用来做判断,如果发送的数据包中没有 request_uri,就会执行 else 后面的两行代码进行赋值。这会导致漏洞利用失败,如图 5-14 所示
接下来是对路径的拼接。这里可以看到,如果传递数据时不传递 servlet_path ,则 result 在进行路径拼接时不会将“/”拼接在“ WEB-INF/web.xml ”的头部。最后拼接的结果仍然是“WEB-INF/web.xml ”,如图 5-15 所示
返回 DefaultServle.serveResource() 。然后判断 path 变量长度是否为 0 ,为 0 则调用目录重定向方法,如图 5-16 所示。
下面的代码开始读取指定的资源文件,如图 5-17 和图 5-18 所示:
执行 StandardRoot.getResource()方法,如图 5-19 所示
getResource()方法中调用了很重要的 validate()方法,并将 path 作为变量传递进去进行处理。这里会涉及不能通过“/../../”的方式来读取 webapp 目录的上层目录中的文件的原因。首先是正常请求流程,如图 5-20 所示。
我们可以看到正常请求后 return 的 result 路径就是文件所在的相对路径。 当我们尝试使用 WEB-INF/../../Test.txt 来读取 webapp 以外的目录中的文件时, 可以看到此时返回的 result 是 null,而且会抛出异常,如图 5-21 所示
所有原因都在于 RequestUtil.normalize()函数对我们传递进来的路径的处理方式。
关键的点就在下面的截图代码中。我们传入的路径是“ /WEB-INF/../../Test.txt ” , 首先程序会判断路径中是否存在“/../ ”,答案是包含且索引大于 8 ,所以第一个 if 判断不会成功,也不会跳出 while 循环。此时处理我们的路径,截取“/WEB-INF/..”以后的内容。然后 String,indexOf()函数判断路径中是否包含“/../ ”,答案是包含且索引为零,符合第二个 if 判断的条件,返回 null ,如图 5-21 所示
·此处的目标是不允许传递的路径的开头为“/../”,且不允许同时出现两个连在一起的“/../”,所以我们最多只能读取到 webapp 目录,无法读取 webapp 以外的目录中的文件。
要读取 webapp 目录下的其余目录内的文件,可以通过修改数据包中的“ URI ”参数来实现,如图 5-22 所示
程序最终会拼接出我们所指定文件的绝对路径,并作为返回值返回,如图 5-23 所示。
接下来回到 getResource() 函数进行文件读取,如图 5-24 所示
以下是任意文件读取的调用链,如图 5-25 所示
前面介绍过 Tomcat$ CATALINA_BASE/conf/web.xml 配置文件中默认定义了两个 Servlet 。上述任意文件读取利用了 DefaultServlet,而 RCE 则需要用到 JspServlet 。 默认情况下,JspServlet 的 url-pattern 为 .jsp 和 .jspx ,因此它负责处理所有 JSP 文件的请求。
- 根据 JSP 文件生成对应 Servlet 的 Java 代码(JSP 文件生成类的父类 org.apache.jasper.runtime.HttpJspBase——实现了 Servlet 接口)。
- 将 Java 代码编译为 Java 类。
- 构造 Servlet 类实例并且执行请求。
这里的“URI ”参数必须以“ .jsp ”结尾,但是该 JSP 文件可以不存在。其余 3 个参数与之前的没有区别,“ path_info ”参数对应的是我们上传的包含 JSP代码的文件。
接下来调用 serviceJspFile()方法,如图 6-6 所示