zoukankan      html  css  js  c++  java
  • 【CVE-2020-1938】Tomcat AJP 任意文件读取和包含漏洞分析记录

    0x00 前言
    2020年2月20日傍晚,在某群看到有群友问CNVD的tomcat文件包含漏洞有什么消息没
    接着看到安恒信息应急响应中心公众号发了个漏洞公告
    随后chy师傅也在群里发了个阿里云的公告链接
    根据安恒和阿里云公告给出的信息我们知道是tomcat ajp服务出了问题,但具体是哪里出了问题却不知道。(这个时候我就在想阿里云是怎么知道ajp服务出了问题的呢?
     
    以tomcat7为例给出的修复版本是100,遂去github看commit,看100版本release之前的commit记录。
     
    0x01 初见端倪
    找到15天前相关ajp的commit
    从最下面往上看:
    1,先是默认不开启AJP connector,然后又是修改默认绑定地址,绑定在本地

    2,修改一个属性设置

    https://github.com/apache/tomcat/commit/40d5d93bd284033cf4a1f77f5492444f83d803e2

     强制设置认证secret,否则不启动AJP Connector

    3,添加一个新AJP属性

    https://github.com/apache/tomcat/commit/b99fba5bd796d876ea536e83299603443842feba

     应该就是新属性这里。

    因为原本的代码里面会把不识别的属性添加进去,从而导致操纵内部数据。(但怎么操纵呢?)

    https://github.com/apache/tomcat/commit/b99fba5bd796d876ea536e83299603443842feba#diff-e5bf250e10dab446db3ee424bc5c9ba8L871

    接下来重点是分析ajp协议交互,如何发送属性
    后面翻资料,看到cnvd的公告(https://www.cnvd.org.cn/webinfo/show/5415

    注意公告里面的这句话

    相关参数可控,构造特定参数”

    加上前面对commit的分析,也从侧面证实了自己的想法。
    课间休息,吃个饭回来
    安恒研究院公众号直接发了分析文章(https://mp.weixin.qq.com/s/GzqLkwlIQi_i3AVIXn59FQ)
    直接就抛出了要控制的三个属性

     

    与及两种利用方式

    1、利用DefaultServlet实现任意文件下载 (不带后缀)

    2、通过jspservlet实现任意后缀文件包含 (带jsp后缀)

     这下子就全明白了,接下来就是下载tomcat源码调试了。

    0x02 环境搭建

    环境搭建这块其实是在吃饭之前就弄了,因为一直被吹去吃饭,导致一直配置不成功源码导入idea。
    根据这个文章:https://blog.csdn.net/u013268035/article/details/81349341,我用的是tomcat 7.0.99搭建环境
    有几个注意点:
    1,我们需要下载两个tomcat,一个是tomcat源码压缩包(https://archive.apache.org/dist/tomcat/tomcat-7/v7.0.99/src/apache-tomcat-7.0.99-src.zip),另一个是tomcat可执行的压缩包。(https://archive.apache.org/dist/tomcat/tomcat-7/v7.0.99/bin/apache-tomcat-7.0.99-windows-x64.zip)
    2,根据文章,注意是将tomcat可执行文件压缩包里面的webapps和conf拷贝到源码的home目录
    3,新建一个pom.xml,复制一下即可
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     
        <modelVersion>4.0.0</modelVersion>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>Tomcat7.0.99</artifactId>
        <name>Tomcat7.0.99</name>
        <version>7.0</version>
     
        <properties>
            <java.version>1.7</java.version>
        </properties>
     
        <dependencies>
            <dependency>
                <groupId>org.apache.ant</groupId>
                <artifactId>ant</artifactId>
                <version>1.7.1</version>
            </dependency>
            <dependency>
                <groupId>ant</groupId>
                <artifactId>ant-apache-log4j</artifactId>
                <version>1.6.5</version>
            </dependency>
            <dependency>
                <groupId>ant</groupId>
                <artifactId>ant-commons-logging</artifactId>
                <version>1.6.5</version>
            </dependency>
            <dependency>
                <groupId>wsdl4j</groupId>
                <artifactId>wsdl4j</artifactId>
                <version>1.6.2</version>
            </dependency>
            <dependency>
                <groupId>javax.xml.rpc</groupId>
                <artifactId>javax.xml.rpc-api</artifactId>
                <version>1.1</version>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jdt.core.compiler</groupId>
                <artifactId>ecj</artifactId>
                <version>4.5.1</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
     
        <build>
            <finalName>Tomcat7.0</finalName>
            <sourceDirectory>java</sourceDirectory>
            <resources>
                <resource>
                    <directory>java</directory>
                </resource>
            </resources>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.0</version>
                    <configuration>
                        <encoding>UTF-8</encoding>
                        <source>${java.version}</source>
                        <target>${java.version}</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
    

    成功跑起来(后补的图)

    接下来的问题就在于如何用ajp协议与tomcat交互,然后动态调试了
    交互这块,两个方法:
    1,直接根据官方文档手撸
    2,找人家写好的
    在之前长亭科技(漏洞发现者)发了漏洞公告,还起了个名字叫“幽灵猫”,搭配发了poc检测到xray,拉下xray用wireshark抓包分析了一下
    发现只是检测一下版本而已,但是有一个标头让我觉得奇怪,“AJP_REMOTE_PORT”,这个东西应该就是属性了。

    回到正题,代码太水,手撸时间花费太多了,直接找别人写好的,github大法好,直接搜索ajp

    找到两个库,看一下READEME,选择了python版本
    拉下来研究一下代码(代码很熟悉就是chy师傅推特gif图上面的),又看到了我们熟悉的"AJP_REMOTE_PORT"

    接着研究一下代码,改写一下就可以发送我们自己的属性了。

     wireshark抓包,跟xray的poc一样了

     0x03 源码调试

    根据前面,我们可以知道关键点在org.apache.coyote.ajp.AbstractAjpProcessor prepareRequest方法
    在org.apache.coyote.ajp.AbstractAjpProcessor process方法中 在调用prepareRequest方法的地方下一个断点
     

     跟进,直接跟进到Decode extra attributes,也就是获取解析属性设置属性的地方

    循环获取,switch判断,看到如果case是属性类型,在最后的一个else里面把没有判断到的属性直接设置到request里面

     

    代码接着往下走,在预处理完了request headers之后,在adapter里面处理request

     接着调用容器来处理

     根据请求的url是否带JSP后缀,tomcat会将request交由不同的servlet来处理

    不带jsp后缀的,直接用DefaultServlet来处理的情况(文件读取)

    在HttpServlet中根据请求方法调用不同的方法处理

    这里方法是GET,一路跟进去

     跟进,最后看到处理路径的方法getRelativePath

     也就是在这里对安恒说的那三个值进行判断

     当javax.servlet.include.request_uri不为空的时候,取javax.servlet.include.path_info和javax.servlet.include.servlet_path的值进行拼接,然后返回path,之后进入lookupCache方法

     

     这里面的流程先是在缓存里面找,找不到了,然后在本地找,最终来到 org.apache.naming.resources.FileDirContext 的file方法,然后new一个File类对象。

     在File构造函数中会对path进行净化,限制了跨目录

     调用栈

    file:811, FileDirContext (org.apache.naming.resources)
    doLookup:208, FileDirContext (org.apache.naming.resources)
    doLookupWithoutNNFE:494, BaseDirContext (org.apache.naming.resources)
    lookup:475, BaseDirContext (org.apache.naming.resources)
    lookupCache:1463, ProxyDirContext (org.apache.naming.resources)
    serveResource:831, DefaultServlet (org.apache.catalina.servlets)
    doGet:435, DefaultServlet (org.apache.catalina.servlets)
    service:621, HttpServlet (javax.servlet.http)
    service:415, DefaultServlet (org.apache.catalina.servlets)
    service:728, HttpServlet (javax.servlet.http)
    internalDoFilter:303, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:208, ApplicationFilterChain (org.apache.catalina.core)
    invoke:219, StandardWrapperValve (org.apache.catalina.core)
    invoke:110, StandardContextValve (org.apache.catalina.core)
    invoke:492, AuthenticatorBase (org.apache.catalina.authenticator)
    invoke:165, StandardHostValve (org.apache.catalina.core)
    invoke:104, ErrorReportValve (org.apache.catalina.valves)
    invoke:1025, AccessLogValve (org.apache.catalina.valves)
    invoke:116, StandardEngineValve (org.apache.catalina.core)
    service:452, CoyoteAdapter (org.apache.catalina.connector)
    process:190, AjpProcessor (org.apache.coyote.ajp)
    process:654, AbstractProtocol$AbstractConnectionHandler (org.apache.coyote)
    run:317, JIoEndpoint$SocketProcessor (org.apache.tomcat.util.net)
    runWorker:1145, ThreadPoolExecutor (java.util.concurrent)
    run:615, ThreadPoolExecutor$Worker (java.util.concurrent)
    run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
    run:745, Thread (java.lang)
    

    最后就是将读取到的资源输出回来

    带JSP后缀jspservlet处理情况(文件包含)

    在jspservlet的service方法断点,从request中属性中取出org.apache.catalina.jsp_file的值放到jspFile中,之后传入到serviceJspFile中处理。

     继续跟进,会先判断jsp 文件是否存在,如果存在,随后才会初始化wrapper,最后调用JspServletWrapper的service方法来解析。

     这里继续跟进getResource,当System.getSecurityManager()=true的时候,可从远程加载文件

     继续走,先是对path进行规范化处理

     继续跟进,到最后同样也是在org.apache.naming.resources.FileDirContext file方法中新创建一个File对象,判断文件是否存在。

    而其中base变量的值为访问的容器的web根目录

    题外:tomcat内部是如何判断使用哪个servlet的呢?

    在org.apache.tomcat.util.http.mapper internalMapExtensionWrapper方法进行一系列判断,设置wrapper,其中有判断,根据后缀设置warpper,当后缀为jsp或jspx的时候都会用jspServlet来处理

     

     调用栈如下:

    internalMapExtensionWrapper:1170, Mapper (org.apache.tomcat.util.http.mapper)
    internalMapWrapper:945, Mapper (org.apache.tomcat.util.http.mapper)
    internalMap:874, Mapper (org.apache.tomcat.util.http.mapper)
    map:742, Mapper (org.apache.tomcat.util.http.mapper)
    postParseRequest:782, CoyoteAdapter (org.apache.catalina.connector)
    service:446, CoyoteAdapter (org.apache.catalina.connector)
    process:190, AjpProcessor (org.apache.coyote.ajp)
    process:654, AbstractProtocol$AbstractConnectionHandler (org.apache.coyote)
    run:317, JIoEndpoint$SocketProcessor (org.apache.tomcat.util.net)
    runWorker:1145, ThreadPoolExecutor (java.util.concurrent)
    run:615, ThreadPoolExecutor$Worker (java.util.concurrent)
    run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
    run:745, Thread (java.lang)
    

    具体的处理逻辑就不说了

    0x04 总结
    0,思路一定要冷静清晰,分析清楚最重要的点,明白自己应该做什么
    这里总结一下自己的思路:看公告,寻蛛丝马迹,然后到github找commit记录,大概理解漏洞的原理,然后根据需要的东西一步步进行
    (需要AJP交互,如何解决?需要源码调试,环境搭建?)
    1,漏洞公告以官方为准,细心留意公告的用词
    2,开源的代码首先想到到github找commit记录
    3,多方资料辅助验证
     
    不足的地方:
    0,tomcat源码运行流程,安恒是怎么知道DefaultServlet和jspservlet的呢?
    1,代码能力提升,如果没有人家的轮子,你能自己快速造出来吗?
  • 相关阅读:
    班课6
    lesson one
    班课5
    ES6之Proxy及Proxy内置方法
    ES6模板字符串
    ES6之Symbol
    ES6对象及ES6对象简单拓展
    ES6函数的拓展
    ES6数组及数组方法
    ES6字符串方法
  • 原文地址:https://www.cnblogs.com/r00tuser/p/12343153.html
Copyright © 2011-2022 走看看