RMI攻击(二)
RMI使用
上接RMI攻击(一)服务注册
服务发现
1、Client 端向 Register 发送请求
//获取注册中心
//此处获取到本地创建的 RegistryImpl_Stub
Registry registry = LocateRegistry.getRegistry();
//调用 loolup() 查找
IHello hello = (IHello) registry.lookup("hello");
//调用 sayHello() 方法
String bai = hello.sayHello("bai");
System.out.println(bai);
registry.lookup() 即调用至 RegistryImpl_Stub#lookup()
//此处var1 为 lookup() 参数 name
public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
try {
//与 Registry 端连接
RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);
try {
ObjectOutput var3 = var2.getOutputStream();
//将 name 序列化传递
var3.writeObject(var1);
} catch (IOException var18) {
throw new MarshalException("error marshalling arguments", var18);
}
//调用 UnicastRef#invoke() 与 Registry 端通信
super.ref.invoke(var2);
Remote var23;
try {
ObjectInput var6 = var2.getInputStream();
//将 Registry 端回传结果反序列化
var23 = (Remote)var6.readObject();
} catch (IOException var15) {
throw new UnmarshalException("error unmarshalling return", var15);
} catch (ClassNotFoundException var16) {
throw new UnmarshalException("error unmarshalling return", var16);
} finally {
super.ref.done(var2);
}
return var23;
} catch (RuntimeException var19) {
throw var19;
} catch (RemoteException var20) {
throw var20;
} catch (NotBoundException var21) {
throw var21;
} catch (Exception var22) {
throw new UnexpectedException("undeclared checked exception", var22);
}
}
2、Registry 端处理请求并将结果发送至 Client 端
与服务发现阶段调用一致,依旧调用至RegistryImpl_Skel#dispatch()
,lookup 对应值为2
public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception {
if (var4 != 4905912898345647071L) {
throw new SkeletonMismatchException("interface hash mismatch");
} else {
RegistryImpl var6 = (RegistryImpl)var1;
String var7;
Remote var8;
ObjectInput var10;
ObjectInput var11;
switch(var3) {
...
case 2:
try {
var10 = var2.getInputStream();
//反序列化 client 传入的 name
var7 = (String)var10.readObject();
} catch (IOException var89) {
throw new UnmarshalException("error unmarshalling arguments", var89);
} catch (ClassNotFoundException var90) {
throw new UnmarshalException("error unmarshalling arguments", var90);
} finally {
var2.releaseInputStream();
}
//调用 RegistryImpl#lookup()
//此处获取到的是 server 端声明的动态代理类 RemoteObjectInvocationHandler,此类中包含 Server 端对此项服务监听的端口
var8 = var6.lookup(var7);
try {
ObjectOutput var9 = var2.getResultStream(true);
//将查询结果序列化传输至 client
var9.writeObject(var8);
break;
} catch (IOException var88) {
throw new MarshalException("error marshalling return", var88);
}
...
}
}
服务调用
1、Client 端获取 Register 端返回结果后调用方法
IHello hello = (IHello) registry.lookup("hello");
此处即调用至 RemoteObjectInvocationHandler#invoke(),类中包含 Server 端对此项服务监听的端口,此时 Client 端与 Server 端使用JRMP协议直接进行通信。RemoteObjectInvocationHandler#invoke() 方法之前已经分析过,若为 Object 的方法将调用 invokeObjectMethod 方法,其他的则调用 invokeRemoteMethod 方法。invokeRemoteMethod 调用UnicastRef#invoke()方法,调用至 UnicastRef#unmarshalValue(),此处前面分析过,不再赘述
protected static Object unmarshalValue(Class<?> var0, ObjectInput var1) throws IOException, ClassNotFoundException {
if (var0.isPrimitive()) {
if (var0 == Integer.TYPE) {
return var1.readInt();
} else if (var0 == Boolean.TYPE) {
return var1.readBoolean();
} else if (var0 == Byte.TYPE) {
return var1.readByte();
} else if (var0 == Character.TYPE) {
return var1.readChar();
} else if (var0 == Short.TYPE) {
return var1.readShort();
} else if (var0 == Long.TYPE) {
return var1.readLong();
} else if (var0 == Float.TYPE) {
return var1.readFloat();
} else if (var0 == Double.TYPE) {
return var1.readDouble();
} else {
throw new Error("Unrecognized primitive type: " + var0);
}
} else {
//反序列化
return var1.readObject();
}
}
2、Server 端调用 Client要求调用的方法,并将结果传给 Client
此处调用前半段与 Register 端监听调用链一致,即Server 端启动监听线程后由sun.rmi.transport.tcp.TCPTransport#handleMessages()方法处理请求 –> serviceCall() –> serviceCall() 从 ObjectTable 中获取封装的 Target 对象,并获取其中的封装的 UnicastServerRef 以及 RegistryImpl 对象。然后调用 UnicastServerRef 的 dispatch 方法
public void dispatch(Remote obj, RemoteCall call) throws IOException {
// positive operation number in 1.1 stubs;
// negative version number in 1.2 stubs and beyond...
int num;
long op;
try {
// read remote call header
ObjectInput in;
try {
in = call.getInputStream();
num = in.readInt();
} catch (Exception readEx) {
throw new UnmarshalException("error unmarshalling call header",
readEx);
}
if (num >= 0) {
//Server 端 skel 为空不进入if
if (skel != null) {
oldDispatch(obj, call, num);
return;
} else {
throw new UnmarshalException(
"skeleton class not found but required " +
"for client version");
}
}
try {
op = in.readLong();
} catch (Exception readEx) {
throw new UnmarshalException("error unmarshalling call header",
readEx);
}
/*
* Since only system classes (with null class loaders) will be on
* the execution stack during parameter unmarshalling for the 1.2
* stub protocol, tell the MarshalInputStream not to bother trying
* to resolve classes using its superclasses's default method of
* consulting the first non-null class loader on the stack.
*/
MarshalInputStream marshalStream = (MarshalInputStream) in;
marshalStream.skipDefaultResolveClass();
//从 this.hashToMethod_Map 中寻找 Client 端对应执行 Method 的 hash 值
Method method = hashToMethod_Map.get(op);
//找不到 method 报错
if (method == null) {
throw new UnmarshalException("unrecognized method hash: " +
"method not supported by remote object");
}
// if calls are being logged, write out object id and operation
logCall(obj, method);
// unmarshal parameters
Class<?>[] types = method.getParameterTypes();
Object[] params = new Object[types.length];
try {
unmarshalCustomCallData(in);
// Unmarshal the parameters
for (int i = 0; i < types.length; i++) {
//反序列化 Client 端传过来参数
params[i] = unmarshalValue(types[i], in);
}
} catch (AccessException aex) {
// For compatibility, AccessException is not wrapped in UnmarshalException
// disable saving any refs in the inputStream for GC
((StreamRemoteCall) call).discardPendingRefs();
throw aex;
} catch (java.io.IOException | ClassNotFoundException e) {
// disable saving any refs in the inputStream for GC
((StreamRemoteCall) call).discardPendingRefs();
throw new UnmarshalException(
"error unmarshalling arguments", e);
} finally {
call.releaseInputStream();
}
// make upcall on remote object
Object result;
try {
//通过反射执行方法
result = method.invoke(obj, params);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
// marshal return value
try {
ObjectOutput out = call.getResultStream(true);
Class<?> rtype = method.getReturnType();
if (rtype != void.class) {
//序列化方法执行结果,传递给 Client 端
marshalValue(rtype, result, out);
}
...
}
3、客户端获取服务端返回调用结果,并将其反序列化
此处调用逻辑与第3步完全相同,同样在 unmarshalValue() 处进行反序列化
RMI调用总结
RMI 调用整体来看分以下三步:1、注册端启动,服务端创建远程对象获取注册中心并将远程对象绑定至注册中心(图中1-5) 2、客户端获取注册中心并查找服务,注册端向客户端返回代理类(图中6-8) 3、客户端调用远程对象,服务端收到请求调用方法并将结果返回客户端(图中9-10)
RMI调用流程, [Javasec]描述如下:
RMI 底层通讯采用了Stub (运行在客户端) 和 Skeleton (运行在服务端) 机制,RMI 调用远程方法的大致如下:
1.RMI 客户端在调用远程方法时会先创建 Stub ( sun.rmi.registry.RegistryImpl_Stub )。2.Stub 会将 Remote 对象传递给远程引用层 ( java.rmi.server.RemoteRef ) 并创建 java.rmi.server.RemoteCall( 远程调用 )对象。3.RemoteCall 序列化 RMI 服务名称、Remote 对象。4.RMI 客户端的远程引用层传输 RemoteCall 序列化后的请求信息通过 Socket 连接的方式传输到 RMI 服务端的远程引用层。5.RMI服务端的远程引用层( sun.rmi.server.UnicastServerRef )收到请求会请求传递给 Skeleton ( sun.rmi.registry.RegistryImpl_Skel#dispatch )。6.Skeleton 调用 RemoteCall 反序列化 RMI 客户端传过来的序列化。7.Skeleton 处理客户端请求:bind、list、lookup、rebind、unbind,如果是 lookup 则查找 RMI 服务名绑定的接口对象,序列化该对象并通过 RemoteCall 传输到客户端。8.RMI 客户端反序列化服务端结果,获取远程对象的引用。9.RMI 客户端调用远程方法,RMI服务端反射调用RMI服务实现类的对应方法并序列化执行结果返回给客户端。10.RMI 客户端反序列化 RMI 远程方法调用结果。
如您有问题、建议、需求、合作、加群交流请后台留言或添加微信
原文始发于微信公众号(白给信安):RMI攻击(二)