fastjson-流程分析
创始人
2024-11-12 11:06:27
0

参考视频:fasfjson反序列化漏洞1-流程分析

分析版本

fastjson1.2.24

JDK 8u65

分析过程

新建Person类

public class Person {      private String name;     private int age;      public Person() {         System.out.println("constructor_0");     }      public Person(String name, int age) {         this.name = name;         this.age = age;         System.out.println("constructor_2");     }      public String getName() {         System.out.println("getName");         return name;     }      public void setName(String name) {         this.name = name;         System.out.println("setName");     }      public int getAge() {         System.out.println("getAge");         return age;     }      public void setAge(int age) {         this.age = age;         System.out.println("setAge");     } } 

新建JSONTest

import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject;  public class JSONTest {     public static void main(String[] args) throws Exception {         String s = "{\"@type\":\"Person\",\"age\":18,\"name\":\"tttt\"}";          JSONObject jsonObject = JSON.parseObject(s);         System.out.println(jsonObject);     } } 

image-20240715102848791

发现parseObject(s)过程还调用了get方法。详细的过程可以跟一下上面视频。

分析下fastjson的JSON.parseObject(s);逻辑

主要逻辑在DefaultJSONParser的parse方法

public static Object parse(String text, int features) {     if (text == null) {         return null;     }      DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);     Object value = parser.parse();    //主要的逻辑在这儿      parser.handleResovleTask(value);      parser.close();      return value; } 

parse()先进行字符串的匹配

case LBRACE: //匹配到左大括号     JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));     return parseObject(object, fieldName); 

之后进入parseObject

key是@type,进入此循环,fastjson会尝试将字符串反序列化为输入的@type类。可以看到进入循环之后会调用loadCLass方法,加载类

image-20240715142244242

TypeUtils.loadClass对输入进行了预处理,不处理的话loadClass默认是不能加载数组类的

image-20240715143333345

加载完类之后,继续往下跟。到下面的位置会进行反序列化,跟进去

image-20240715143853109

public ObjectDeserializer getDeserializer(Type type) {     ObjectDeserializer derializer = this.derializers.get(type);  //首先查看有没有符合条件的默认的反序列化器,我们自己写的类,肯定是返回null     if (derializer != null) {         return derializer;     }      if (type instanceof Class) {         return getDeserializer((Class) type, type);   //之后进入这个方法     }      if (type instanceof ParameterizedType) {         Type rawType = ((ParameterizedType) type).getRawType();         if (rawType instanceof Class) {             return getDeserializer((Class) rawType, type);         } else {             return getDeserializer(rawType);         }     }      return JavaObjectDeserializer.instance; } 

getDeserializer((Class) type, type);方法中,找不到符合条件的反序列化器,则把传入的默认当作JavaBean。

image-20240715145213169

在createJavaBeanDeserializer中又调用到了JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, type, propertyNamingStrategy);

通过这个build方法去获取Person的信息,从而创建Person的反序列化器。

这里不详细写了

下面三个循环,第一个寻找public的set方法,第二个寻找public的属性,第三个寻找public的get方法(如果有了对应的set方法,那么这里不在创建get方法)

image-20240715151747655

fastjson还有一个设定是,如果找到了某个属性的set方法,那么get方法就不再add。这个操作是在最后一个循环的下面这里实现的。

image-20240715164947894

这里要说一下根据上面分析,如果针对某个属性只有getter方法,则会创建getter方法,但是fastjson对getter方法的返回值做了判断,需要满足下面条件

image-20240731102641050

之后我们就拿到了需要的反序列化器(这有个关于debug的问题,大家看视频吧,这儿就不写了,更新了Person类)

// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) //  import java.util.Map;  public class Person {     private String name;     private int age;     private Map map;      public Person() {         System.out.println("constructor_0");     }      public Person(String name, int age) {         this.name = name;         this.age = age;         System.out.println("constructor_2");     }      public String getName() {         System.out.println("getName");         return this.name;     }      public void setName(String name) {         this.name = name;         System.out.println("setName");     }      public int getAge() {         System.out.println("getAge");         return this.age;     }      public void setAge(int age) {         this.age = age;         System.out.println("setAge");     }      public Map getMap() {         System.out.println("getMap");         return this.map;     } } 

下面可以跟一下反序列化器JavaBeanDeserializer中,是如何调用构造函数,set和get方法的。

