shiro rememberMe 反序列化漏洞(550)

7/15/2022 Vulnerabilities笔记

# shiro remember me 反序列化(550)还有一个721

为了让浏览器或服务器重启后用户不丢失登录状态,Shiro 支持将持久化信息序列化并加密后保存在 Cookie 的 rememberMe 字段中,下次读取时进行解密再反序列化。但是在 Shiro 1.2.4 版本之前内置了一个默认且固定的加密 Key,导致攻击者可以伪造任意的 rememberMe Cookie,进而触发反序列化漏洞。

完整的调用链:

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()
1
2
3
4
5
6
7
8
9
10

# 构造思路

这里借助cc的利用链来构造。

cc主要有两条,一条是利用chainedTransformer链式调用transform方法,另一条是利用TemplatesImpl.newTransformer来动态loadClass构造好的恶意类的字节码。

而shiro的deserialize方法第75行使用了ClassResolvingObjectInputStream类,而非传统的ObjectInputStream

image-20220920103649602

它重写了ObjectInputStream类的resolveClass函数,ObjectInputStreamresolveClass函数用的是Class.forName类获取当前描述器所指代的类的Class对象。而重写后的resolveClass函数采用的是ClassUtils.forName。而这个类最终调用的是 Tomcat 下的 webappclassloader,该类会使用 Class.forName() 加载数组类,但是使用的 classloader 是 URLClassLoader,无法载入非Java自带的数组类的对象(具体原因参考:https://www.anquanke.com/post/id/192619)也就是说不能加载第三方jar包

image-20220920103622407

所以显然,这里只能使用TemplatesImpl.newTransformer这条链。

先回顾下CC2的利用链:

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

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

所以需要改造一下。

我们先将注意力关注在InvokerTransformer.transform()

image-20220920103545028

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

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

两种方式:

1.配合ChainedTransformer

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

2.无意义的String

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

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

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

public Object getValue() {
	return map.get(key);
}
1
2
3

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

public Object get(Object key) {
	//create value for key if key is not currently in th map
	if (map.containsKey(key) == false) {
		Object value = factory.transform(key);//重点
		map.put(key, value);
		return value;
	}
	return map.get(key);
}
1
2
3
4
5
6
7
8
9

这里就接上了我们前面讨论的,将构造好的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);
1
2
3
Last Updated: 12/31/2022, 2:58:06 PM