Java反序列化中的RMI、JRMP、JNDI、LDAP(2)

前言

乱七八糟的东西。


readObject

找一找Java里有没有其他不受黑白名单限制,又可以通过反序列化触发的反序列化点。

简单来说就是存在重开InputStream操作的类,要么是可序列化类,要么存在发起连接操作,不然数据就要通过别的可序列化类传递过来,不好控制。

有一些叫做getObject的函数,比如SignedObject、SealedObject,然而没有什么会调用他们的地方。

MarshalledObject

其get函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public T get() throws IOException, ClassNotFoundException {
if (objBytes == null) // must have been a null object
return null;

ByteArrayInputStream bin = new ByteArrayInputStream(objBytes);
// locBytes is null if no annotations
ByteArrayInputStream lin =
(locBytes == null ? null : new ByteArrayInputStream(locBytes));
MarshalledObjectInputStream in =
new MarshalledObjectInputStream(bin, lin, objectInputFilter);
@SuppressWarnings("unchecked")
T obj = (T) in.readObject();
in.close();
return obj;
}

看起来是可控的,然而看到这里有一个碍眼的objectInputFilter,它在readObject时会被赋值:

1
2
3
4
5
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject(); // read in all fields
objectInputFilter = ObjectInputFilter.Config.getObjectInputFilter(stream);
}

getObjectInputFilter会从原本的InputStream中取出其InputFilter:

1
2
3
4
public static ObjectInputFilter getObjectInputFilter(ObjectInputStream var0) {
Objects.requireNonNull(var0, (String)"inputStream");
return SharedSecrets.getJavaOISAccess().getObjectInputFilter(var0);
}

所以原本反序列化流程中存在的InputFilter也会影响这里的反序列化。

想要通过反序列化操作调用这里的get函数,就要调用ActivatableRef类的invoke函数,即RemoteRef接口的invoke函数,但是反序列化过程中方便触发的点位于RemoteObjectInvocationHandler函数中,需要使用动态代理,还需要代理的接口继承了Remote。

整条利用链应该跟以前学习过的JRMP绕过类似,这里就不验证了。

RegistryImpl_Skel

即RMI中注册中心使用的反序列化点,该类不可序列化。

当通过LocateRegistry.createRegistry创建注册中心时会实例化一个RegistryImpl对象:

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

其构造函数会开始建立监听并通过registryFilter函数注册反序列化白名单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public RegistryImpl(final int var1) throws RemoteException {
this.bindings = new Hashtable(101);
if (var1 == 1099 && System.getSecurityManager() != null) {
try {
AccessController.doPrivileged((PrivilegedExceptionAction)(new PrivilegedExceptionAction<Void>() {
public Void run() throws RemoteException {
LiveRef var1x = new LiveRef(RegistryImpl.id, var1);
RegistryImpl.this.setup(new UnicastServerRef(var1x, (var0) -> {
return RegistryImpl.registryFilter(var0);
}));
return null;
}
}), (AccessControlContext)null, new SocketPermission("localhost:" + var1, "listen,accept"));
}
...
} else {
LiveRef var2 = new LiveRef(id, var1);
this.setup(new UnicastServerRef(var2, RegistryImpl::registryFilter));
}

}

RegistryImpl_Stub

位于其list函数和lookup函数中,依旧是RMI中注册中心使用的反序列化点,该类不可序列化。

同样受到白名单影响。

UnicastRef

即RMI中服务端使用的反序列化点,其unmarshalValue函数中会有一个反序列化操作,用于反序列化客户端传输过来的对象。

可反序列化,有两条路可以来到该反序列化点,第一是跟MarshalledObject类似,同样是调用RemoteRef接口的invoke函数,但是需要满足的条件更多,需要代理的函数返回类型不是Int、Boolean等主要类型。

第二就是走正常途径,通过反序列化搞定一个RMI服务端了,听起来就不可靠。

DGCImpl_Skel

位于dispatch函数中,即JRMPListener利用链使用的反序列化点,会受到DGCImpl类的白名单影响。

DGCImpl_Stub

位于dirty函数中,即JRMPClient利用链使用的反序列化点,同样会受到DGCImpl类的白名单影响。

StreamRemoteCall

位于executeCall函数中,同样有两条路径,第一条与MarshalledObject相似,通过RemoteRef接口的invoke函数触发,即以前学习过的JRMP反序列化绕过利用链。

第二条不怎么可信,走DGCImpl_Stub的clean、dirty函数会受到白名单影响,走RegistryImpl_Stub的话它的ref成员又无法通过反序列化控制。

触发RemoteRef接口的invoke函数

可控触发点位于RemoteObjectInvocationHandler类中,需要用其代理一个Remote子接口中定义的函数才能触发。

RMIServer

存在一个getVersion函数,或许可以跟fastjson之类的会调用getter的东西一起用?

ActivationInstantiator

往上追又到了Activator接口的activate函数,没必要看了。

Activator

有一个activate函数,要通过RemoteRef接口的invoke函数触发。


参考


Java反序列化中的RMI、JRMP、JNDI、LDAP(2)
http://yoursite.com/2022/02/27/Java反序列化中的RMI、JRMP、JNDI、LDAP-2/
作者
Aluvion
发布于
2022年2月27日
许可协议