安全简报

XML 拒绝服务攻击和防御

Bryan Sullivan

拒绝服务 (DoS) 攻击是其中最旧的类型对网站的攻击。 记录在案的 DoS 攻击作为 predates (1998年中发现) 的 SQL 注入的 1992年久远背面作为至少存在跨站点脚本 (wasn’t 发明直到 1995 JavaScript,) 和跨站点请求伪造 (CSRF 攻击通常要求会话 cookie 和 cookie weren’t 引入直到 1994年)。

将从头 DoS 攻击是非常流行与黑客社区和 ’s 轻松地了解其中的原因。 少量的技能和资源具有一个单一 “ 脚本 kiddie ” 攻击者可能会生成大量的 (对于同步) 的 TCP SYN 请求足够 knock 出服务的网站。 用于 fledgling 的电子商务世界这已毁坏:如果用户 couldn’t 获取到网站,它们很好 couldn’t 要么花费金钱存在。 doS 攻击已 erecting 刀片电线的虚拟等效项 fence 砖灰泥存储区周围,不同之处在于可以在任何时间、 天或晚上任何存储区会遭到攻击。

在年 SYN 洪水攻击有了很大程度上缓解由 Web 服务器软件和网络硬件中的改进。 但是,最近发生了内部安全社区的 DoS 攻击感兴趣的 resurgence — 不能用于 “ 旧学校 ” 网络级 DoS,但改为应用程序级 DoS 和特别 XML 分析器 DoS。

XML DoS 攻击是极不对称的:要传递攻击负载,攻击者需要花费只处理能力或受害者需要花来处理该负载的带宽的分数。 更糟糕的是仍然,在代码中处理 XML 的 DoS 漏洞也是极其广泛的。 即使您使用类似于在 Microsoft.net Framework System.Xml 类中找到的那些经过彻底测试的分析器,您的代码可以仍受到除非您采取明确步骤来保护它。

本文介绍了一些新的 XML DoS 攻击。 它还显示了为您检测潜在的 DoS 漏洞,以及如何减轻它们在代码中的方法。

XML 炸弹

一种特别麻烦的 XML DoS 攻击类型是 XML 炸弹 — XML 是格式良好和有效的 XML 架构规则根据到但其中崩溃或挂起程序,当该程序尝试来分析它的块。 XML 炸弹的著名的示例可能是指数实体扩展攻击。

内部的 XML 文档类型定义 (DTD),您可以定义您自己实质上充当字符串替换宏的实体。 渚嬪则可以将这行添加到您要替换字符串的所有匹配项的 DTD &companyname; 与 “ Contoso Inc.”:

<!ENTITY companyname "Contoso Inc.">

您还可以嵌套实体像下面这样:

<!ENTITY companyname "Contoso Inc.">
<!ENTITY divisionname "&companyname; Web Products Division">

熟悉使用外部 DTD 文件大多数开发人员时 ’s 还可能包括与 XML 数据本身一起内嵌 dtd。 您只需定义直接在 DTD 在 <! DOCTYPE > 声明而不是使用 <! DOCTYPE > 引用外部 DTD 文件:

<?xml version="1.0"?>
<!DOCTYPE employees [
  <!ELEMENT employees (employee)*>
  <!ELEMENT employee (#PCDATA)>
  <!ENTITY companyname "Contoso Inc.">
  <!ENTITY divisionname "&companyname; Web Products Division">
]>
<employees>
  <employee>Glenn P, &divisionname;</employee>
  <employee>Dave L, &divisionname;</employee>
</employees>

攻击者现在可以充分利用 XML (替换实体、 嵌套的实体和内嵌 dtd) 到精心创建一个恶意的 XML 炸弹的这三个属性。 攻击者将前面的示例一样,但而不是嵌套只一个级别深,他嵌套的嵌套实体与 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;">
]>
<lolz>&lol9;</lolz>

应注意的是此 XML 是格式良好和有效,根据 DTD 的规则。 当 XML 分析器加载此文档时,它可以看到它包括一个根元素,“ lolz ”,包含文本的 “ &lol9; ”。 但是,“ &lol9; ” 是已定义的实体,将扩展到一个字符串,该字符串包含十 “ &lol8; ” 字符串。 每个 “ &lol8; ” 字符串是已定义的实体,将展开到 10 个 “ &lol7; ” 字符串,和等)。 在处理所有实体扩展之后,XML 这个小 (< 1 KB) 块将实际包含几乎 3 GB 的内存占用的一个亿 “ lol ” s!  您可以为自己使用下面的代码非常简单的块尝试 (有时称为阶层 Laughs 攻击) 这种攻击 — 只需做好终止您测试应用程序进程从任务管理器:

