CVE-2020-1938漏洞分析

前言

跟各类大拿比,写的很晚,尽量详细的写下此漏洞和挖掘过程中的思维CVE-2020-1938,官方威胁公告:CNVD官方说明

AJP协议

首先得了解下AJP协议,避免在深入挖掘此漏洞时遇到坑比如我,AJP说白了就是一种传输层协议,由tomcat出品,显然是一种面向连接,效率较高的传输协议,下面这个图百度的,基本就这么回事了。具体的协议内容见(下面在分析代码的时候会遇到各种getbytes的枚举,其中各数字代表应用层向下封装的头部分,物数网传会表应不多介绍了哈,了解下OSI7层模型):官方AJP协议介绍文档
CVE-2020-1938漏洞分析_第1张图片
/conf/server.xml中可配置connector,这个也是官方临时修复建议需要注释掉的点也就是关闭8009端口,关闭了AJP协议,自然就没有此漏洞了,此漏洞需要借助ajp协议进行攻击。
CVE-2020-1938漏洞分析_第2张图片

漏洞成因

tomcat7.0.99举例:下载位置

首先看二进制的输入点,定位到ajp中调用socket的点,位置为/org/apache/coyote/ajp目录,索引socket关键字。

先看下安恒给的点AjpProcessor.java
CVE-2020-1938漏洞分析_第3张图片
CVE-2020-1938漏洞分析_第4张图片
预请求的prepareRequest()方法来自于父类AbstractAjpProcessor,找下
CVE-2020-1938漏洞分析_第5张图片

关键点在如下代码中,在这个之前呢,贴几个图,当然可以下一个tomcat版本自己看,各版本有细微差异,比如枚举的值有的直接就是赋值硬编码,可能是后来改进了。
CVE-2020-1938漏洞分析_第6张图片
CVE-2020-1938漏洞分析_第7张图片
CVE-2020-1938漏洞分析_第8张图片
CVE-2020-1938漏洞分析_第9张图片
下面的代码加上详细注释方便新手理解,因为一般相对了解javaweb的人都比较难看懂。

// Decode extra attributes
        boolean secret = false;
        //定义一个byte类型的参数attributecode
        byte attributeCode;
        //while()!=结构,当ATBcode=requestHM.getbyte(),atbcode=请求头信息类型转换的byte,当其不等于255(这个对应Constants.SC_A_ARE_DONE的值0xFF)时,执行下面的switch,case语句,这里是个恒true的循环。
        while ((attributeCode = requestHeaderMessage.getByte())
                != Constants.SC_A_ARE_DONE) {

            switch (attributeCode) {
//当case到Constants.SC_A_REQ_ATTRIBUTE时,这个枚举的值是10,在AJP协议封装里,代表attribute类型。
            case Constants.SC_A_REQ_ATTRIBUTE :
            //tmpMB在上面有定义,提取byte[]数组里面的子byte,再tostring转换为string类型,有同学可能不能理解为啥定义两个相同的n和v参数,这里是为了在下面一个进行判断一个进行赋值,tomcat的开发人员都是大佬,这样写可以避免参数的混乱。
                requestHeaderMessage.getBytes(tmpMB);
                String n = tmpMB.toString();
                requestHeaderMessage.getBytes(tmpMB);
                String v = tmpMB.toString();
                /*
                 * AJP13 misses to forward the local IP address and the
                 * remote port. Allow the AJP connector to add this info via
                 * private request attributes.
                 * We will accept the forwarded data and remove it from the
                 * public list of request attributes.
                 */
                 //赋值IP给request
                if(n.equals(Constants.SC_A_REQ_LOCAL_ADDR)) {
                    request.localAddr().setString(v);
                    //赋值端口给request
                } else if(n.equals(Constants.SC_A_REQ_REMOTE_PORT)) {
                    try {
                        request.setRemotePort(Integer.parseInt(v));
                    } catch (NumberFormatException nfe) {
                        // Ignore invalid value
                    }
                    //赋值协议https协议key给request
                } else if(n.equals(Constants.SC_A_SSL_PROTOCOL)) {
                    request.setAttribute(SSLSupport.PROTOCOL_VERSION_KEY, v);
                    //v的值直接setAttribute给request的属性。
                } else {
                    request.setAttribute(n, v );

上面的代码是核心漏洞输入点,既然找到了输入点,那么就要找AJP-REQUEST的输出点和REQUEST-RESPONSE的点。
接下来,就要利用到tomcat的内核连接器与容器的桥梁——CoyoteAdapter类了(百度。。。。。),下面的图是子类AjpNioProcessor的点,关于Adapter类如何处理request的其实不重要,很多人可能会苦读这块内容导致被绕晕。
CVE-2020-1938漏洞分析_第10张图片
jspServlet处理uri为.jsp后缀请求的点

CVE-2020-1938漏洞分析_第11张图片
CVE-2020-1938漏洞分析_第12张图片
看下serviceJspfile,这里就是通过getresource获取jsp文件内容,然后传入wrapper对象再给jspservletwrapper对象处理
CVE-2020-1938漏洞分析_第13张图片

到这里基本漏洞的原理就差不多了,最后就是注意一下普通后缀defaultservlet中会对path进行限制跨目录
CVE-2020-1938漏洞分析_第14张图片
有些不同应该是版本不同造成的区别,我这里如果是普通后缀的文件是交由defaultservlet处理的,当然我也没看出能包含的内容。差不多就这样吧。

复现方式可以利用脚本,也可以利用java客户端发送ajp数据包。

修复:我这里能想到的就是在setAtt的时候再进行几次判断,从输入点考虑。当然还是不太理解为什么要有这样的功能,或者是直接最后一个else不要了,直接返回一个错误呢?
后续再思考下。。。

你可能感兴趣的:(CVE-2020-1938漏洞分析)