前言

AJP(Apache JServ Protocol)是定向包协议。因为性能原因,使用二进制格式来传输可读性文本。WEB服务器通过TCP连接和 SERVLET容器连接。

影响版本

tomcat 6

tomcat7 ~ 7.0.100

tomcat8 ~ 8.5.51

tomcat9 ~ 9.0.31

修复参考

1.默认在conf/server.xml中禁用AJP连接器

2.强制AJP协议默认监听本地环回地址,而不是0.0.0.0

3.若使用AJP协议,设置secretRequired属性为true,强制配置secret来设置AJP协议认证凭证

4.配置属性白名单,若向AJP连接器发送任意未被识别的属性,都会响应403;

环境准备

基础知识简介

(1) Tomcat Connector(连接器)

首先来说一下Tomcat的Connector组件,Connector组件的主要职责就是负责接收客户端连接客户端请求的处理加工。每个Connector会监听一个指定端口,分别负责对请求报文的解析和响应报文组装,解析过程封装Request对象,而组装过程封装Response对象。

举个例子,如果把Tomcat比作一个城堡,那么Connector组件就是城堡的城门,为进出城堡的人们提供通道。当然,可能有多个城门,每个城门代表不同的通道。而Tomcat默认配置启动,开了两个城门(通道):一个是监听8080端口的HTTP Connector,另一个是监听8009端口的AJP Connector

Tomcat组件相关的配置文件是在conf/server.xml,配置文件中每一个元素都对应了Tomcat的一个组件(可以在配置文件中找到如下两项,配置了两个Connector组件):

1
<!-- Define a non-SSL/TLS HTTP/1.1 Connector on port 8080 -->
2
 <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
3
 .....
4
 <!-- Define an AJP 1.3 Connector on port 8009 -->
5
 <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

HTTP Connector很好理解,通过浏览器访问Tomcat服务器的Web应用时,使用的就是这个连接器;

AJP Connector是通过AJP协议和一个Web容器进行交互。在将Tomcat与其他HTTP服务器(一般是Apache )集成时,就需要用到这个连接器。AJP协议是采用二进制形式代替文本形式传输,相比HTTP这种纯文本的协议来说,效率和性能更高,也做了很多优化。

显然,浏览器只支持HTTP协议,并不能直接支持AJP协议。所以实际情况是,通过Apache的proxy_ajp模块进行反向代理,暴露成http协议(8009端口)给客户端访问,大致如下图所示:

image-20200813223619814

(2) Servlet(服务程序)

Servlet意为服务程序,也可简单理解为是一种用来处理网络请求的一套规范。主要作用是给上级容器(Tomcat)提供doGet()和doPost()等方法,其生命周期实例化、初始化、调用、销毁受控于Tomcat容器。有个例子可以很好理解:想象一下,在一栋大楼里有非常多特殊服务者Servlet,这栋大楼有一套智能系统帮助接待顾客引导他们去所需的服务提供者(Servlet)那接受服务。这里顾客就是一个个请求,特殊服务者就是Servlet,而这套智能系统就是Tomcat容器。

Tomcat中Servlet的配置是在conf/web.xml。Tomcat默认配置定义了两个servlet,分别为DefaultServletJspServlet

1
<!-- The default servlet for all web applications, that serves static    -->
2
    <!-- resources.  It processes all requests that are not mapped to other   -->
3
    <!-- servlets with servlet mappings. -->
4
    <servlet>
5
        <servlet-name>default</servlet-name>
6
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
7
        ......
8
        ......
9
    </servlet>
10
    <!-- The JSP page compiler and execution servlet, which is the mechanism  -->
11
    <!-- used by Tomcat to support JSP pages.  Traditionally, this servlet    -->
12
    <!-- is mapped to the URL pattern "*.jsp". -->
13
    <servlet>
14
        <servlet-name>jsp</servlet-name>
15
        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
16
        ......
17
        ......
18
    </servlet>
19
    ......
20
    ......
21
    <!-- The mapping for the default servlet -->
22
    <servlet-mapping>
23
        <servlet-name>default</servlet-name>
24
        <url-pattern>/</url-pattern>
25
    </servlet-mapping>
26
27
    <!-- The mappings for the JSP servlet -->
28
    <servlet-mapping>
29
        <servlet-name>jsp</servlet-name>