void processXml(string xml)
{
    System.Xml.XmlDocument document = new XmlDocument();
    document.LoadXml(xml);
}

一些更 devious 的读者可能想知道此时是否 ’s 可能创建无限 recursing 实体扩展包含两个互相引用的实体:

<?xml version="1.0"?>
<!DOCTYPE lolz [
  <!ENTITY lol1 "&lol2;">
  <!ENTITY lol2 "&lol1;">
]>
<lolz>&lol1;</lolz>

这是一个非常有效的攻击,但幸运的是 isn’t 合法的 XML 并将不分析。 但是,另一种变体的不正常工作,指数实体扩展 XML 炸弹是二次 Blowup 攻击,Amit Klein 的 Trusteer 的发现。 而不是定义多个小型的深层嵌套实体,攻击者定义了一个非常大的实体,并引用它很多时候:

<?xml version="1.0"?>
<!DOCTYPE kaboom [
  <!ENTITY a "aaaaaaaaaaaaaaaaaa...">
]>
<kaboom>&a;&a;&a;&a;&a;&a;&a;&a;&a;...</kaboom>

如果攻击者定义了实体 “ &a; ” 为 50,000 个字符长,和引用中的 50,000 的时间内根 “ kaboom ” 元素的实体他结束与 XML 炸弹攻击负载大小将扩大为 2.5 GB,当分析时稍有超过 200 KB。 此扩展纵横比不是完全一样难忘作为与指数实体扩展攻击但是采取关闭分析进程仍然足够。

另一个 Klein 已经 XML 炸弹以供搜索的是属性 Blowup 攻击。 许多旧版本 XML 分析器,包括那些在.net Framework 版本 1.0 和 1.1 版分析极低效率的二次 O (n 2 ) 中的 XML 属性运行库中。 通过创建 XML 文档具有大量的单个元素的属性 (比如 100,000 或更多),XML 分析器将处理器独占长的时间,并因此导致发生拒绝服务的情况。 但是,此漏洞已被固定在.net framework 2.0 及更高版本。

外部实体攻击

而不是为常数定义实体替换字符串,也可能是以定义它们,以便从外部 uri 中提取其值:

<!ENTITY stockprice SYSTEM    "https://www.contoso.com/currentstockprice.ashx">

虽然确切行为取决于特定的 XML 分析器实现意图此处是每次 XML 分析器遇到实体 “ &stockprice; ” 分析器将使一个 www.contoso.com/currentstockprice.ashx 的请求并替换 stockprice 实体该请求从接收到响应。 这肯定是一个很酷和有用的功能,XML 的但它还启用某些 devious 的 DoS 攻击。

滥用外部实体功能最简单的方法是将 XML 分析器发送到将永远不会返回一个资源 ; 也就是发送到一个无限等待循环。 渚嬪濡傛灉鏀诲嚮鑰呮湁服务器 adatum.com 控件,他可以设置在 http://adatum.com/dos.ashx 一般的 HTTP 处理程序文件,如下所示:

using System;
using System.Web;
using System.Threading;

public class DoS : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        Thread.Sleep(Timeout.Infinite);
    }

    public bool IsReusable { get { return false; } }
}

他可能然后精心创建一个恶意的实体,指向 http://adatum.com/dos.ashx,然后当 XML 分析器中读取 XML 文件时,分析器将挂起。 但是,这不是特别有效的攻击。 DoS 攻击点是占用资源,以使它们的应用程序的合法用户不可用。 我们前面的示例的指数实体展开和二次 Blowup XML 炸弹导致服务器使用大量的内存和 CPU 时间,但此示例不会。 所有此类攻击真正消耗是单个执行线程。 通过强制服务器占用一些资源 let’s 提高这种攻击 (从攻击者已经角度):

public void ProcessRequest(HttpContext context)
{
    context.Response.ContentType = "text/plain";
    byte[] data = new byte[1000000];
    for (int i = 0; i < data.Length; i++) { data[i] = (byte)’A’; }
    while (true)
    {
        context.Response.OutputStream.Write(data, 0, data.Length);
        context.Response.Flush();
    }
}

此代码将编写数量无限的 ‘ A ’ 字符 (一次一百万) 到响应流,并在很短的时间内 chew 向上一个巨大的内存量。 如果攻击者无法或不设置他的页为此目的而拥有 — — 也许他 doesn’t 想留下轨迹的点到他的证据 — 他可以转而指向外部实体第三方 Web 站点上一个非常大的资源。 影片或文件的下载项可以为此目的尤其有效 ; Visual Studio 2010 专业试用版下载渚嬪是 2 GB 以上。

