javasec | fastjson底层分析

admin 2024年11月17日12:02:28评论5 views字数 12202阅读40分40秒阅读模式

扫码领资料

获网安教程

javasec | fastjson底层分析

javasec | fastjson底层分析

本文由掌控安全学院 -  nn0nkey 投稿

Track安全社区投稿~  

千元稿费!还有保底奖励~(https://bbs.zkaq.cn)

前言

我们平常不得不了解一种数据就是json数据,而在java中,处理这数据的两种主流的一种是fastjson,一种是jackson 简单给大家说一下他们两个

fastjson

https://github.com/alibaba/fastjson Fastjson 是一个 Java 库,可用于将 Java 对象转换为 JSON 表示形式。它还可用于将 JSON 字符串转换为等效的 Java 对象。Fastjson 可以处理任意 Java 对象,包括您没有源代码的现有对象。 可以通过maven
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version></dependency>

JACKSON

Jackson 是一个用于处理 JSON 数据的 Java 库,它能够将 Java 对象与 JSON 之间进行转换(序列化和反序列化)。Jackson 提供了多个模块,能够灵活地处理各种数据格式和需求 可以通过maven
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.7.9</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.7.9</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.7.9</version> </dependency>
因为我主要讲讲fastjson,这里就简单介绍了 测试代码
import com.fasterxml.jackson.databind.ObjectMapper;public class Example { public static void main(String[] args) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); User user = new User("John", 30); String json = objectMapper.writeValueAsString(user); System.out.println(json); String jsonInput = "{"name":"John","age":30}"; User deserializedUser = objectMapper.readValue(jsonInput, User.class); System.out.println(deserializedUser.getName()); }}class User { private String name; private int age; public User() {} public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}
输出的结果分别为
{"name":"John","age":30}John

为什么需要了解底层

fastjson可以说是国内用得比较多的了,比如很火的若依系统里面,就有我们的fastjson
javasec | fastjson底层分析
img
可以看到用了许许多多fastjson去解析json数据 但是fastjson的漏洞随着版本的迭代,不同的绕过手法一直在更新,其中的绕过都是从底层中找到的,所以在这里给大家分析一下fatsjon到底是如何解析json数据的,才能更好的学习fastjson,而且fastjson也是java安全必学的一部分,面试几乎都会问到

底层调试分析

调试代码