30
        <url-pattern>*.jsp</url-pattern>
31
        <url-pattern>*.jspx</url-pattern>
32
    </servlet-mapping>

所有的请求进入tomcat,都会流经servlet。由注释可以很明显看出,如果没有匹配到任何应用指定的servlet,那么就会流到默认的servlet(即DefaultServlet),而JspServlet负责处理所有JSP文件的请求。

(3) Tomcat内部处理请求流程

Tomcat内部处理请求的流程第一次看可能觉得会有点复杂。网上很多分析tomcat内部架构的文章,看几篇就能明白个大概了。网上看到张图,简单修改重新绘制了下,介绍一下Tomcat内部处理HTTP请求的流程,便于理解后续的漏洞分析:

image-20200813223746705

  1. 用户点击网页内容,请求被发送到本机端口8080,被Connector获得(Connector中的Processor用于封装Request,Adapter用于将封装好的Request交给Container)。
  2. Connector把该请求交给Container中的Engine来处理,并等待Engine的回应。
  3. Engine获得请求localhost/test/index.jsp,匹配所有的虚拟主机Host。
  4. Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机),名为localhost的Host获得请求/test/index.jsp,匹配它所拥有的所有的Context。Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为” “的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。

文件读取漏洞分析

首先在webapps/manager/WEB-INF/新建aaa文件,文件内容如下:

1
<%Runtime.getRuntime().exec(new String[]{"open","/Applications/Calculator.app"});%>

修改poc的perform_request:

image-20200813004826524

运行poc:python3 CNVD-2020-10487-Tomcat-Ajp-lfi.py 127.0.0.1 -p 8009 -f WEB-INF/aaa

调试过程:

首先将断点打到AjpProcessor类的service()方法。

image-20200813003209247

运行poc:

1
python3 CNVD-2020-10487-Tomcat-Ajp-lfi.py 127.0.0.1 -p 8009 -f WEB-INF/aaa

然后一步步调试,随后跟入prepareRequest()方法。该方法解析请求,将相关属性匹配到该request的属性里。

image-20200813004116390

DefaultServlet类中service中打断点,继续运行到此方法,并跟入super.service

image-20200813004346683

HttpServletservice下断点,跟进doGet

image-20200813005446780

跟进doGet中的serveResource方法,跟进serveResource中的getRelativePath的方法。

image-20200813010443159

这三个参数对应的值是:

1
static final String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri";
2
static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
3
static final String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";

也是poc中我们可控输入的值(看下源码就知道)。

跳出getRelateivePath继续跟进,跟进到resources.getResource(path),进入getResource。调用了validate函数,继续跟进这函数。validate方法内主要调用了normalize方法对path参数进行校验。

image-20200813011317566

经过validate()方法校验后,getResources()方法随后的一系列操作就通过路径读取到了资源。最后通过getOutputStream()方法获得ServletOutputStream的实例,利用ServletOutputStream.write()向输出流写入返回内容。

文件包含漏洞分析

修改poc的perform_request:

image-20200813014147337

运行poc:python3 CNVD-2020-10487-Tomcat-Ajp-lfi.py 127.0.0.1 -p 8009 -f WEB-INF/aaa

调试过程:

断点打到JspServlet类的service()方法,先将servlet_path和path_info拼接在一起,赋值给jspUri(外部可控)。

image-20200812235438362

随后进入serviceJspFile()方法,将/aaa带入Tomcat加载和处理jsp的流程里,处理流程如下:

image-20200813000438082

简单理解就是传入的aaa被当成jsp文件编译执行。带入了Tomcat处理jsp的处理流程,将jsp(aaa)转义成Servlet源代码.java(aaa.java),将Servlet源代码.java编译成Servlet类.class(aaa.class),Servlet类执行后,响应结果至客户端。

image-20200813000749396

总结

AJP协议请求中的三个属性request_uripath_infoservlet_path用户可控,如果请求的servlet不存在则

调用DefaultServlet导致文件读取漏洞。如果请求是.jsp结尾,则调用JspServlet导致文件包含漏洞(RCE)。

参考

https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi

https://xz.aliyun.com/t/7683

https://www.cnblogs.com/r00tuser/p/12343153.html

https://blog.csdn.net/flyfrommath/article/details/85039030

https://www.cnblogs.com/jajian/p/9410844.html