但这种攻击的另一个聪明的变体是目标服务器自己已经 intranet 资源在指向外部实体。 发现此攻击技术的贷记到的 Intel Steve Orrin。 这一技术确实会要求攻击者可以通过在服务器具有内部的 intranet 网站可访问的知识,但如果 intranet 资源攻击可以执行它可以是特别有效因为服务器花费自己的资源 (处理器时间、 带宽和内存) 本身或其同级服务器在同一网络上的攻击。

防范 XML 炸弹

所有类型的 XML 实体攻击都防御最简单的方法是完全禁用只是您的 XML 分析对象中的内嵌 DTD 架构的使用。 这是一个简单的应用程序的攻击面缩减原则:如果您不使用某个功能,将其关闭,以便攻击者 won’t 能够滥用它。

在.net Framework 版本 3.5 和早期,DTD 分析行为由 System.Xml.XmlTextReader 和 System.Xml.XmlReaderSettings 类别中找到布尔 ProhibitDtd 属性控制。 完全设置此值设置为 true 将禁用内嵌 dtd,请执行下列操作:

XmlTextReader reader = new XmlTextReader(stream);
reader.ProhibitDtd = true;

XmlReaderSettings settings = new XmlReaderSettings();
settings.ProhibitDtd = true;
XmlReader reader = XmlReader.Create(stream, settings);

默认值 ProhibitDtd XmlReaderSettings 中如此,但 ProhibitDtd XmlTextReader 中的默认值是 false,这意味着您不必显式设置为它设置为 true 将禁用内嵌 dtd。

在.net Framework 版本 4.0 (在测试版在撰写本文时),分析行为的 DTD 已被更改。 ProhibitDtd 属性已被否决而新的 DtdProcessing 属性。 您可以将此属性设置为禁止 (默认值) 到导致运行库引发异常,如果一个 <! DOCTYPE > 元素是 XML 中存在:

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

或者,您可以将 DtdProcessing 属性设置为不会引发异常,遇到的忽略一个 <! DOCTYPE > 元素但将只是跳过它并不处理它。 最后,您可以分析来设置 DtdProcessing 如果确实需要允许并处理内嵌 dtd。

如果确实想要分析 dtd 中,您应采取一些额外步骤来保护您的代码。 第一步是限制扩展的项的大小。 请记住介绍的攻击工作通过创建展开到巨大的字符串和强制分析器占用大量内存的实体。 通过设置 XmlReaderSettings 对象的 MaxCharactersFromEntities 属性,可以大写可以创建通过实体扩展的字符数。 确定合理的最大值,并相应地设置该属性。 例如:

XmlReaderSettings settings = new XmlReaderSettings();
settings.ProhibitDtd = false;
settings.MaxCharactersFromEntities = 1024;
XmlReader reader = XmlReader.Create(stream, settings);

防范外部实体攻击

在这时我们有强化此代码,以使 XML 炸弹要少得多易受攻击的但我们还 haven’t 解决恶意外部实体所带来的危险。 如果通过更改其 XmlResolver 自定义 XmlReader 的行为,您可以提高您针对这些攻击的复原能力。 XmlResolver 对象用于解析包括外部实体的外部引用。 具有默认 XmlResolvers (实际上 XmlUrlResolvers) 预填充 XmlTextReader 实例以及从 XmlReader.Create,对的调用返回 XmlReader 实例。 XmlReader 防止同时仍然允许其通过的 XmlReaderSettings XmlResolver 属性设置为 null 解析内联实体解析外部实体。 这是攻击面减少在工作再次 ; 如果 don’t 需要功能,将其关闭:

XmlReaderSettings settings = new XmlReaderSettings();
settings.ProhibitDtd = false;
settings.MaxCharactersFromEntities = 1024;
settings.XmlResolver = null;
XmlReader reader = XmlReader.Create(stream, settings);

如果这种情况 doesn’t 适用于您 — — 如果您真正,真正需要解析外部实体 — 所有希望都不会丢失,但您是否具有垃圾回收器稍有更多工作来做。 若要使 XmlResolver 的拒绝服务攻击更具弹性,需要更改其行为三种方式。 首先,需要设置请求超时,以防止无限长的延迟的攻击。 其次,您需要限制将检索的数据量。 最后,作为深度防护措施,您需要从检索本地主机上的资源限制在 XmlResolver。 可以通过创建自定义的 XmlResolver 类来执行所有这些操作。

