Analysis of the principle of Fastjson deserialization vulnerability

0 23
0x01 IntroductionRecently, I have learned about the Fastjson deserialization vul...

0x01 Introduction

Recently, I have learned about the Fastjson deserialization vulnerability. After watching videos and reading articles, I have just barely understood the principle. Here, I will briefly summarize the content I have learned, hoping it will be helpful for everyone to understand the Fastjson deserialization vulnerability. If there are any mistakes in the article, please criticize and correct them, and I will make the necessary corrections as soon as possible.

0x02 Prior knowledge

What is a JSON string?

A string like the following is called a JSON string, which is in the form of a dictionary.

Analysis of the principle of Fastjson deserialization vulnerability

{

"name":"BossFrank",

"age":23

{}

What is Fastjson?

An open-source Java utility library used to serialize Java objects into JSON strings or deserialize JSON strings into Java objects

Dynamic assignment of object properties

1. Call the getter/setter methods of the object

2. Reflection assignment

Security issues

FastJson introduces a feature that allows the object to be deserialized to be specified through the @type field, which is the fully qualified name of the object class. Due to this @type, during Fastjson deserialization, any deserialization class can be specified. If the user passes a malicious JSON string, security issues may arise.

FastJson serialization and deserialization functions

Serialization function: JSON.toJSONString(Object object)

Deserialization function: Fastjson deserialization uses the JSON.parseObject() and JSON.parse() methods.


parseObject(): Return JSON object

The object returned by the first parse is our ordinary Java object, which will then be logically judged and finally converted into a JSON object to be returned, so, whether @type is used to specify the object or not, the object returned will always be a JSON object.

1726109601_66e257a1562d194404e9c.png!small?1726109600757

1726109618_66e257b2b82cfcc26bde5.png!small?1726109618058


There are two ways to obtain a regular object by calling parseObject, one is to pass the second parameter to parseObject, which is the class of the object to be returned.

1726109674_66e257ea145cdbd1069ad.png!small?1726109673552

In fact, the function called here is not the parseObject mentioned earlier, but a function with the same name.

1726109893_66e258c5886ad5bdc4cd2.png!small?1726109892864

The second method is to call the toJavaObject function after obtaining the JSON object.

1726109923_66e258e30bafac4520794.png!small?1726109922485

1726109946_66e258fa5ca7f996646e7.png!small?1726109945730


Parse():Return the class object itself.

1726109981_66e2591d8cd0138584c9b.png!small?1726109980875

As can be seen, after specifying the type with @type, the returned object is a person object.

1726110036_66e259546f7da3c600015.png!small?1726110036156

At the same time, we notice that when calling parse and parseObject, the getter and setter methods of the person class are automatically called. The reason for this will be known in the later process analysis. It was also found that there are differences in the getter/setter methods called by the two, that is, parseObject will call the getter and setter methods, while parse will only call the setter method.

The reason for the difference can be superficially understood as: parseObject will first create a common java class object, here is the person object, as we know before, to assign values to object properties, either reflection or set method, so we need to call the set method to assign properties to the person object, and because we need to convert it to a JSON object {"name":"mike","age":17} when returning, so we need to call the get method to get the value of each property}

0x03 Process Analysis

Set a breakpoint at the parseObject, follow in, and find that the parse function that only accepts a string is called

1726110511_66e25b2fe814aa9764ea0.png!small?1726110511566

Continue to follow in, and then call parse(String text, int features), which means that both the parseObject function that only accepts a string and the parse function will finally call here

1726110530_66e25b4271d2d31a7f951.png!small?1726110529967

Here, a default JSON parser is instantiated, and then a parse function without parameters is called, continue to follow in, and finally we come to a parse function that accepts an Object type parameter, the logic in this function is to get the token, parse according to the type of the token, such as left curly brace, JSON array, left bracket, etc.

1726110665_66e25bc917df508af0917.png!small?1726110664459

Since we are a JSON string, that is, the left curly brace, it will go to this part

1726110673_66e25bd1443d010170b3e.png!small?1726110672792

Create a new JSON object and pass it to the parseObject(final Map object, Object fieldName) function in DefaultJSONParser, continue to follow in, and there are some boundary value checks at the beginning, and the core logic is here in the try block

1726110687_66e25bdf0e865eba7d463.png!small?1726110686458

Continue to go down, define a key, and then there are some if-else statements to get the value of the key

1726110707_66e25bf33753801ddf570.png!small?1726110706698

Then we will come to this part, where the key value is judged; in fact, the key value is JSON.DEFAULT_TYPE_KEY, which means that it will parse the Java object below, so it will go into this if statement

1726110877_66e25c9d07d1e403b1d3c.png!small?17261108766761726110818_66e25c628a947cc087211.png!small?1726110818026

First, the class is loaded in the if statement, and then there are some if-else judgments, but the most important part is still here

1726110900_66e25cb499ea7d9d9e64b.png!small?1726110899951

We get the deserializer for the person class, and then use this deserializer to perform deserialization; here, the returned object is our person object, and we follow into the getDeserializer function

1726110928_66e25cd0a3a55ad144a04.png!small?1726110928203

