XML文档中禁止使用DTD的异常情况

58

在使用C#应用程序解析XML文档时,我遇到了以下错误:

"由于安全原因,此XML文档中禁止使用DTD。要启用DTD处理,请将XmlReaderSettings上的ProhibitDtd属性设置为false,并将设置传递给XmlReader.Create方法。"

需要参考的是,在以下代码的第二行发生了异常:

using (XmlReader reader = XmlReader.Create(uri))
{
    reader.MoveToContent(); //here

    while (reader.Read()) //(code to parse xml doc follows).

我对XML的了解非常有限,也不知道DTD处理是什么,也不知道如何做出错误信息所建议的操作。请问有什么帮助可以找出导致这个问题的原因并解决它的方法吗?谢谢...


2
您是否按照错误信息中列出的步骤进行了操作?如果没有,那么为什么这些步骤对您无效呢? - user7116
我有点惊讶你想要解析一个XML文档却不知道DTD是什么。那你如何检测输入中的任何错误呢? - U. Windl
4个回答

88

首先,提供一些背景信息。

什么是DTD?

您正在尝试解析的文档包含一个文档类型声明;如果您查看文档,您将在开头附近找到以<!DOCTYPE开头以相应的>结尾的字符序列。这样的声明允许XML处理器根据一组声明验证文档,这些声明指定了一组元素和属性,并限制了它们可以具有的值或内容。

由于实体也在DTD中声明,因此DTD允许处理器知道如何扩展对实体的引用。(实体pubdate可能被定义为包含文档的发布日期,例如“2012年12月15日”,并在文档中多次引用为&pubdate; - 由于实际日期仅在实体声明中给出,这种使用使得更容易使文档中与发布日期相关的各个引用保持一致。)

DTD的意义是什么?

文档类型声明具有纯声明性含义:此文档类型的模式(以XML规范中定义的语法)可以在某个位置找到。

一些由XML基础知识薄弱的人编写的软件存在一个关于声明的基本混淆;它假设文档类型声明的含义不是声明性的(模式在那里),而是命令性的(请验证此文档)。您正在使用的解析器似乎就是这样的解析器;它假定通过向它提供具有文档类型声明的XML文档,您已经请求了某种处理方式。它的作者可能会受益于关于如何接受用户运行时参数的补习课程。(您可以看到对于一些人来说理解声明性语义是多么困难:甚至一些XML解析器的创建者有时也无法理解它们并滑入命令性思维中。唉。)

他们所说的“安全原因”是什么?

一些注重安全的人认为DTD处理(验证或无验证实体扩展)构成安全风险。使用实体扩展,很容易制作一个非常小的XML数据流,当所有实体完全扩展时,它就会扩展成一个非常大的文档。如果想了解更多信息,请搜索所谓的“十亿笑攻击”。
保护免受十亿笑攻击的一个明显方法是,对于在用户提供的或不受信任的数据上调用解析器的人,以限制解析过程被允许消耗的内存或时间的环境中调用解析器。这样的资源限制自20世纪60年代中期以来一直是操作系统的标准部分。然而,出于我尚不清楚的原因,一些注重安全的人认为正确的答案是在未经验证的输入上运行解析器而没有资源限制,并坚信只要使其无法根据约定模式验证输入即可安全。
这就是为什么您的系统告诉您的数据存在安全问题的原因。
对于一些人来说,DTD是安全风险的想法听起来更像是偏执狂而不是良好的判断力,但我不认为他们是正确的。请记住(a)健康的偏执狂是安全专家在生活中所需要的,(b)任何真正关心安全的人都会坚持使用资源限制——在解析过程中存在资源限制时,DTD是无害的。禁止DTD不是偏执狂而是迷信。
现在,有了这个背景,如何解决这个问题呢?
最好的解决方案是向您的供应商抱怨他们被一个关于XML安全性的老妇人谎言欺骗,告诉他们如果他们关心安全性,他们应该进行理性的安全分析,而不是禁止DTD。
同时,就像消息所建议的那样,“将XmlReaderSettings上的ProhibitDtd属性设置为false,并将这些设置传递给XmlReader.Create方法。”如果输入确实不可信,则还可以探索给进程提供适当资源限制的方法。

作为一种备选方案(我不推荐这样做),您可以在输入中注释掉文档类型声明。


2
所以简而言之,只需按照错误消息所说的做即可?我怀疑OP与MS打开连接条目不会很快解决DTD处理方面的问题,无论是他们的问题还是你的问题。 - user7116
1
谢谢提供的信息,帮助我理解了问题出现的原因。至于如何解决它,只需要添加两行额外的代码就可以了。 - ConnorU
2
尽管资源耗尽攻击是一个问题,但现在出现了更重要的问题——外部实体处理攻击在此处记录。有效地,它可以允许攻击者从您的服务器或网络中读取文件。默认设置仍然可能是正确的选择! - pattermeister
谢谢。这是另一个有趣的安全分析案例,但我不理解其中的意义。所涉及的外部实体位于本地系统上。解析器正在本地系统上运行。浏览器中使用的同源策略足以防止访问它们,在任何情况下,您指向的文章中没有解释攻击者如何访问信息。假设似乎假定服务器的解析器将解析后的数据返回给XML文档源,而不是返回给在服务器上运行的某个应用程序;这并非必然如此。 - C. M. Sperberg-McQueen
2
System.Xml.XmlReaderSettings.ProhibitDtd已过时:请改用XmlReaderSettings.DtdProcessing属性。请参见AaronD的答案。 - Nicolas Raoul
作为专注于应用安全的渗透测试人员,XML实体注入是一种被严重低估的威胁。我在许多情况下使用它来访问服务器上的文件以获取密码等信息。我会始终禁用DTD解析。如果有启用它的原因,我会覆盖解析器并确保任何外部引用都被列入白名单。 - Casey

52

请注意,settings.ProhibitDtd现已过时,请改用DtdProcessing:(新的选项为Ignore、Parse或Prohibit)

XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Parse;

根据这篇文章所述:《亿次笑攻击》是如何工作的?

为了避免DoS攻击,您应该添加字符数量限制:

XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Parse;
settings.MaxCharactersFromEntities = 1024;

29

就修复这个问题而言,经过一些搜索,我发现只需要添加以下内容即可:

XmlReaderSettings settings = new XmlReaderSettings();
settings.ProhibitDtd = false;

并将这些设置传递给create方法。

[更新于2017年3月9日]

如一些人所指出的,.ProhibitDTDT现在已经被弃用。 Dr. Aaron Dishno的回答显示了替代方案。


29
截至最新的(.Net framework 4.5.1)版本,.ProhibitDtd已经过时,应使用settings.DtdProcessing = DtdProcessing.Ignore代替。 - Karl Cassar
请问您能否更新一下您的答案?因为“ProhibitDtd”现在已经过时了。 - NoWar
1
@Dimi,以下是Aaron Dishno博士的回答,解释了这个问题并提供了目前最好的解决方法以及一些很好的建议。我不想通过将他的答案包含在我的答案中来获得荣誉或声誉。 - ConnorU

-3

在尝试了上述所有答案均未成功后,我将服务用户从 service@mydomain.com 更改为 service@mydomain.onmicrosoft.com,现在应用程序在 Azure 中运行时可以正常工作。

或者,如果您在一个您有更多控制权的环境中遇到此问题,您可以将以下内容粘贴到 hosts 文件中:

127.0.0.1 msoid.onmicrosoft.com
127.0.0.1 msoid.mydomain.com
127.0.0.1 msoid.mydomain.onmicrosoft.com
127.0.0.1 msoid.*.onmicrosoft.com

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接