“GhostCat” Tomcat 文件读取与文件包含漏洞分析(CVE-2020-1938)

关于 GhostCat

Tomcat 最近曝出了一个新的高危漏洞,由长亭命名为“GhostCat”,该漏洞可以读取和包含 Tomcat 应用的任意文件。

通常只要开启了 ajp 端口就可以利用,需要注意是否为修复后的版本。

Tomcat server.xml 中默认配置的 ajp 端口是 8009。

    <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

而且很多的 Tomcat 是默认开启着的,并且对外开放。

fofa、zoomeye 就能搜到不少,内网环境更是重灾区

漏洞检测

现在网上已经有很多个检测和利用的工具了,可以使用 xray、YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi 来验证是否存在漏洞。

关于 AJP

简单来说,正常情况下我们使用的都是 HTTP 连接器去访问的 Tomcat 服务器。

我们直接通过浏览器去访问,就进的是 HTTP 的连接器。

而 AJP 连接器则更多出现在反向代理,由 浏览器 -> Apache 服务器 -> AJP -> Tomcat 应用

比如 NGINX 是这样配置 AJP 反代的

location /{  
       ajp_pass 127.0.0.1:8009;   
}  

再举例 Apache

<VirtualHost *:8088>
... ...
ProxyPass /examples/ ajp://192.168.17.93:8009/examples/
ProxyPassReverse /examples/ ajp://192.168.17.93:8009/examples/
</VirtualHost>

也就是说我们有时候遇见的看似是 Apache 实际上是 java 应用,基本上就是用了反向代理。

而反向代理有些是正常的 HTTP 反向代理,剩下就是 AJP 反向代理了。

漏洞复现

先启动一个漏洞影响版本内的 Tomcat 服务。

1582382082(1).jpg

启动后可以看到除了 HTTP 8080 端口外还监听了一个 8009 端口,这正是 server.xml 配置中默认设置的那个 ajp 端口

1582384368(1).jpg

使用已公开的工具可以读取到 ROOT 目录下的 WEB-INF/web.xml。

漏洞分析

POC 分析

既然已经有了 POC ,不如先来分析一下 POC 。

先从黑盒角度来分析一下这个 POC 都做了什么。

1582388841(1).jpg

首先是文件读取的数据包,虽然不是标准的 HTTP 数据包,但基本上和 HTTP 无异。

1582423796(1).jpg

然后是文件包含的。

1582423989(1).jpg

把两段数据包内容经过文本 diff 对比一下可以看出,实际上文件包含只是在请求地址后加了一个.jsp,告诉服务器这是一个 jsp 文件,但是这个 asdf.jsp 又不存在,那就从 javax.servlet.include.path_info 中取文件地址来当做 jsp 文件执行。

以上为暂时的猜测,还需要后面从代码层面分析才行。

1582427108(1).jpg

在 POC 中这些就是最主要的部分。

其他部分就是按照 Tomcat 指定的 AJP 协议格式构建数据包,详细可以参考官方文档

代码分析

代码部分的分析可以通过 debug 在java.io.File类的构造方法上打断点追溯方法栈的代码,这样比较方便。

为了能更清楚摸清逻辑这里用正向分析的方法。

先看 AJP 协议处理类 AjpProcessor 的父类 AbstractAjpProcessor,在process()方法里从二进制流里组装好请求对象后再去执行 Servlet。

1582446417(1).jpg

跟进 prepareRequest() 方法

1582446896(1).jpg

它把键和值存在了 request 的 attributes。

再来看 Tomcat 的父 web.xml,可以在 Tomcat 安装目录下的 /conf/ 目录找到。

里面有两个重要的 Servlet,一个是 DefaultServlet 另一个是 JspServlet。

文件读取(DefaultServlet)

    <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-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

这个对应的就是文件读取漏洞,他在 url-pattern 设置了接受所有 URL 请求。

它通常用来处理静态文件,如 txt、jpg,jsp 另由JspServlet处理。

所以现在要跟进 DefaultServlet 这个类看一下代码。

1582436652(1).jpg

1582436704(1).jpg

btw,这里可以看到 Tomcat 的一个比较鸡肋的PUT文件上传漏洞 CVE-2017-12615,只要设置 readOnly 为 false,就可以利用。

关于这个文件读取漏洞,则要从 DefaultServlet.serveResource() 看起,这个方法由 doGet 调用。

1582437351(1).jpg

1582437482(1).jpg

