我们专注漏洞检测方向:danenmao、arnoxia、皇天霸、lSHANG、KeyKernel、BugQueen、zyl、隐形人真忙、oxen(不分先后)
欢迎关注我们的微信公众号:EnsecTeam
作者:隐形人真忙 & arnoxia
1.TL;DR
在排查业务线安全问题时,我们发现内部扫描平台的S2-052扫描插件扫出了某业务线的一例RCE漏洞,但是业务线反馈并没有使用Struts2框架。
通过深入分析,发现是由于SpringMVC中MarshallingHttpMessageConverter使用不当导致的反序列化漏洞,从而造成一系列的安全风险,本文主要深入分析该问题的技术原理与解决方案。
2.HttpMessageConverter机制
要理解这个漏洞,首先需要了解SpringMVC的HttpMessageConverter机制。HttpMessageConverter接口是Spring MVC中用来对HTTP Body部分的数据进行定制化转换的。
该接口的定义如下:
booleancanWrite(Class<?> var1, @Nullable MediaType var2);
//获取支持的MIME类型
List<MediaType>getSupportedMediaTypes();
//读操作
T read(Class<? extendsT> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException;
//写操作
void write(T var1, @NullableMediaType var2, HttpOutputMessage var3) throws IOException,HttpMessageNotWritableException;
}
主要有五个需要实现的方法,即判断是否可读、是否可写,获取MIME的类型,以及读和写操作。Spring web模块中提供了一些内置的接口实现类,比如StringHttpMessageConverter,FormHttpMessageConverter,MarshallingHttpMessageConverter等。
其中,这里的MarshallingHttpMessageConverter 主要用来实现对XML进行序列化和反序列化的,用户只需要设置执行XML序列化和反序列化的类,也就是给MarshallingHttpMessageConverter提供相应的Marshaller和Unmarshaller即可。原理大致如下图:
后端设置好MarshallingHttpMessageConverter之后,就可以执行对body的序列化和反序列化了,将body数据与java对象进行相互转换。当用户发出请求报文后,MarshallingHttpMessageConverter会调用相应的Unmarshaller对body进行反序列化操作,将body中的XML还原为Java对象。在响应时,SpringMVC会将java对象再序列化为XML文档返回给用户。
MarshallingHttpMessageConverter可以处理的MIME类型默认为[application/xml,text/xml, application/*+xml],可以在controller获取注入的convert对象执行getSupportedMediaTypes方法来查看。
3.XStreamMarshaller反序列化问题
XStream反序列化漏洞想必大家都不陌生,S2-052就是由于这个问题引发的。Spring-oxm中提供了一系列的marshaller,其中有XStreamMarsaller,这个编码器的内部是通过XStream完成的对象和XML之间转换的。
我们在spring中注入MarshallingHttpMessageConverter的时候可以指定XStreamMarshaller作为marshaller和unmarshaller,springboot配置代码如下:
首先注入XStreamMarshaller,然后注入MarshallingHttpMessageConverter,并设置converter的marshaller和unmarshaller对象。
根据上一章节的分析,外部实际上直接可以通过修改Content-Type为application/xml等MIME类型来触发反序列化。使用marshalsec构造payload,然后发包,效果如下:
我们来进行动态调试探究一下具体的处理过程,首先在XStreamMarshaller的doUnmarshal方法下断点,然后发送payload,断点处的调用栈信息如下:
可以看到最终调用了XStreamMarshaller的doUnmarshal方法,这个方法又最终调用了XStream的unmarshal方法:
这个其实和XStream反序列化漏洞原理如出一辙了,XStream.fromXML最终也是调用了unmarshall方法来完成的反序列化过程,外界传入精心构造的payload就会触发远程命令执行。
实际上这个问题已经被pwntester大牛发现,但是该问题并没有得到足够的重视,同时Spring官方把这个问题推给了XStream和开发者。
4.XStream反序列化问题深入分析
pwntester在其blog中给出了一个简单的payload:通过在Post中发送特意构造的xml数据:
<sorted-set>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="java.beans.EventHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>calc</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
</sorted-set>
借助java中的对象动态代理机制,在xstream将xml转换为对象过程中执行特定命令,payload详细分析如下:
getMethodArgumentValues org.springframework.web.method.support.InvocableHandlerMethod
resolveArgument mvc.method.annotation.RequestResponseBodyMethodProcessor
.....
readFromSource http.converter.xml.MarshallingHttpMessageConverter.readFromSource
unmarshal Object org.springframework.oxm.Unmarshaller.unmarshal
...
doUnmarshalorg.springframework.oxm.xstream.XStreamMarshaller.doUnmarshal
unmarshal com.thoughtworks.xstream.XStream.unmarshal
在解析http请求参数过程中会调用spring-oxm的unmarshal函数,而unmarshal函数则会调用相应的子类方法,最终调用了xstream的unmarshal方法。
XStream.unmarshal
TreeUnmarshaller.start 开始xml解析
TreeUnmarshaller.covert
...
TreeSetConverter.unmarshal 获取第一个节点为sorted-set后,调用TreeSetCoverter方法
TreeMapConverter.populateTreeMap
TreeSetConverter.putCurrentEntryIntoMap 解析第一对entry,即dynamic-proxy
AbstractCollectionConverter.readItem 生成proxy65$对象
readClassType 获取类类型,即DynamicProxyMapper$DynamicProxy
covertAnother 递归解析对象
....
DynamicProxyConverter.unmarshal 调用DynamicProxyConverter生成$Proxy65对象
result.putAll 将解析结果存入hashmap中
TreeMap.put(key, value) 将key,value存入hash map
在解析xml对象时,根据节点类型调用不同的coverter,如果为sorted-set,调用TreeSetCoverter.unmarshal;如果为map,则调用MapCoverter.unmarshal。针对每一对entry,采用递归调用的方式进行解析,在解析完所有对象后,通过调用TreeMap.put函数将key和value存入hash map,此时key和value值都为:
为一个EventHandler对象,target为ProcessBuilder对象,而action为start
当xtream解析完xml后,在将key和value存入hashmap中时:
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key,key); //比较key,进行类型检查
此时key值即为$Proxy65,然后调用java.util.TreeMap.compare函数:
@SuppressWarnings("unchecked")
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
此时comparator为null,返回((Comparable<? super K>)k1).compareTo((K)k2),此时将调用$Proxy65.compareTo(Object)方法,因此会调用代理类的invoke方法,此时即为EventHandler.invoke(proxy, method,args)。其中,proxy为$Proxy65,method为compareTo,args为null。
public Object invoke(final Object proxy, final Method method, final Object[] arguments) {
....
return invokeInternal(proxy, method, arguments);
….
}
调用invokeInternal方法:
private Object invokeInternal(Object proxy,Method method, Object[] arguments) {
String methodName = method.getName();
...
Method targetMethod =Statement.getMethod(
target.getClass(),action, argTypes);
if (targetMethod == null) {
targetMethod =Statement.getMethod(target.getClass(),
"set" +NameGenerator.capitalize(action), argTypes);
}
...
returnMethodUtil.invoke(targetMethod, target, newArgs);
}
...
}
首先获取方法名,然后利用Statement.getMethod函数构造目标方法,此时的目标方法为java.lang.ProcessBuilder.start(),然后调用MethodUtil.invoke函数:
publicstatic Object invoke(Method m, Object obj, Object[] params)
throws InvocationTargetException, IllegalAccessException {
try {
returnbounce.invoke(null, new Object[] {m, obj, params});
最终会执行ProcessBuilder(command).start(),从而导致命令执行:
总结来说,就是通过在传入spring的xml中构造sorted-set对象,并在其中包含实现了Comparable接口的Proxy类对象,对象中包含一个EventHandler的handle,而Eventhandler中则包含了一个ProcessBuilder的target和值为’start’的action。因此在解析完对象后,存入hashMap时,会调用compareTo方法,触发代理类实现的invoke方法,从而导致命令执行。
除了通过sorted-set以外,还可以通过map对象,此时map对象解析使用的是MapCoverter,可以参见S2-052从Payload到执行浅析,这篇关于struts2的漏洞分析。
5.漏洞修复
XStream 1.4.7开始对于反序列化漏洞有一些缓解措施,但是必须由开发者手动设置。可以调用addPermission,allowTypes,denyTypes等对某些类进行限制,通过这个机制可以建立白名单和黑名单机制。具体用法如下:
详细可以参考:http://x-stream.github.io/security.html
需要注意的是,在注入XStreamMarshaller的时候不要设置xstream的安全策略,而要在设置MarshallingHttpMessageConverter时获取出XStreamMarshaller,然后提取出其中的xstream对象进行安全设置。这是因为XStreamMarshaller实现了InitializingBean接口,在afterPropertiesSet中会重置一次xstream,如果在注入XStreamMarshaller的方法中设置xstream,可能会导致配置的安全策略失效。
黑名单和白名单机制有个问题,就是不好维护。此外XStream官方维护的Blacklist也存在被攻击者绕过的风险。因此为了保险起见,可以直接放弃使用XStreamMarshaller而改用spring-oxm中的其他marshaller。或者直接放弃使用MarshallingHttpMessageConverter,选择其他的converter来进行替换。
Reference
http://www.freebuf.com/vuls/147170.html
https://github.com/mbechler/marshalsec
http://www.pwntester.com/blog/2013/12/24/more-on-xstream-rce-springmvc-ws/ http://www.pwntester.com/blog/2013/12/23/rce-via-xstream-object-deserialization38/