RSS Feed
更好更安全的互联网

Hessian deserialization and related gadget chains

2020-11-02

Author:Longofo@Knownsec 404 Team 
Time: February 20, 2020 
Chinese version: https://paper.seebug.org/1131/

Not long ago, there was a vulnerability about Apache Dubbo Http deserialization. It was originally a normal function (it can be verified that it is indeed a normal function instead of an unexpected Post by calling the packet capture normally),the serialized data is transmitted via Post for remote calls,but if Post passes malicious serialized data, it can be used maliciously. Apache Dubbo also supports many protocols, such as Dubbo (Dubbo Hessian2), Hessian (including Hessian and Hessian2, where Hessian2 and Dubbo Hessian2 are not same), Rmi, Http, etc. Apache Dubbo is a remote call framework. Since the remote call of Http mode transmits serialized data, other protocols may also have similar problems, such as Rmi, Hessian, etc. I haven't analyzed a deserialization component processing flow completely before, just take this opportunity to look at the Hessian serialization, deserialization process, and marshalsec Several gadget chains for Hessian in the tool.

About serialization/deserialization mechanism

Serialization/deserialization mechanism(also called marshalling/unmarshalling mechanism, marshalling/unmarshalling has a wider meaning than serialization/deserialization), refer to marshalsec.pdf, the serialization/deserialization mechanism can be roughly divided into two categories:

  • Based on Bean attribute access mechanism
  • Based on Field mechanism
Based on Bean attribute access mechanism
  • SnakeYAML
  • jYAML
  • YamlBeans
  • Apache Flex BlazeDS
  • Red5 IO AMF
  • Jackson
  • Castor
  • Java XMLDecoder
  • ...

The most basic difference between them is how to set the property value on the object. They have common points and also have their own unique processing methods. Some automatically call getter (xxx) and setter (xxx) to access object properties through reflection, and some need to call the default Constructor, and some processors (referring to those listed above) deserialized objects If some methods of the class object also meet certain requirements set by themselves, they will be automatically called. There also have a XML Decoder processor that can call any method of an object. When some processors support polymorphism, for example, a certain property of an object is of type Object, Interface, abstract, etc. In order to be completely restored during deserialization, specific type information needs to be written. At this time, you can specify For more classes, certain methods of concrete class objects are automatically called when deserializing to set the property values of these objects. The attack surface of this mechanism is larger than the attack surface based on the Field mechanism because they automatically call more methods and automatically call methods when they support polymorphic features than the Field mechanism.

Based on Field mechanism

