可扩展标记语言(XML)是用于定义数据的标准标记语言。XML 也是 ASP.NET Core web 应用可以用来解析信息的格式。为了实现这一点,开发人员可以使用框架中随时可用的任意数量的.NET XML 解析器。
作为输入源的 XML 很可能容易被恶意数据注入。名为XML 外部实体(XXE的功能允许 XML 使用 URL 或文件路径定义自定义实体。这种用 XML 表示外部实体的能力可能被滥用或利用。不受限制的外部实体引用可使攻击者将敏感信息和文件发送到应用的受信任域之外,并发送到行为人控制的服务器。此漏洞的存在可能导致拒绝服务(DoS攻击,使整个应用无法访问,因为请求泛滥,或文件包含攻击,对手可以未经授权访问文件。
为了确保代码的安全性并防止这些类型的基于 XML 的注入攻击,必须使用XML 模式(XSD验证 XML。XSD 的使用确保了 XML 与所需格式的一致性。此外,必须仔细配置和设置要使用的.NET 解析器,以避免 XML 解析器出现任何意外行为。
在本章中,我们将介绍以下配方:
XmlDocument
固定 XXE 注入液XmlTextReader
固定 XXE 注入液这些方法将教会我们如何向 ASP.NET Core web 应用添加 XML 模式验证,以及如何修复代码中允许注入和处理外部 XML 实体的各种漏洞。
技术要求这本书是为配合 VisualStudio 代码、Git 和.NET5.0 而编写和设计的。ASP.NET Core Razor 页面中提供了配方中的代码示例。示例解决方案还使用 SQLite 作为数据库引擎,以简化设置。本章的完整代码示例可在上找到 https://github.com/PacktPublishing/ASP.NET-Core-Secure-Coding-Cookbook/tree/main/Chapter05 。
启用 XML 验证一个XSD指定了 XML 的组成方式。模式有助于定义 XML 结构,并防止不需要的元素、属性和文本。如果没有 XSD,.NET 解析器将盲目处理 XML 数据,并增加代码中 XXE 注入漏洞的风险。
本食谱将教您如何创建使用 XSD 和验证 XML 数据的方法。
为了完成本章中的菜谱,我们需要一个示例网上银行应用。
打开命令 shell 并通过克隆 ASP.NET Secure Codeing Cookbook 存储库下载示例网上银行应用,如下所示:
git clone https://github.com/PacktPublishing/ASP.NET-Core-Secure-Coding-Cookbook.git
运行示例应用以验证没有生成或编译错误。在命令 shell 中,导航到位于\Chapter05\missing-validation\before\OnlineBankingApp
的示例应用文件夹,并运行以下命令:
dotnet build
dotnet build
命令将构建示例OnlineBankingApp
项目及其依赖项。
让我们来看看这个食谱的步骤:
在“开始练习”文件夹中,通过键入以下命令来启动 Visual Studio 代码:
cs
code .
Under the wwwroot
directory, right-click and select New File. Name the file Knowledgebase.xsd
:
图 5.1–添加新文件
在Knowledgebase.xsd
文件中添加以下标记(该xsd
文件的全部内容可在\Chapter05\missing-validation\after\OnlineBankingApp
下的已完成练习文件夹中找到):
cs
// markup removed for brevity
既然我们已经创建了模式,我们将使用这个xsd
文件来验证Knowledgebase.xml
的格式,它包含我们知识库的数据。
打开\Services\KnowledgebaseServices.cs
并添加对以下命名空间的引用:
cs
using System.Xml.Schema;
using System.IO;
这些名称空间允许您使用验证 XML 模式所需的类,并为 XML 文件上的文件输入输出操作提供一些方法。
我们需要重构Search
方法中的代码,以便它可以使用我们在步骤 3中创建的模式。在声明webroot
变量的位置之后和声明file
的位置之前插入以下代码:
cs
var webRoot = _env.WebRootPath;
var schemaSet = new XmlSchemaSet();
var xsdFile = System.IO.Path.Combine(webRoot, "Knowledgebase.xsd");
using (System.IO.FileStream stream = File.OpenRead(xsdFile))
{
schemaSet.Add(XmlSchema.Read(stream, (s, e) =>
{
var x = e.Message;
}));
}
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidatiOnType= ValidationType.Schema;
settings.Schemas = schemaSet;
settings.DtdProcessing = DtdProcessing.Ignore;
var file = System.IO.Path.Combine(webRoot, "Knowledgebase.xml");
声明一个XmlReader
对象,该对象将以file
和settings
作为前面代码行中的参数:
cs
XmlReader reader = XmlReader.Create(file, settings);
Modify this line of code so that the XmlDocument
instance will load the reader instead of the file:
cs
xmlDoc.Load(reader);
XmlDocument
对象现在加载XmlReader
,其中包括模式验证。
knowledgebase.xsd
文件包含knowledgebase.xml
中允许的有效元素和属性。这个 XSD 还描述了元素的预期数据类型。例如,
期望sensitivity
元素为string
数据类型。knowledgebase.xml
文件应遵循此格式。否则,验证过程将失败。
我们在\Services\KnowledgebaseServices.cs
中添加了对System.Xml.Schema
名称空间的引用,该名称空间包含我们需要验证的类。从这个引用中,我们使用了XmlSchemaSet
类来存储knowledgebase.xsd
模式。XmlSchemaSet
类可以包含多个模式,但我们的配方将只使用一个模式。
然后,我们用File.OpenRead(xsdFile)
方法创建了一个FileStream
对象。xsdFile
对象表示knowledgebase.xsd
并作为参数传递给File.OpenRead
。然后将FileStream
对象提供给XmlSchema.Read
方法,并将knowledgebase.xsd
添加到schemaSet
集合中。
XmlReaderSettings
属性的ValidationType
被赋值为ValidationType.Schema
,该值设置新的XmlReader
以使用 XSD 模式执行验证。
通过XmlReader.Create
方法,我们使用前面几行中定义的XmlReaderSettings
属性创建了XmlReader
的reader
实例。最后,我们将 reader 对象传递给XmlDocument
实例,让应用开始解析knowledgebase.xml
文件。
笔记
与 XML 类似,XSD 也可以具有外部引用,并且可以来自不受信任的资源。需要进行额外的验证,以确保源是可信的,并且您没有使用来自恶意 XSD 的恶意字符串。
XML 的另一种形式是可扩展样式表语言转换,或者简称为的XSLT。该语言用于将 XML 文档转换为其他文档格式,如纯文本文件或 HTML。
您可以使用System.Xml.Xsl
命名空间中的XslCompiledTransform
类将 XSLT 加载到 ASP.NET web 应用中。调用Load
和Transform
方法加载 XSLT 并将其转换为特定格式:
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load("Knowledgebase.xsl");
xslt.Transform("Knowledgebase.xml", "Knowledgebase.html");
然而,与任何形式的输入一样,XSLT 也可能被污染或破坏以加载恶意的 XXE。要安全地编译 XSLT,请传递一个null``XmlResolver
来停止解析外部资源,并将 XmlReaderSetting 的DtdProcessing
设置为Ignore
。
为了防止 XSLT 嵌入脚本块和使用危险的document()
函数,我们将 XSLT 设置的EnableScript
和EnableDocumentFunction
设置为false
:
XslCompiledTransform xslt = new XslCompiledTransform();
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Ignore;
var file = System.IO.Path.Combine(webRoot, "Knowledgebase.xsl");
XmlReader reader = XmlReader.Create(file, settings);
XsltSettings xsltSettings = new XsltSettings();
xsltSettings.EnableDocumentFunction = false;
xsltSettings.EnableScript = false;
xslt.Load(reader, xsltSettings, null);
笔记
默认情况下,EnableDocumentFunction
和EnableScript
属性被禁用并设置为false
。
在将这些对象传递到重载的Load
方法之前,使用正确的设置配置这些对象有助于防止 XSLT 注入和 XXE 攻击。
XmlDocument
类实际上是.NET 应用的 XML 解析器。这个 XML 解析器对象通常用于加载、修改和删除内存中的 XML。它有一个XmlResolver
属性,允许使用外部 XML 资源,如 DTD。
文档类型定义,最常见的称为DTD,类似于 XML 文件,但包含 XML 的组成或结构信息。它可以有一个ENTITY
元素,可以是内部的,也可以是外部的。当XDocument
使用 DTD 解析 XML 文件时,该 XML 解析器将处理该文件及其ENTITY
声明。
让我们看一下带有恶意注入的ENTITY
声明的 XML 文件的某些内容。这是一个已知的十亿笑声攻击的经典例子,这是一种拒绝服务(DoS)攻击,目标是XmlDocument
等 XML 解析器。加载此 XML 将导致 ASP.NET Core web 应用崩溃或无响应:
]>
此外,意外地设置您的不安全自定义XmlResolver
可能允许将来自不受信任源或主机的 DTD 包含在knowledgebase.xml
解析中,可能导致XXE 注入。
这个方法将向您展示如何禁用 DTD,从而使您的代码在解析 XML 时更加安全。
使用 Visual Studio 代码,打开位于\Chapter05\xxe-injection01\before\OnlineBankingApp\
的示例网上银行应用文件夹。
让我们来看看这个食谱的步骤:
在起始练习文件夹中,通过键入以下命令启动 Visual Studio 代码:
cs
code .
打开Services\KnowledgebaseService.cs
文件,注意分配给XmlUrlResolver
对象的行:
cs
XmlUrlResolver resolver = new XmlUrlResolver();
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.XmlResolver = resolver;
xmlDoc.Load(file);
Assign the XmlResolver
property of xmlDoc
to null
:
cs
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.XmlResolver = null;
xmlDoc.Load(file);
将XmlResolver
设置为null
将禁止 DTD 加载到knowledgebase.xml
文件中。
笔记
当 XML 文档中存在外部实体或DTD时,当应用尝试解析XML时会抛出XMLException
。
在我们的OnlineBankApp
示例应用中,XMLResolver
被实例化并分配给 xmlDoc 的XmlResolver
属性。为XmlResolver
属性设置值表示允许加载 DTD 或解析外部实体引用。
然而,为了缓解代码中的这个漏洞,我们必须取消XmlResolver
引用,从而防止恶意 DTD 被加载。
如果需要解析 ASP.NET Core web 应用中的 DTD,则必须使用XmlSecureResolver
类并将其实例分配给XmlDocument
的XmlResolver
属性。XmlSecureResolver
是XmlResolver
类的安全实现,限制对资源的访问。
要使用此类实现安全的 DTD 解析,只需将 URL 作为XmlSecureResol
ver
构造函数的第二个参数传递,即可定义允许的资源。明确声明了h
ttps://localhost:5001
URL,表示我们只允许来自此 URL 的资源:
XmlSecureResolver resolver = new XmlSecureResolver(new XmlUrlResolver(), "https://localhost:5001");
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.XmlResolver = resolver;
xmlDoc.Load(file);
XmlSecureResolver
对象用PermissionSet
包装XmlResolver
对象,指定允许的访问。在内部,它调用PermitOnly
方法,正如其名称所示,该方法设置了不会导致调用代码失败的权限。
与XmlDocument
类似,另一个快速、非缓存、仅转发到 XML 的解析器选项是XmlTextReader
。这种高性能解析器的一个主要缺点是缺乏数据验证。XmlTextReader
还允许您在默认情况下处理 DTD,如果您的 XML 源不可信,这可能会引起关注。
此配方将向您展示如何使用XmlTextReader.
禁用 DTD 处理
使用 Visual Studio 代码,打开位于\Chapter05\xxe-injection02\before\OnlineBankingApp\
的示例网上银行应用文件夹。
让我们来看看这个食谱的步骤:
在起始练习文件夹中,通过键入以下命令启动 Visual Studio 代码:
cs
code .
Open the Services\KnowledgebaseService.cs
file. This version of the OnlineBankingApp
sample solution is using XmlTextReader
to parse the Knowledgebase.xml
file:
cs
XmlTextReader xmlReader = new XmlTextReader(file);
xmlReader.DtdProcessing = DtdProcessing.Parse;
XPathDocument xmlDoc = new XPathDocument(xmlReader);
将xmlReader
的DtdProcessing
属性赋值给DtdProcessing.Ignore
的enum
值:
cs
XmlTextReader xmlReader = new XmlTextReader(file);
xmlReader.DtdProcessing = DtdProcessing.Ignore;
XPathDocument xmlDoc = new XPathDocument(xmlReader);
将DtdProcessing
属性设置为DtdProcessing.Ignore
将阻止处理 DTD 并忽略标记。DOCTYPE
是一种标记形式,用于通知浏览器文档使用的 HTML 版本。
遵循以下步骤:
DtdProcessing
属性设置为enum
值DtdProcessing.Parse
时,这表示将处理 DTD。如果一个坏角色将恶意实体引用节点注入到knowledgebase.xml
文件中,那么允许处理 DTD 可能是危险的。DtdProcessing.Ignore
的DtdProcessing
属性使处理 DTD 变得不可能,从而使代码更加安全。语言集成查询或LINQ是.NET 框架内的一个 API,为编写声明性代码提供类似查询的语法。LINQ 有不同的风格,LINQ 到 XML 就是其中之一。LINQ to XML 是一个内存中的 XML 解析器,允许您执行 XML 转换——从修改元素和节点到序列化。
一般来说,LINQ 到 XML 不受 XXE 注入的影响。XDocument
类默认已禁用 DTD 处理。但是,当使用不安全的 XML 解析器(如XmlReader
)实例化时,这可能是不安全的。此配方将向您展示如何在 LINQ to XML 代码中发现安全漏洞,并通过禁用 DTD 处理来修复该漏洞。
使用 Visual Studio 代码,打开位于\Chapter05\xxe-injection03\before\OnlineBankingApp\
的示例网上银行应用文件夹。
您可以在此文件夹中执行使用 LINQ to XML 修复 XXE 注入的步骤。
让我们来看看这个食谱的步骤:
在起始练习文件夹中,通过键入以下命令启动 Visual Studio 代码:
cs
code .
Open the Services\KnowledgebaseService.cs
file. This version of the OnlineBankingApp
sample solution is using Linq to XML
to parse the Knowledgebase.xml
file.
注意XDocument
类的使用和解析 XML 的类似查询的方法:
cs
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Parse;
settings.MaxCharactersFromEntities = 1024;
settings.MaxCharactersInDocument = 2048;
XmlReader reader = XmlReader.Create(file, settings);
XDocument xmlDoc = XDocument.Load(reader);
var query = from i in xmlDoc.Element("knowledgebase")
.Elements("knowledge")
where
(i.Element("topic").ToString() .Contains(input) == true ||
i.Element("description").ToString() .Contains(input) == true) &&
i.Element("sensitivity").ToString() .Contains("Public") == true
select new
{
Topic = (string)i.Element("topic"),
Description =
(string)i.Element("description")
};
XmlReader
类的Create
方法被一个易受攻击的XmlReaderSettings
调用。正如我们在前面的配方中看到的,我们必须禁用 DTD 处理。这可以通过将DtdProcessing
设置为DtdProcessing.Prohibit
:
cs
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit;
settings.MaxCharactersFromEntities = 1024;
settings.MaxCharactersInDocument = 2048;
的枚举值来实现
4. 将XmlReaderSettings
对象的DtdProcessing
属性从DtdProcessing.Parse
更改为DtdProcessing.Prohibit
将阻止 DTD 处理,并且在 XML 中存在 DTD 时也会抛出XmlException
。
5. 现在,我们必须将XmlReaderSettings
实例的MaxCharactersFromEntities
赋值为1024
:
cs
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit;
settings.MaxCharactersFromEntities = 1024;
settings.MaxCharactersInDocument = 2048;
分配MaxCharactersFromEntities
将限制扩展实体的大小并防止滥用。
We must also explicitly assign MaxCharactersInDocument
of the XmlReaderSettings
object with a value of 2048
:
cs
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit;
settings.MaxCharactersFromEntities = 1024;
settings.MaxCharactersInDocument = 2048;
将MaxCharactersInDocument
属性的值指定为2048
表示 XML 文件中允许的最大字符数为2048
。同样,这有助于防止攻击者可能滥用。
笔记
添加一个try-catch
语句来处理XmlException
的可能性。始终在代码中练习安全的错误处理。本书的最后一章将详细介绍这一最佳实践。
在KnowledgebaseService
类中,我们有一个Search
方法对整个Knowledgebase.xml
文件执行搜索,其中包含帮助查询数据。当用户点击网页上的搜索时,它会调用此方法并创建XmlReader
和XDocument
类的实例。
XDocument
用XmlReader
实例化。XmlReader
的属性基于知识库 XML 数据及其XmlReaderSettings
。虽然这是用 XML 数据填充解析器的有效方法,XmlReader
的XmlReaderSettings
将DtdProcessing
属性设置为DtdProcessing.Parse
,从而使用不安全的解析器设置XDocument
对象。此设置导致代码易受 XXE 注入攻击。
为了解决这个问题,我们必须选择一个更好的财产价值——要么是DtdProcessing.Ignore
要么是DtdProcessing.Prohibit
——并将其分配给XmlReaderSettings
的DtdProcessing
。这两个值都可以防止危险的 DTD 处理。我们在前面的配方中涵盖了DtdProcessing.Ignore
属性值,因此我们选择了DtdProcessing.Prohibit
。
要管理通过 XML 解析器发生的 DTD,请在DtdProcessing.Ignore
上选择DtdProcessing.Prohibit
属性值。拥有DtdProcessing.Prohibit
会引发XmlExceptions
,您可以使用 try-catch 块来处理,而DtdProcessing.Ignore
则完全忽略 DTD。
假设攻击者能够输入包含大量数据的任意 XML 文件。这可能会导致系统消耗大量计算资源,从而导致 DOS 攻击。我们可以通过为XmlReaderSettings
的MaxCharactersFromEntities
和MaxCharactersInDocument
属性分配一个合理的值来防止这种情况发生。这些属性限制了 XML 及其元素和属性的扩展大小。