要修改的行为受 XmlResolver 方法 GetEntity 的控制。 创建新类 XmlSafeResolver 出自 XmlUrlResolver 和重写 GetEntity 方法,如下所示:

class XmlSafeResolver : XmlUrlResolver
{
    public override object GetEntity(Uri absoluteUri, string role, 
        Type ofObjectToReturn)
    {

    }
}

XmlUrlResolver.GetEntity 方法的默认行为类似下面的代码可以用作您的实现起始点:

public override object GetEntity(Uri absoluteUri, string role, 
    Type ofObjectToReturn)
{
    System.Net.WebRequest request = WebRequest.Create(absoluteUri);
    System.Net.WebResponse response = request.GetResponse();
    return response.GetResponseStream();
}

第一个更改是发出请求,并读取响应应用超时值。 在 System.Net.WebRequest 和 System.IO.Stream 类提供固有支持超时。 在 图 1 中,所示的代码示例中我只需 hardcode 超时值,但您可以轻松地公开公用超时属性 XmlSafeResolver 类上的如果您希望更大的可配置性。

图 1 配置超时值

private const int TIMEOUT = 10000;  // 10 seconds

public override object GetEntity(Uri absoluteUri, string role, 
   Type ofObjectToReturn)
{
    System.Net.WebRequest request = WebRequest.Create(absoluteUri);
    request.Timeout = TIMEOUT;

    System.Net.WebResponse response = request.GetResponse();
    if (response == null)
        throw new XmlException("Could not resolve external entity");

    Stream responseStream = response.GetResponseStream();
    if (responseStream == null)
        throw new XmlException("Could not resolve external entity");
    responseStream.ReadTimeout = TIMEOUT;
    return responseStream;
}

下一步是大写最大响应中检索的数据量。 有 ’s 没有 “ MaxSize ” 属性或为 Stream 类等效这样不必实现此功能。 若要执行此操作可以一次从响应流一个文本块中读取数据并将其复制到本地流缓存。 如果从响应流中读取的字节总数超过了预定义的限制 (再次硬为简单起见只编码),停止从流读取和引发异常 (请参阅 的 图 2)。

图 2 Capping 检索的数据的最大数量

private const int TIMEOUT = 10000;                   // 10 seconds
private const int BUFFER_SIZE = 1024;                // 1 KB 
private const int MAX_RESPONSE_SIZE = 1024 * 1024;   // 1 MB

public override object GetEntity(Uri absoluteUri, string role, 
   Type ofObjectToReturn)
{
    System.Net.WebRequest request = WebRequest.Create(absoluteUri);
    request.Timeout = TIMEOUT;

    System.Net.WebResponse response = request.GetResponse();
    if (response == null)
        throw new XmlException("Could not resolve external entity");

    Stream responseStream = response.GetResponseStream();
    if (responseStream == null)
        throw new XmlException("Could not resolve external entity");
    responseStream.ReadTimeout = TIMEOUT;

    MemoryStream copyStream = new MemoryStream();
    byte[] buffer = new byte[BUFFER_SIZE];
    int bytesRead = 0;
    int totalBytesRead = 0;
    do
    {
        bytesRead = responseStream.Read(buffer, 0, buffer.Length);
        totalBytesRead += bytesRead;
        if (totalBytesRead > MAX_RESPONSE_SIZE)
            throw new XmlException("Could not resolve external entity");
        copyStream.Write(buffer, 0, bytesRead);
    } while (bytesRead > 0);

    copyStream.Seek(0, SeekOrigin.Begin);
    return copyStream;
}

作为一个替代包装 Stream 类和实现中重写的 Read 方法直接检查限制 (请参阅 的 图 3)。 这是更有效的实现,因为保存为早期的示例中缓存 MemoryStream 分配额外的内存。

图 3 定义一个大小-受限 Stream 包装类

class LimitedStream : Stream
{
    private Stream stream = null;
    private int limit = 0;
    private int totalBytesRead = 0;

    public LimitedStream(Stream stream, int limit)
    {
        this.stream = stream;
        this.limit = limit;
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        int bytesRead = this.stream.Read(buffer, offset, count);
        checked { this.totalBytesRead += bytesRead; }
        if (this.totalBytesRead > this.limit)
            throw new IOException("Limit exceeded");
        return bytesRead;
    }

    ...
}

现在,只是换行从一个 LimitedStream 中 WebResponse.GetResponseStream 返回流并从 GetEntity 方法返回在 LimitedStream (请参阅 的 图 4)。

图 4 使用 LimitedStream 中 GetEntity