大概逻辑就是先从请求中获取路径,再读从 WEB 目录读取这个文件,如果读取到了就直接把内容写进输出流里。

这里重点是如何从请求中获取路径的。

org.apache.catalina.servlets.DefaultServlet#getRelativePath

这个方法是用来从请求中获取路径的,我把代码片段贴出来。

    protected String getRelativePath(HttpServletRequest request) {
        String result;
        if (request.getAttribute("javax.servlet.include.request_uri") != null) {
            result = (String)request.getAttribute("javax.servlet.include.path_info");
            if (result == null) {
                result = (String)request.getAttribute("javax.servlet.include.servlet_path");
            } else {
                result = (String)request.getAttribute("javax.servlet.include.servlet_path") + result;
            }

            if (result == null || result.equals("")) {
                result = "/";
            }

            return result;
        } else {
            result = request.getPathInfo();
            if (result == null) {
                result = request.getServletPath();
            } else {
                result = request.getServletPath() + result;
            }

            if (result == null || result.equals("")) {
                result = "/";
            }

            return result;
        }
    }

这里优先获取javax.servlet.include.request_uri属性值判断如不为空。

则将javax.servlet.include.servlet_pathjavax.servlet.include.path_info拼接组成一个相对路径。

反之为空则取正常的请求路径。

1582447454.jpg

实际上就是在取 AbstractAjpProcessor.prepareRequest() 方法里往 request.attributes 填充的那几条数据。

1582427108(1).jpg

再回顾一下 POC 的代码,这样就能明白是怎么回事了。

这里假设拼接后得到的 path 变量为 “/WEB-INF/web.xml”

后面的部分就是从 WEB 目录读取 “/WEB-INF/web.xml” 然后再写入到响应输出流里。

另外要注意的是这个漏洞只能读 WEB 目录下的文件,没法跳转到其他目录。

因为最终在 org.apache.catalina.webresources.AbstractFileResourceSet#file 有一个判断,如果最终组成的绝对路径开头不是 WEB 目录的话就返回null导致无法读取到文件。

1582444519(1).jpg


文件包含(JspServlet)

    <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>

    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.jsp</url-pattern>
        <url-pattern>*.jspx</url-pattern>
    </servlet-mapping>

文件包含更简单,只要请求地址结尾为 .jsp或者.jspx就会进入 JspServlet

1582448942(1).jpg

service() 方法的代码很直观,只需要设定“org.apache.catalina.jsp_file”或者“javax.servlet.include.servlet_path”和“javax.servlet.include.path_info”的值为指定的文件路径,就可以将它当做 jsp 文件执行。

比如我在根目录创建了一个 test.log 的文件,内容是一段执行系统命令的 jsp 代码。

<%@page
        import="java.io.*,java.util.*"%>
<%
Runtime run = Runtime.getRuntime();
Process p = run.exec("ipconfig");
BufferedInputStream in = new BufferedInputStream(p.getInputStream());  

BufferedReader inBr = new BufferedReader(new InputStreamReader(in));  
String a = "";
String lineStr;  
while ((lineStr = inBr.readLine()) != null)  
    a+=lineStr;
response.getWriter().println(a);
%>

然后我将 payload 部分替换成

    {'name':'req_attribute','value':['org.apache.catalina.jsp_file','/test.log']}

请求的地址后面加上一个 .jsp 让它进入 JspServlet

还有要注意,如果请求地址是 /asdf.jsp ,那 WEB 目录下就不能存在 asdf.jsp。

1582450043(1).jpg

最终将 test.log 当成 jsp 文件执行,俗称“文件包含漏洞”。

这个漏洞可以在有上传附件功能的环境下利用,随便上传一个包含JSP恶意代码的图片,然后利用这个漏洞让它解析为 jsp 文件。

修复建议

  • server.xml 里注释这段代码
<!--<Connectorport="8009" protocol="AJP/1.3"redirectPort="8443" />--> 
  • 设定只允许监听 127.0.0.1 并且加一个 secret
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" address="YOUR_TOMCAT_IP_ADDRESS" secret="YOUR_TOMCAT_AJP_SECRET"/>
  • 更新到 Tomcat 最新版本

总结

漏洞曝出来后拖了两天才开始写文章,还是朋友让写的,不然这个月可能都不更博客了。

没啥总结的,赶紧修漏洞吧。

发表留言

如未标注转载则文章均为本人原创,转载前先吱声,未授权转载我就锤爆你狗头。

人生在世,错别字在所难免,无需纠正。