The field-based mechanism is implemented by special native methods (native methods are not implemented in java code, so it won't call more java methods such as getter and setter like Bean mechanism.) are invoked like the Bean mechanism. The mechanism of the assignment operation is not to assign attributes to the property through getters and setters (some processors below can also support the Bean mechanism if they are specially specified or configured). The payload in ysoserial is based on Java Serialization. Marshalsec supports multiple types, including the ones listed above and the ones listed below:

  • Java Serialization
  • Kryo
  • Hessian
  • json-io
  • X Stream
  • ...

As far as method made by objects are concerned, field-based mechanisms often do not constitute an attack surface. In addition, many collections, Maps, and other types cannot be transmitted/stored using their runtime representation(for example, Map, which stores information such as the hashcode of the object at runtime, but does not save this information when storing), which means All field-based marshallers bundle custom converters for certain types (for example, there is a dedicated MapSerializer converter in Hessian). These converters or their respective target types must usually call methods on the object provided by the attacker. For example, in Hessian, if the map type is deserialized, MapDeserializer is called to process the map. During this time, the map put method is called, it will calculate the hash of the recovered object and cause a hashcode call (here the call to the hashcode method is to say that the method on the object provided by the attacker must be called). According to the actual situation, the hashcode method may trigger subsequent other method calls .

Hessian Introduction

Hessian is a binary web service protocol. It has officially implemented multiple languages such as Java, Flash / Flex, Python, C ++, .NET C #, etc. Hessian, Axis, and XFire can implement remote method invocation of web services. The difference is that Hessian is a binary protocol and Axis and X Fire are SOAP protocols. Therefore, Hessian is far superior to the latter two in terms of performance, and Hessian JAVA is very simple to use. It uses the Java language interface to define remote objects and integrates serialization/deserialization and RMI functions. This article mainly explains serialization/deserialization of Hessian.

Here is a simple test of Hessian Serialization and Java Serialization:

The results are as follows:

Through this test, it can be easily seen that Hessian deserialization takes less space than JDK deserialization results. Hessian serialization takes longer than JDK serialization, but Hessian deserialization is fast. And both are based on the Field mechanism, no getter and setter methods are called, and the constructor is not called during deserialization.

Hessian concept map

The following are the conceptual diagrams commonly used in the analysis of Hessian on the Internet. In the new version, these are the overall structures, and I Just use it directly:

  • Serializer: Serialized interface
  • Deserializer: deserializer interface
  • Abstract Hessian Input: Hessian custom input stream, providing corresponding read various types of methods
  • Abstract Hessian Output: Hessian custom output stream, providing corresponding write various types of methods
  • Abstract Serializer Factory
  • Serializer Factory: Standard implementation of Hessian serialization factory
  • ExtSerializer Factory: You can set a custom serialization mechanism, which can be extended through this Factory
  • Bean Serializer Factory: Force the serialization mechanism of the Serializer Factory's default object to be specified, and specify to use the Bean Serializer to process the object

Hessian Serializer/Derializer implements the following serializers/deserializers by default. Users can also customize serializers/deserializers through interfaces/abstract classes:

When serializing, it will select the corresponding serialization according to different types of objects and attributes for serialization; when deserializing, it will also select different deserializers according to different types of objects and attributes; each type of serializer also has specific Field Serializer. Note here that Java Serializer/Java Deserializer and Bean Serializer/Bean Deserializer are not type serializers/deserializers, but belong to mechanism serializers/deserializers:

  • Java Serializer: Get all bean properties for serialization by reflection, exclude static and transient properties, and perform recursive serialization on all other properties (such as the property itself is an object)
  • Bean Serializer follows the conventions of pojo beans, scans all the methods of the bean, and finds that the properties of the get and set methods are serialized. It does not directly manipulate all the properties, which is gentle.

Hessian deserialization process

Here a demo is used for debugging. The Student property contains String, int, List, Map, Object type properties, and the property setter and getter methods are added, as well as the read Resovle, finalize, to String, hash Code methods, and The output was carried out for easy observation. Although it will not cover all the logic of Hessian, we can roughly see its appearance:

The following is the general appearance of Hessian's processing during deserialization drawn after debugging the above demo. (The picture is not clear, you can click this link):

The following details are explained by debugging to some key positions.

Get target type deserializer

First enter Hessian Input,read Object () to read the tag type identifier. Since Hessian serializes the result into a Map, the first tag is always M (ascii 77):

In the processing of case 77, the type to be deserialized is read, then this._serializerFactory.readMap (in, type)is called for processing. By default, Hessian standard used by _serializer Factory implements Serializer Factory:

First get the corresponding Deserializer of this type, then call the corresponding Deserializer.readMap (in) for processing, and see how to get the corresponding Derserializer:

The first red box mainly determines whether a deserializer of this type is cached in _cacheTypeDeserializerMap; the second red box mainly determines whether a deserializer of this type is cached in_staticTypeMap_staticTypeMap mainly stores the basic type and the corresponding deserializer; the third red box determines whether it is an array type, and if so, enters the array type processing; the fourth obtains the Class corresponding to the type, and enters this .getDeserializer(Class) gets the Deserializer corresponding to this class, this example enters the fourth:

Here it again judged whether it is in the cache, but this time it used _cacheDeserializerMap, whose type is ConcurrentHashMap, and before that it was _cacheTypeDeserializerMap, and the type was HashMap. This may be to solve the problem of obtaining in multithread . This example enters the second this.loadDeserializer(Class):

The first red box is to traverse the Serializer Factory set by the user and try to get the Serializer corresponding to the type from each factory; the second red box tries to get the Serializer corresponding to the type from the context factory; the third red box Try to create a context factory and try to get a custom deserializer of this type, and the deserializer corresponding to this type needs to be similar to xxxHessianDeserializer, where xxx indicates the class name of the type; the fourth red box is judged in turn, If not match, then use getDefaultDeserializer (Class),. This example is the fourth:

_isEnableUnsafeSerializer is true by default. The determination of this value is first determined based on whether the the unsafe field of sun.misc.Unsafe is empty, and the unsafe field ofsun.misc.Unsafe is initialized in the static code block by default and Not empty, so it is true; then it will also be based on whether the system property com.caucho.hessian.unsafe is false. If it is false, the value determined by sun.misc.Unsafe is ignored, but the system property com. caucho.hessian.unsafe is null by default, so it won't replace the result of ture. Therefore, the value of _isEnableUnsafeSerializer is true by default, so the above figure defaults to the UnsafeDeserializer used, and enters its constructor.

Get deserializer of each attribute of target type

Here all the properties of the type are obtained and the corresponding FieldDeserializer is determined. It is also determined whether there is a ReadResolve () method in the class of the type. Let's first see how the type property and FieldDeserializer determine:

Get the properties of this type and all parent classes, determine the FIeld Deserializer of the corresponding properties in turn, and the properties cannot be transient or static modified properties. The following is the Field Deserializer that determines the corresponding properties in turn. Some Field Deserializers are customized in Unsafe Deserializer.

Determine if the target type has a read Resolve() method defined

Then in the above Unsafe Deserializer constructor, it will also determine whether there is a readResolve() method in a class of this type:

Iterate through all the methods in this class to determine if there is a readResolve() method.

Okay, after that the acquired Deserializer are returned in the original way. In this example, the class uses Unsafe Deserializer, and then returns to SerializerFactory.readMap (in, type) and calls UnsafeDeserializer.readMap (in):

So far, the deserializer UnsafeDeserializer of thecom.longofo.deserialize.Student class in this example is obtained, and the Field Serializer corresponding to each field is defined. At the same time, the readResolve() method is defined in the Student class, so got the readResolve()method of this class.

Assigning object to target type

Next, an object is assigned to the target type:

An instance of this class is allocated via _unsafe.allocateInstance (classType). This method is a native method in sun.misc.Unsafe. Assigning an instance object to this class does not trigger a constructor call. The attributes are now just given the JDK default values.

Recovery of target type object attribute values

The next step is to restore the attribute values of the target type object:

Into the loop, first call in.readObject () to get the attribute name from the input stream, then match the Field Deserizlizer corresponding to the attribute from the previously determined this._fieldMap, and then call Field Deserializer on the match to process. The serialized attributes in this example are inner Map (Map type), name (String type), id (int type), and friends (List type). Here we take the inner Map attribute recovery as an example.

Take Inner Map attribute recovery as an example

The Field Deserializer corresponding to the inner Map is UnsafeDeserializer$ ObjectFieldDeserializer:

First call in.readObject (fieldClassType) to get the property value from the input stream, and then call _unsafe.putObject, the native method insun.misc.Unsafe, and will not trigger the getter and setter methods. Here's how to deal with in.readObject (fieldClassType):

Here Map type uses Map Deserializer, correspondingly calling MapDeserializer.readMap (in)method to restore a Map object:

Note the several judgments here. If it is a Map interface type, Hash Map is used. If it is a Sorted Map type, Tree Map is used. Other Maps will call the corresponding default constructor. In this example, because it is a Map interface type, Hash Map is used. Next comes the classic scenario. First use in.readObject() (this process is similar to the previous one and will not be repeated). The map's key and value objects in the serialized data are restored, and then call map.put (key, value), here is Hash Map, the put method of Hash Map will call hash(key) to trigger thekey.hashCode() method of key object, and put Val will be called in put method, and put Val will Call the key.equals (obj) method of the key object. After processing all keys and values, return to UnsafeDeserializer$ObjectFieldDeserializer:

Use the native method _unsafe.putObject to complete the inner Map property assignment of the object.

Analysis of several Hessian gadget chains

In the marshalsec tool, there are several chains available for Hessian deserialization:

  • Rome
  • X Bean
  • Resin
  • Spring Partially Comparable Advisor Holder
  • Spring Abstract Bean Factory Pointcu tAdvisor

Let‘s analyze two of them, Rome and Spring Partially Comparable Advisor Holder. Rome is triggered by HashMap.put->key.hashCode, and Spring Partially Comparable Advisor Holder is triggered by HashMap.put->key.equals. Several others are similar, either using hash Code or equals.

SpringPartiallyComparableAdvisorHolder

There are all corresponding Gadget Tests in marshalsec, which is very convenient:

To make it clearer, I extracted SpringPartiallyComparableAdvisorHolder gadget chain from marshalsec:

Look at the following trigger process:

After HessianInput.readObject(), it comes to MapDeserializer.readMap (in) to process Map type attributes, which triggers HashMap.put (key, value):

HashMap.put has called theHashMap.putVal method, and the key.equals(k) method will be triggered on the second put:

At this time, key and k are as follows, both are Hot Swappable Target Source objects:

Enter HotSwappableTargetSource.equals:

In HotSwappableTargetSource.equals, the respective target.equals method is triggered, that is, XString.equals(PartiallyComparableAdvisorHolder):

PartiallyComparableAdvisorHolder.toString is triggered here:

Triggered AspectJPointcutAdvisor.getOrder:

触发了AspectJAroundAdvice.getOrder

Here trigger BeanFactoryAspectInstanceFactory.getOrder:

This triggered SimpleJndiBeanFactory.getTYpe->SimpleJndiBeanFactory.doGetType-> SimpleJndiBeanFactory.doGetSingleton->SimpleJndiBeanFactory.lookup-> JndiTemplate.lookup->Context.lookup:

Rome

Rome is relatively simple to trigger:

Like above, I extracted the gadget chain:

Take a look at the trigger process:

Then called the hash method, which called the key.hashCode method:

Then EqualsBean.hashCode-> EqualsBean.beanHashCode is triggered:

Triggered ToStringBean.toString:

This calls JdbcRowSetImpl.getDatabaseMetadata, which triggersJdbcRowSetImpl.connect-> context.lookup:

summary

As can be seen from the above two chains, when Hessian deserialization basically uses deserialization to process the Map type, the call Map.put->Map.putVal-> key.hashCode / key.equals-> ... will be triggered, the subsequent series of starting processes are also related to polymorphic characteristics. Some class attributes are of type Object, which can be set to any class, and the hash Code and equals methods just call Certain methods of properties carry out a subsequent series of triggers. So to find such a gadget chain, we can directly find the classes with hash Code, equals, and read Resolve methods, and then people judge and construct, but this workload should be heavy; or use some chain mining tools, write rules to scan as needed.

Simple analysis of Apache Dubbo deserialization

Apache Dubbo Http deserialization

Let's take a brief look at the HTTP problem mentioned earlier, and directly use the official samples, there is a dubbo-samples-http which can be used directly, put a breakpoint directly in the DemoServiceImpl.sayHello method, and deserialize the data in RemoteInvocationSerializingExporter.doReadRemoteInvocation, using Java Serialization:

Looking at the packet, the obvious ac ed flag:

Apache Dubbo Dubbo deserialization

Also use the official Dubbo-samples-basic, the default Dubbo hessian2 protocol, Dubbo has magically modified the hessian2, but the general structure is still similar, in MapDeserializer.readMap is still similar to Hessian:

Reference

  1. https://docs.ioin.in/writeup/blog.csdn.net/_u011721501_article_details_79443598/index.html
  2. https://github.com/mbechler/marshalsec/blob/master/marshalsec.pdf
  3. https://www.mi1k7ea.com/2020/01/25/Java-Hessian%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
  4. https://zhuanlan.zhihu.com/p/44787200

Paper

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1137/

作者:江 | Categories:技术分享 | Tags:

发表评论