声明
好好学习,天天向上
漏洞简述
Java 是目前 Web 开发中最主流的编程语言,而 Tomcat 是当前最流行的 Java 中间件服务器之一,从初版发布到现在已经有二十多年历史,在世界范围内广泛使用。
Ghostcat(幽灵猫) 是由长亭科技安全研究员发现的存在于 Tomcat 中的安全漏洞,由于 Tomcat AJP 协议设计上存在缺陷,攻击者通过 Tomcat AJP Connector 可以读取或包含 Tomcat 上所有 webapp 目录下的任意文件,例如可以读取 webapp 配置文件或源代码。此外在目标应用有文件上传功能的情况下,配合文件包含的利用还可以达到远程代码执行的危害。
详情可以在这个网址进行了解
https://www.chaitin.cn/en/ghostcat
影响范围
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
漏洞原理&复现
tomcat
提到tomcat,最先反应过来的就是web站点,一般都是通过tomcat去开放web服务,比起apache、IIS等专业HTTP服务器,tomcat主要功能是提供JSP/Servlet的容器,对静态资源像是html的处理速度远不及apache和IIS,这就意味着,tomcat管后台服务,前面的静态页面交给apache和IIS
第一种架构,是直接通过tomcat开放web服务,包括前后端了,那就是用户直接通过http访问tomcat也是我们调试或者说平时开发环境比较常见的方式
第二种架构,专业的事情交给专业的人,apache擅长静态前端页面,tomcat擅长后端,那就是客户通过http访问
apache提供的前端页面,apche通过tomcat专有协议AJP访问tomcat(8009)
无论是哪种架构,tomcat内部的机制都一样的,Connector和Container是最核心的,通过Connector去开放端口监听请求,封装成Request,经过下面一大串部件的努力,最后返回Response
通最常见的两个Connector
8080是直接提供http服务的第一种架构的形式
8009是第二种架构
可以在server.xml中理解tomcat的体系结构
tomcat处理请求的方式是通过servlet进行请求的分发的,熟悉j2ee开发的,最先接触的就是servlet,哪个请求哪个参数交给哪个类处理,最终返回什么页面等等
而tomcat有两个默认servlet,一个是default,一个是jsp,处理机制为:
1.请求的url里面有.jsp或者.jspx就进入jsp的servlet
2.如果没匹配到就进入default的servlet
tomcat的漏洞就是基于AJP协议的,我们可以先进行源码部署
https://blog.csdn.net/qq_15719169/article/details/120936516
tomcat下载地址,我就用8.5.46
https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.46
poc地址
https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi
文件读取
源码部署完成后,我们来跟踪代码
可以看到,python发送的是AJP协议的请求,-f也就是我们读取的文件,对应请求体attributes里面的args.file,而发送的的url是/asdf,也就是让tomcat调用default的servlet
先执行poc
python2 ./tomcat.py 192.168.2.99 -p 8009 -f ./WEB-INF/web.xml
进入第一站org/apache/coyote/ajp/AjpProcessor.java的prepareRequest方法,这里会运行三次,会把我们刚刚在python里面写入的attributes传进来,给attributes进行赋值
注意第二次,已经把我们想要读取的文件注入到attribute里了
org/apache/coyote/ajp/AjpProcessor.java的service方法,而这里的requests已经变了
javax/servlet/http/HttpServlet.java的service方法,由于我们访问的url是/asdf,没匹配上jsp的servlet,那么自然会交给DefaultServlet处理
org/apache/catalina/servlets/DefaultServlet.java的service方法和doGet方法
org/apache/catalina/servlets/DefaultServlet.java的serveResource方法,会调用getRelativePath方法
org/apache/catalina/servlets/DefaultServlet.java的getRelativePath方法
要注意这一段代码了,要满足if条件才做,pathInfo和servletPath的赋值操作
if (request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null) {
// For includes, get the info from the attributes
pathInfo = (String) request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
servletPath = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
} else {
pathInfo = request.getPathInfo();
servletPath = request.getServletPath();
}
而RequestDispatcher.INCLUDE_REQUEST_URI在javax/servlet/RequestDispatcher.java有定义
所以也就自然走if里面了,最后pathInfo和servletPath被赋值,并且字符串拼接后返回给刚刚的path变量,并通过getResource根据path获取资源
我们直接ctrl点击getResource方法后,会进入org/apache/catalina/WebResourceRoot.java,但它是个接口,我们得找实现了这个接口并且实现了这个方法的地方
org/apache/catalina/webresources/StandardRoot.java,实现了这个接口
并完成了这个方法,但是这个方法还对path进行了校验,我们得看看我们传入的path是否经得住考验
进入本类的validate方法,查看使用normalize方法进行了过滤,normalize方法会将…/进行处理,无法路径穿越,这也就是我们只能在webapp中读取任意文件的原因所在
再回到org/apache/catalina/servlets/DefaultServlet.java的serveResource方法,准备响应了
将resourceBody也就是根据我们给的文件名读取的内容写到输出流
可以看到我们读取的是ROOT下面的WEB-INF/web.xml
文件包含
先更改poc,url加入.jsp,期望让tomcat交给jsp的servlet处理
在ROOT下的随便目录下写入想要被包含的文件,shell.txt,内容为,这里是我们想要执行的代码
<%Runtime.getRuntime().exec("calc.exe");%>
执行poc
python2 ./tomcat.py 192.168.2.99 -p 8009 -f shell.txt
org/apache/coyote/ajp/AjpProcessor.java的prepareRequest方法,跟文件读取一样的套路,先注入attribute,可以看到shell.txt已经注入,但是我们不是为了读取,我们目标是让shell.txt被当做jsp文件包含,从而执行calc
进入service方法,由于匹配到了.jsp,所以会交给JspServlet.java的service方法
org/apache/jasper/servlet/JspServlet.java的service方法,获取需要包含的文件名,保存在jspUri
根据jspUri调用我们熟悉的service
org/apache/jasper/servlet/JspServletWrapper.java的service方法,会调用compile,按住ctrl进入继续跟进
org/apache/jasper/JspCompilationContext.java的compile方法,会调用compile,按住ctrl进入继续跟进
org/apache/jasper/compiler/Compiler.java的compile方法,这里将shell.txt编译成class,不过我的断点为什么没有触发呢?
org/apache/jasper/servlet/JspServletWrapper.java的service方法,也就是最后执行代码的地方
执行了shell.txt中的代码