在反序列化器JavaBeanDeserializer中,只会调用setAge和setName。不会调用getAge和getName方法(上面讲了原因)和getMap(这是因为在JavaBeanDeserializer中做了判断)

} else if (fieldClass == float[][].class) {   //上面还有很多类型的判断,但是没有Map类,所以这里为假     fieldValue = lexer.scanFieldFloatArray2(name_chars);      if (lexer.matchStat > 0) {         matchField = true;         valueParsed = true;     } else if (lexer.matchStat == JSONLexer.NOT_MATCH_NAME) {         continue;     } } else if (lexer.matchField(name_chars)) {  //检查map是否在JSON中     matchField = true; } else {     continue; } 

我们想要输出getMap改一下JSON就行了。String s = "{\"@type\":\"Person\",\"age\":18,\"name\":\"tttt\",\"map\":{}}";这样就能输出getMap了。

剩下的getAge和getName是在JSON.toJSON(obj);中完成输出的。

///JSON public static JSONObject parseObject(String text) {     Object obj = parse(text);     if (obj instanceof JSONObject) {         return (JSONObject) obj;     }      return (JSONObject) JSON.toJSON(obj); } 
///JSON if (serializer instanceof JavaBeanSerializer) {     JavaBeanSerializer javaBeanSerializer = (JavaBeanSerializer) serializer;          JSONObject json = new JSONObject();     try {         Map values = javaBeanSerializer.getFieldValuesMap(javaObject);         for (Map.Entry entry : values.entrySet()) {             json.put(entry.getKey(), toJSON(entry.getValue()));         }     } catch (Exception e) {         throw new JSONException("toJSON error", e);     }     return json; } 

利用

下面弹个计算器试试

一、注意类里面都没有定义map这个属性,但是因为fastjson是按set和get等方法寻找属性的,所以并不影响。

要注意setMap中参数必须为1,否则fastjson会报错

public class Test {     public void setMap(String map) throws IOException {         Runtime.getRuntime().exec("calc");     } } 
public class JSONTest {     public static void main(String[] args) throws Exception {         String s = "{\"@type\":\"Test\",\"map\":\"aaaa\"}";         JSONObject jsonObject = JSON.parseObject(s);         System.out.println(jsonObject);     } } 

二、get方法注意不能有参数

public class Test {         public Map getMap() throws IOException { //如果返回类型改为int的话,需要在JSON语句中加入map的赋值,否则不会执行get方法。这样做程序可以在toJSON中执行get方法             Runtime.getRuntime().exec("calc");             return new HashMap();         } } 
public class JSONTest {     public static void main(String[] args) throws Exception {         String s = "{\"@type\":\"Test\"}";         JSONObject jsonObject = JSON.parseObject(s);         System.out.println(jsonObject);     } } 

如果getMap返回类型是Map,而且JSON中还给map赋值了。那么会运行两次getMap(我的Test类里面没有setMap方法)

第一次是在形成反序列化器时

第二次是在toJSON中。

相关内容

热门资讯

总结辅助!潮汕老友麻将开挂(辅... 总结辅助!潮汕老友麻将开挂(辅助挂)本来真的有辅助攻略(有挂助手)1、潮汕老友麻将开挂有没有辅助教程...
绝活辅助!好友赣南新版本脚本(... 您好,好友赣南新版本脚本这款游戏可以开挂的,确实是有挂的,需要了解加去威信【136704302】很多...
策略辅助!途游四川辅助器是真的... 策略辅助!途游四川辅助器是真的吗(辅助挂)都是存在有辅助方法(有挂辅助)1、不需要AI权限,帮助你快...
攻略辅助!边锋老友圈怎么开辅助... 攻略辅助!边锋老友圈怎么开辅助(辅助挂)本来存在有辅助器(有挂方法)1、边锋老友圈怎么开辅助辅助器安...
方案辅助!吉祥填大坑攻略(辅助... 方案辅助!吉祥填大坑攻略(辅助挂)本来存在有辅助脚本(竟然有挂)1、吉祥填大坑攻略辅助软件下载优化,...
妙招辅助!微信填大坑辅助(辅助... 妙招辅助!微信填大坑辅助(辅助挂)总是是有辅助软件(的确有挂)1、下载好微信填大坑辅助透视辅助下载之...
法子辅助!功夫川麻老是输什么情... 法子辅助!功夫川麻老是输什么情况(辅助挂)一直是真的有辅助app(真实有挂)1、首先打开功夫川麻老是...
方案辅助!天酷辅助器(辅助挂)... 方案辅助!天酷辅助器(辅助挂)切实真的是有辅助脚本(有挂教学)1、每一步都需要思考,不同水平的挑战天...
绝活辅助!朋朋政和麻为什么一直... 绝活辅助!朋朋政和麻为什么一直输(辅助挂)切实是真的有辅助工具(有挂方法)1、进入到朋朋政和麻为什么...
学习辅助!闲逸斗地主辅助(辅助... 学习辅助!闲逸斗地主辅助(辅助挂)本来是真的有辅助技巧(有挂猫腻)一、闲逸斗地主辅助游戏安装教程牌型...