In this function, we first try to get an existing deserializer from the cache based on our type, but there is none here, so it will not return directly, and then we call a function with the same name getDeserializer((Class<?>) type, type) in ParserConfig, and we follow into it

1726110947_66e25ce3291cce94df299.png!small?1726110946618

Here is a blacklist; continue down after passing through some big and small if conditions, and finally come to here, creating a JavaBean deserializer, and follow in

1726110964_66e25cf4cb0191fbd8479.png!small?1726110964397

First, we see there is a button asmEnable, which is set to true by default

1726110981_66e25d0515df794fa3051.png!small?1726110980609

Then there are some if conditions that can change the value of asmEnable, and then here, a new JavaBeanInfo is created. It is important to know that when creating the deserializer of the class, you need to understand some content inside the class first, such as fields, constructors, setter/getter functions, and then put these contents into JavaBeanInfo, and we follow in

1726111055_66e25d4f5b204d21bd7ce.png!small

Here we get all the fields and methods of the person class

1726111079_66e25d67ad9963b177e4b.png!small?1726111079214

Then continue down to here, briefly discuss the functions of these three loops, which are to obtain all the setter methods, fields, and getter methods that meet the conditions of this class

1726111091_66e25d73325aa2a1b8ade.png!small?1726111090716

Let's briefly talk about the logic of the first loop, traversing all methods, with some if conditions at the beginning to find methods that meet the following conditions:

  • The length of the method name is greater than 4
  • Not a static function
  • The return value is void
  • The function accepts one parameter
  • Starting with set

Then, cut off the field name after setxxx, convert it to lowercase, and then put the obtained information into the new FieldInfo and add it to the fieldList

1726111103_66e25d7f68c83b7fb2a99.png!small?1726111103288

The logic of the second loop is to add some fields such as public, private, and not existing in the list to the fieldList

The third loop is similar to the first, finding the method that meets the conditions and adding it to the fieldList, which is actually the getter method; the conditions to be met here are probably:

  • The length of the method name is greater than 4
  • Not a static function
  • Starting with get
  • The return value must be one of Collection, Map, AtomicBoolean, AtomicInteger, AtomicLong
  • The function accepts a parameter of 0
  • And the fields that have not been added to the FileList before, that is to say, there is no corresponding setter method

Then it will process the set as above,截取getxxx after the field name, convert it to lowercase, and add it to the fieldList

After processing these three loops, encapsulate this information into JavaBeanInfo and return it out, and then create and return the JavaBean deserializer based on JavaBeanInfo

1726111142_66e25da67f00b8572c68d.png!small?1726111142020

1726111334_66e25e66d6e39fb70ad4e.png!small?1726111334210

We return all the way back to here

1726111356_66e25e7c93d6646d3dcdf.png!small?1726111355934

We follow the deserialze function to see how deserialization is performed, and find that only the value changes; the specific code execution cannot be debugged because the asmEnable button is turned on, and we return the temporary deserializer created by asm

1726111380_66e25e94df0bd595f1b7f.png!small?1726111380331

If we want to debug, we need to return JavaBeanDeserializer, that is, we need to change the value of asmEnable to false. We can modify the configuration before deserialization to change the value of asmEnable

1726111394_66e25ea252aff7456344a.png!small?1726111394004

After debugging, we follow up on the deserialze function, bypass some if conditions, and here we get the fields added to the FieldList before

1726111408_66e25eb02ad2c27cc3fbc.png!small?1726111407851

Then, here is the construction of the object

1726111415_66e25eb7632494b868da5.png!small?1726111414760

1726111420_66e25ebce3ed59088bc16.png!small?1726111420239

Continue to call the assignment function

1726111426_66e25ec25d24e35ceb463.png!small?1726111426145

Then, it will call the corresponding setter function and the corresponding functions with only getter, so the call to parseObject function at the beginning will call the setter function, and at the same time, we also know that the functions with only getter are also called in the process of returning the person object

1726111460_66e25ee40a15ab7a9aee7.png!small?1726111459374


1726111468_66e25eecc3b75f41e7cb7.png!small?1726111468214

For getter functions that correspond to setter functions, they are called after the person object is returned by calling the toJSON function, that is, at this place, so the setter function will not be automatically called when parse() is called

1726111501_66e25f0ddaebd5e8a96eb.png!small?1726111508181


Let's write a malicious class to try

1726111538_66e25f3233a7b12db657b.png!small?1726111537623

1726111567_66e25f4f1e488f8479108.png!small?1726111566569

We can see that the calculator has popped up, and at the same time, we notice that it is also possible to have fields in the JSON string that do not correspond to setter/getter functions in the class or do not exist, because as we know, it will intercept the part behind the setter/getter function as the field in the processing of setter/getter function. Therefore, the variable name of the input JSON string uses the part behind the setter/getter function, and the value will be passed into the corresponding setter/getter function. In our actual use, it is unlikely that we can find malicious code directly in the setter function, usually starting from the setter/getter function as the entry point and looking for the deserialization chain.






你可能想看:
最后修改时间:
admin
上一篇 2025年03月27日 01:40
下一篇 2025年03月27日 02:03

评论已关闭