private const int TIMEOUT = 10000; // 10 seconds
private const int MAX_RESPONSE_SIZE = 1024 * 1024; // 1 MB

public override object GetEntity(Uri absoluteUri, string role, Type
ofObjectToReturn)
{
    System.Net.WebRequest request = WebRequest.Create(absoluteUri);
    request.Timeout = TIMEOUT;

    System.Net.WebResponse response = request.GetResponse();
    if (response == null)
        throw new XmlException("Could not resolve external entity");

    Stream responseStream = response.GetResponseStream();
    if (responseStream == null)
        throw new XmlException("Could not resolve external entity");
    responseStream.ReadTimeout = TIMEOUT;

    return new LimitedStream(responseStream, MAX_RESPONSE_SIZE);
}

最后,添加一个详细的防护中防御措施,通过阻止实体的 uri 的解析到本地主机 (请参阅 的 图 5).This 的分辨率包括开头 http://localhost、 http://127.0.0.1,和 file:// uri 的 uri。 请注意这也会阻止攻击者可以精心指向的 file://resources 的哪些内容然后及时检索并写入到 XML 文档的分析器的实体创建在其中一个非常棘手的信息泄露漏洞。

图 5 拦截本地主机实体解析

public override object GetEntity(Uri absoluteUri, string role,
    Type ofObjectToReturn)
{
    if (absoluteUri.IsLoopback)
        return null;
    ...
}

既然定义更安全 XmlResolver,您需要将它应用于 XmlReader。 显式实例化 XmlReaderSettings 对象、 将 XmlResolver 属性设置为 XmlSafeResolver,的实例,然后使用该 XmlReaderSettings 时创建 XmlReader,如下所示:

XmlReaderSettings settings = new XmlReaderSettings();
settings.XmlResolver = new XmlSafeResolver();
settings.ProhibitDtd = false;   // comment out if .NET 4.0 or later
settings.DtdProcessing = DtdProcessing.Parse;  // comment out if 
                                               // .NET 3.5 or earlier
settings.MaxCharactersFromEntities = 1024;
XmlReader reader = XmlReader.Create(stream, settings);

其他注意事项

如果一个 XmlReader 未显式提供一个对象或一个方法然后隐式地创建一个为该框架代码中,它 ’s 中的许多 System.Xml 类请注意,很重要。 隐式创建 XmlReader 这将不具有任何附加此文章中指定的防御措施,它将容易受到攻击。 本文中的第一个代码段是一个非常好的示例,此行为:

void processXml(string xml)
{
    System.Xml.XmlDocument document = new XmlDocument();
    document.LoadXml(xml);
}

此代码是完全受到本文中介绍的所有攻击。 若要提高此代码,显式使用适当的设置 (或者禁用内嵌 DTD 分析或者指定一个更安全的冲突解决程序类) 创建一个 XmlReader 和 的 图 6 所示,而不是 XmlDocument.LoadXml 或在其他 XmlDocument.Load 重载的任何使用 XmlDocument.Load(XmlReader) 重载。

图 6 应用安全分析设置以如下的实体

void processXml(string xml)
{
    MemoryStream stream =
        new MemoryStream(Encoding.Default.GetBytes(xml));
    XmlReaderSettings settings = new XmlReaderSettings();

    // allow entity parsing but do so more safely
    settings.ProhibitDtd = false;
    settings.MaxCharactersFromEntities = 1024;
    settings.XmlResolver = new XmlSafeResolver();

    XmlReader reader = XmlReader.Create(stream, settings);
    XmlDocument doc = new XmlDocument();
    doc.Load(reader);
}

XLinq 是其默认设置在某种程度上更安全 ; 默认情况下,创建用于 System.Xml.Linq.XDocument XmlReader 确实允许 DTD 在分析但它会自动将 MaxCharactersFromEntities 设置为 10,000,000 和禁止外部实体分辨率。 如果要 XDocument 显式提供一个 XmlReader,请务必在应用防御前面所述的设置。

总结

XML 实体扩展是一个功能强大的功能,但拒绝您的应用程序的服务的攻击者可以轻松地被滥用。 一定要遵循的攻击面缩减原则,并禁用实体扩展,如果您 don’t 需要其使用。 否则,应用适当的防御措施以限制在其上最大的时间和您的应用程序可以花费的内存量。

Bryan Sullivan 是一个安全计划经理为 Microsoft 安全开发生命周期团队 specializing 中的 Web 应用程序和.net 安全问题。 他是 “ AJAXSecurity ” (Addison-Wesley,2007年) 的作者。