package demo2;public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; }}package demo2;import com.alibaba.fastjson.JSON;public class FastjsonTest { public static void main(String[] args) { Person person = new Person("LJL", 18); String jsonString = JSON.toJSONString(person);//当然还有很多方法,这个不重要 System.out.println(jsonString); }}

过程分析

我们在String jsonString = JSON.toJSONString(person);这里下断点
跟进来到一堆重载的方法,根据你传入参数的类型来选择方法
我们传入的是这样的
public static String toJSONString(Object object) { return toJSONString(object, emptyFilters); }
来到我们的这个方法
public static String toJSONString(Object object, SerializerFeature... features) { return toJSONString(object, DEFAULT_GENERATE_FEATURE, features); }
SerializerFeature… features数组的意思,跟进看看这个方法
public enum SerializerFeature { QuoteFieldNames, UseSingleQuotes, WriteMapNullValue, WriteEnumUsingToString, WriteEnumUsingName, UseISO8601DateFormat, WriteNullListAsEmpty, WriteNullStringAsEmpty, WriteNullNumberAsZero, WriteNullBooleanAsFalse, SkipTransientField, SortField .....//很多不举了 }
是一个定义json解析的配置吧
上面的分别代表不同的意思,你翻译一下差不能明白
比如第一个
QuoteFieldNames: 是否对字段名进行引号引用。默认情况下,字段名在 JSON 中是未引用的,但有些情况下(例如包含空格或特殊字符的字段名)可能需要对其进行引号引用。
然后我们传入的是emptyFilters,看看怎么个事
static final SerializeFilter[] emptyFilters = new SerializeFilter[0];public interface SerializeFilter {}
就是空的啥也不是
然后又会去到下一个方法
public static String toJSONString(Object object, SerializeConfig config, SerializeFilter[] filters, String dateFormat, int defaultFeatures, SerializerFeature... features) { SerializeWriter out = new SerializeWriter((Writer)null, defaultFeatures, features); String var15; try { JSONSerializer serializer = new JSONSerializer(out, config); if (dateFormat != null && dateFormat.length() != 0) { serializer.setDateFormat(dateFormat); serializer.config(SerializerFeature.WriteDateUseDateFormat, true); } if (filters != null) { SerializeFilter[] var8 = filters; int var9 = filters.length; for(int var10 = 0; var10 beforeFilters = null; protected List afterFilters = null; protected List propertyFilters = null; protected List valueFilters = null; protected List nameFilters = null; protected List propertyPreFilters = null; protected List labelFilters = null; .......很多 然后就是获得的方法比如 public List getBeforeFilters() { if (this.beforeFilters == null) { this.beforeFilters = new ArrayList(); this.writeDirect = false; } 就是你自己可以定义
SerializerFeature
这个类是一个枚举类型,它定义了一系列的特性,这些特性可以被用来定制 Fastjson 的序列化行为。
public enum SerializerFeature { QuoteFieldNames, /** * */ UseSingleQuotes, /** * */ WriteMapNullValue, /** * 用枚举toString()值输出 */ WriteEnumUsingToString, /** * 用枚举name()输出 */ WriteEnumUsingName, /** * */ UseISO8601DateFormat, /** * @since 1.1 */ WriteNullListAsEmpty, .....很多
比如
serializeConfig.putFeature(SerializerFeature.WriteMapNullValue); // 输出值为null的字段serializeConfig.putFeature(SerializerFeature.WriteNullListAsEmpty); // 输出列表为null时写为[]
SerializeWriter
1.序列化对象SerializeWriter 可以将 Java 对象序列化为 JSON 格式的字符串。它支持多种数据类型的序列化,包括基本数据类型、字符串、数组、列表、地图等。
2.控制输出格式SerializeWriter 允许你控制输出的 JSON 字符串的格式,例如是否缩进、是否换行等。
3.自定义序列化规则:通过 SerializeWriter,你可以注册自定义的序列化器,以改变默认的序列化行为。这允许你为特定的类或全局配置序列化策略。
还有很多用途
JSONSerializer
JSONSerializer 是一个用于将 Java 对象序列化为 JSON 字符串的类。 回到正题
继续看我们的构造器
public static String toJSONString(Object object, // SerializeConfig config, // SerializeFilter[] filters, // String dateFormat, // int defaultFeatures, // SerializerFeature... features) { SerializeWriter out = new SerializeWriter(null, defaultFeatures, features); try { JSONSerializer serializer = new JSONSerializer(out, config);//获取序列化对象的实例 if (dateFormat != null && dateFormat.length() != 0) { serializer.setDateFormat(dateFormat);//设置我们的数据形式,因为dataFormat为空,默认 serializer.config(SerializerFeature.WriteDateUseDateFormat, true);//设置我们数据的特点,就是前面讲的各种feature } if (filters != null) { for (SerializeFilter filter : filters) { serializer.addFilter(filter); } }//如果我们filter不是空,循环获取我们的filter,增加进我们的序列化里面 serializer.write(object);写入我们的序列化对象,就是person return out.toString(); } finally { out.close(); } }
最后会调用serializer.write(object);这个
我们跟进
public final void write(Object object) { if (object == null) { out.writeNull(); return; } Class<?> clazz = object.getClass();//获取到person的class ObjectSerializer writer = getObjectWriter(clazz);//获取对象的writer try { writer.write(this, object, null, null, 0); } catch (IOException e) { throw new JSONException(e.getMessage(), e); } }
跟进getObjectWriter
public ObjectSerializer getObjectWriter(Class<?> clazz) { return config.getObjectWriter(clazz); }
调用我们config的这个方法,跟进
javasec | fastjson底层分析
img
传入我们的clazz和create赋值为ture
然后就到下面的一个方法对我们进行一系列的判断
前面的判断都是没通过,最后到
if (create) { put(clazz, createJavaBeanSerializer(clazz)); }
到createJavaBeanSerializer这个方法 我们可以把JavaBeanSerializer理解为一个序列化的方式,这个方式运用于有setter,getter方法的对象
当然还有许多其他的序列化方式
private final ObjectSerializer createJavaBeanSerializer(Class<?> clazz) { SerializeBeanInfo beanInfo = TypeUtils.buildBeanInfo(clazz, null, propertyNamingStrategy); if (beanInfo.fields.length == 0 && Iterable.class.isAssignableFrom(clazz)) { return MiscCodec.instance; } return createJavaBeanSerializer(beanInfo); }
进入buildBeanInfo方法看看怎么个事
public static SerializeBeanInfo buildBeanInfo(Class<?> beanType // , Map aliasMap // , PropertyNamingStrategy propertyNamingStrategy) { JSONType jsonType = beanType.getAnnotation(JSONType.class);//值为null,因为person根本没有注解 Map fieldCacheMap = new HashMap();//创建一个map ParserConfig.parserAllFieldToCache(beanType, fieldCacheMap);//把beanType的field(name,age)都装进fieldCacheMap List fieldInfoList = computeGetters(beanType, jsonType, aliasMap, fieldCacheMap, false, propertyNamingStrategy);/使用computeGetters方法创建一个列表,这个方法会获取一堆东西,然后对获取到的进行判断,再获取一堆东西,见p1 FieldInfo[] fields = new FieldInfo[fieldInfoList.size()]; fieldInfoList.toArray(fields); String[] orders = null;//定义为null,下面会用到 final int features; String typeName = null; if (jsonType != null) { orders = jsonType.orders(); typeName = jsonType.typeName(); if (typeName.length() == 0) { typeName = null; } features = SerializerFeature.of(jsonType.serialzeFeatures()); } else { features = 0; } FieldInfo[] sortedFields; List sortedFieldList; if (orders != null && orders.length != 0) { sortedFieldList = TypeUtils.computeGetters(beanType, jsonType, aliasMap,fieldCacheMap, true, propertyNamingStrategy); } else { sortedFieldList = new ArrayList(fieldInfoList); Collections.sort(sortedFieldList); }//又创建了一个list,不过是sortlist,会进行排序,排序规则具体在sort方法里面 sortedFields = new FieldInfo[sortedFieldList.size()]; //然后就是创建一个sortedFields sortedFieldList.toArray(sortedFields); if (Arrays.equals(sortedFields, fields)) { sortedFields = fields; }//进行判断他们是否一样,是不一样的,因为他们的顺序不一样,具体见p2 return new SerializeBeanInfo(beanType, jsonType, typeName, features, fields, sortedFields);//然后就是返回一个SerializeBeanInfo,包含着一些东西见p3 }
p1
javasec | fastjson底层分析
img
p2
javasec | fastjson底层分析
img
p3
javasec | fastjson底层分析
img
回到我们的createJavaBeanSerializer走到下个
return createJavaBeanSerializer(beanInfo); 传入我们的获取的beaninfo
public ObjectSerializer createJavaBeanSerializer(SerializeBeanInfo beanInfo) { JSONType jsonType = beanInfo.jsonType;//还是为null,if不进入 if (jsonType != null) { Class<?> serializerClass = jsonType.serializer(); if (serializerClass != Void.class) { try { Object seralizer = serializerClass.newInstance(); if (seralizer instanceof ObjectSerializer) { return (ObjectSerializer) seralizer; } } catch (Throwable e) { // skip } } if (jsonType.asm() == false) { asm = false; } } Class<?> clazz = beanInfo.beanType;//获取person的class if (!Modifier.isPublic(beanInfo.beanType.getModifiers())) {//检查beanInfo.beanType修饰是不是public getModifiers获取修饰符 因为是public跳过 return new JavaBeanSerializer(beanInfo); } boolean asm = this.asm; //asm为ture if (asm && asmFactory.classLoader.isExternalClass(clazz)//使用asmFactory创建的类加载器来判断clazz是否是由其他应用程序或JVM加载器加载的外部类。见p1 所以跳过if || clazz == Serializable.class || clazz == Object.class) { asm = false; } if (asm && !ASMUtils.checkName(clazz.getSimpleName())) { asm = false; } //遍历字段,看看有没有JSONField注解,有的话进一步检查,看看能不能被asm处理 if (asm) { for(FieldInfo field : beanInfo.fields){ JSONField annotation = field.getAnnotation(); if (annotation == null) { continue; } if ((!ASMUtils.checkName(annotation.name())) // || annotation.format().length() != 0 || annotation.jsonDirect() || annotation.serializeUsing() != Void.class ) { asm = false; break; } } } if (asm) {//如果所有字段都适合使用ASM,代码将尝试创建一个ObjectSerializer try { ObjectSerializer asmSerializer = createASMSerializer(beanInfo); if (asmSerializer != null) { return asmSerializer; } } catch (ClassFormatError e) { // skip } catch (ClassCastException e) { // skip } catch (Throwable e) { throw new JSONException("create asm serializer error, class " + clazz, e); } } return new JavaBeanSerializer(beanInfo); } public boolean isAsmEnable() { return asm; } public void setAsmEnable(boolean asmEnable) { if (ASMUtils.IS_ANDROID) { return; } this.asm = asmEnable; } public static SerializeConfig getGlobalInstance() { return globalInstance; } public SerializeConfig() { this(1024); }
总的来说这段代码的目的是为了在可能的情况下使用ASM来优化序列化过程,但如果有任何条件导致ASM不适合使用,它将退回到传统的JavaBean序列化方法。
isExternalClass判断的依据是
public boolean isExternalClass(Class<?> clazz) { ClassLoader classLoader = clazz.getClassLoader(); if (classLoader == null) { return false; }//我们的person是已经被加载过,所以可以获得它的类加载器为AppClassLoader
回到put(clazz, createJavaBeanSerializer(clazz)); 现在我们已经把
createJavaBeanSerializer(clazz)作为value加入到clazz这个key
然后来到
writer = serializers.get(clazz); 用于从serializers映射中获取与clazz对应的序列化器实例。如果serializers是一个Map,那么这个方法会返回与clazz键关联的值。 然后return writer;
回到梦开始的地方,我们终于有writer了,可以调用这个方法了
JSON类的
serializer.write(object);//序列化对象
javasec | fastjson底层分析
img
最后输出,这里就完成了

总结

1.因为我们要JSON.toJSONString(person);解析我们的person,所以会到toJSONString这个方法,并且完成一些基础的东西,比如config,filter这些,最后获取到我们配置好的 serializer
2.重点就在序列化的地方serializer.write(object);
这里调用write方法,在这个方法里会getObjectWriter,获取对象的write
3.会进入到config的这个重写方法,只要我们的writer为null,就会通过一些判读为我们获取合适的writer
4.最后我们的create为ture,走到createJavaBeanSerializer方法,通过JavaBeanSerializer这种序列化形式
5.在createJavaBeanSerializer方法中会干这戏事情
确定 JSON 类型:通过 jsonType 属性,可以知道是否有特定的序列化类被定义来处理这个 Bean。如果有,并且这个类不是 Void.class,那么尝试创建这个类的实例。 设置 ASM 模式:asm 变量表示是否使用 ASM 字节码操作框架来优化序列化过程。如果 jsonType.asm() 返回 false,则不使用 ASM。 检查类是否为公开的:如果 Bean 类不是公开的(即 Modifier.isPublic(beanInfo.beanType.getModifiers()) 返回 false),则直接创建一个 JavaBeanSerializer 实例。 检查类是否为内部类:如果类是通过 ASM 加载的,或者它是 Serializable.class 或 Object.class,则不使用 ASM。 检查字段注解:如果使用了 ASM 模式,并且字段上没有特定的注解(如 JSONField),则继续检查其他字段。如果任何一个字段有注解(例如有特定的字段名或格式化要求),则关闭 ASM 模式。 尝试创建 ASM 序列化器:如果所有条件都满足,尝试使用 ASM 模式来创建一个序列化器。如果成功,则返回这个 ASM 序列化器。 返回默认的 JavaBeanSerializer:如果没有合适的序列化器被创建(无论是通过特定的序列化类,还是通过 ASM 模式),则返回一个默认的 JavaBeanSerializer 实例。 最终返回一个序列化器
6.然后这个put方法就为这个clazz得到新value,最后获取到我们的writer = serializers.get(clazz);返回
7.获得好了writer以后回到JSONservilize类为
ObjectSerializer writer = getObjectWriter(clazz);为这个赋值 8.然后立马调用它去序列化我们的对象回到JSON类
serializer.write(object);,说明序列化操作终于结束
9.return out.toString();然后利用out的tosting输出
out就是SerializeWriter,这个类可以定于输出的样式
10.回到我们的测试类,成功输出序列化后的数据{“age”:18,”name”:”LJL”}结束

申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,

所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.

javasec | fastjson底层分析

没看够~?欢迎关注!

分享本文到朋友圈,可以凭截图找老师领取

上千教程+工具+靶场账号

javasec | fastjson底层分析

分享后扫码加我

回顾往期内容

Xray挂机刷漏洞

零基础学黑客,该怎么学?

网络安全人员必考的几本证书!

文库|内网神器cs4.0使用说明书

代码审计 | 这个CNVD证书拿的有点轻松

【精选】SRC快速入门+上分小秘籍+实战指南

    代理池工具撰写 | 只有无尽的跳转,没有封禁的IP!

javasec | fastjson底层分析

点赞+在看支持一下吧~感谢看官老爷~ 

你的点赞是我更新的动力

原文始发于微信公众号(进击的HACK):javasec | fastjson底层分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月17日12:02:28
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   javasec | fastjson底层分析http://cn-sec.com/archives/3401783.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息