前言
乱七八糟的东西。
readObject
找一找Java里有没有其他不受黑白名单限制,又可以通过反序列化触发的反序列化点。
简单来说就是存在重开InputStream操作的类,要么是可序列化类,要么存在发起连接操作,不然数据就要通过别的可序列化类传递过来,不好控制。
有一些叫做getObject的函数,比如SignedObject、SealedObject,然而没有什么会调用他们的地方。
MarshalledObject
其get函数如下:
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时会被赋值:
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject(); // read in all fields
objectInputFilter = ObjectInputFilter.Config.getObjectInputFilter(stream);
}
getObjectInputFilter会从原本的InputStream中取出其InputFilter:
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对象:
public static Registry createRegistry(int port) throws RemoteException {
return new RegistryImpl(port);
}
其构造函数会开始建立监听并通过registryFilter函数注册反序列化白名单:
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函数触发。