DataSet 和 DataTable 安全指南

本文适用于:

  • .NET Framework(所有版本)
  • .NET Core 和更高版本
  • .NET 5 及更高版本

DataSetDataTable 类型是允许将数据集表示为托管对象的传统 .NET 组件。 这些组件是作为原始 ADO.NET 基础结构的一部分在 .NET Framework 1.0 中引入的。 其目的是基于关系数据集提供一个托管视图,并抽象掉数据的基础来源是 XML、SQL 还是其他技术的问题。

有关 ADO.NET 的详细信息(包括更现代的数据视图范式),请参阅 ADO.NET 文档

从 XML 反序列化 DataSet 或 DataTable 时的默认限制

在所有受支持版本的 .NET Framework、.NET Core 和 .NET 上,DataSetDataTable 对反序列化数据中可存在的对象类型施加了以下限制。 默认情况下,此列表仅限于:

  • 基元和基元等效项:boolcharsbytebyteshortushortintuintlongulongfloatdoubledecimalDateTimeDateTimeOffsetTimeSpanstringGuidSqlBinarySqlBooleanSqlByteSqlBytesSqlCharsSqlDateTimeSqlDecimalSqlDoubleSqlGuidSqlInt16SqlInt32SqlInt64SqlMoneySqlSingleSqlString
  • 常用非基元类型:TypeUriBigInteger
  • 常用 System.Drawing 类型:ColorPointPointFRectangleRectangleFSizeSizeF
  • Enum 类型。
  • 上述类型的数组和列表。

如果传入的 XML 数据包含类型未在此列表中列出的对象:

  • 引发异常并显示以下消息和堆栈跟踪。 错误消息:System.InvalidOperationException: 此处不允许类型“<类型名称>,版本=<n.n.n.n>,区域性=<区域性>,PublicKeyToken=<令牌值>”。 堆栈跟踪:at System.Data.TypeLimiter.EnsureTypeIsAllowed(Type type, TypeLimiter capturedLimiter) at System.Data.DataColumn.UpdateColumnType(Type type, StorageType typeCode) at System.Data.DataColumn.set_DataType(Type value)

  • 反序列化操作失败。

将 XML 加载到现有的 DataSetDataTable 实例时,还会将现有的列定义纳入考虑。 如果表已包含自定义类型的列定义,则该类型将在 XML 反序列化操作暂时添加到允许列表中。

备注

将列添加到 DataTable 后,ReadXml 将不会从 XML 中读取架构;如果架构不匹配,它也不会读入记录,因此你需要自行添加所有列才能使用此方法。

XmlReader xmlReader = GetXmlReader();

// Assume the XML blob contains data for type MyCustomClass.
// The following call to ReadXml fails because MyCustomClass isn't in the allowed types list.

DataTable table = new DataTable("MyDataTable");
table.ReadXml(xmlReader);

// However, the following call to ReadXml succeeds, since the DataTable instance
// already defines a column of type MyCustomClass.

DataTable table = new DataTable("MyDataTable");
table.Columns.Add("MyColumn", typeof(MyCustomClass));
table.ReadXml(xmlReader); // this call will succeed

使用 XmlSerializer 反序列化 DataSetDataTable 的实例时,对象类型限制也适用。 但是,在使用 BinaryFormatter 反序列化 DataSetDataTable 的实例时,这些限制可能不适用。

使用 DataAdapter.Fill 时,对象类型限制不适用,例如,在不使用 XML 反序列化 API 的情况下直接从数据库填充 DataTable 实例时。

扩展允许的类型列表

除了上面列出的内置类型以外,应用还可以扩展允许的类型列表以包含自定义类型。 如果扩展允许的类型列表,所做的更改将影响应用中所有的 DataSetDataTable 实例。 无法从内置的允许类型列表中删除类型。

通过配置进行扩展(.NET Framework 4.0 及更高版本)

App.config 可用于扩展允许的类型列表。 若要扩展允许的类型列表,请执行以下操作:

  • 使用 <configSections> 元素添加对 System.Data 配置节的引用。
  • 使用 <system.data.dataset.serialization>/<allowedTypes> 指定其他类型。

每个 <add> 元素只能使用类型的程序集限定类型名称指定一个类型。 若要将其他类型添加到允许的类型列表,请使用多个 <add> 元素。

以下示例演示如何通过添加自定义类型 Fabrikam.CustomType 来扩展允许的类型列表。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="system.data.dataset.serialization" type="System.Data.SerializationSettingsSectionGroup, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <section name="allowedTypes" type="System.Data.AllowedTypesSectionHandler, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
    </sectionGroup>
  </configSections>
  <system.data.dataset.serialization>
    <allowedTypes>
      <!-- <add type="assembly qualified type name" /> -->
      <add type="Fabrikam.CustomType, Fabrikam, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2b3831f2f2b744f7" />
      <!-- additional <add /> elements as needed -->
    </allowedTypes>
  </system.data.dataset.serialization>
</configuration>

若要检索类型的程序集限定名称,请使用 Type.AssemblyQualifiedName 属性,如以下代码所示。

string assemblyQualifiedName = typeof(Fabrikam.CustomType).AssemblyQualifiedName;

通过配置进行扩展 (.NET Framework 2.0 - 3.5)

如果你的应用面向 .NET Framework 2.0 或 3.5,你仍可以使用上述 App.config 机制来扩展允许的类型列表。 但是,<configSections> 元素看起来略有不同,如以下代码所示:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <!-- The below <sectionGroup> and <section> are specific to .NET Framework 2.0 and 3.5. -->
    <sectionGroup name="system.data.dataset.serialization" type="System.Data.SerializationSettingsSectionGroup, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <section name="allowedTypes" type="System.Data.AllowedTypesSectionHandler, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
    </sectionGroup>
  </configSections>
  <system.data.dataset.serialization>
    <allowedTypes>
      <!-- <add /> elements, as demonstrated in the .NET Framework 4.0 - 4.8 sample code above. -->
    </allowedTypes>
  </system.data.dataset.serialization>
</configuration>

以编程方式进行扩展(.NET Framework、.NET Core、.NET 5+)

还可以结合已知键 System.Data.DataSetDefaultAllowedTypes 使用 AppDomain.SetData 以编程方式扩展允许的类型列表,如以下代码所示。

Type[] extraAllowedTypes = new Type[]
{
    typeof(Fabrikam.CustomType),
    typeof(Contoso.AdditionalCustomType)
};

AppDomain.CurrentDomain.SetData("System.Data.DataSetDefaultAllowedTypes", extraAllowedTypes);

如果使用扩展机制,与键 System.Data.DataSetDefaultAllowedTypes 关联的值必须是 Type[] 类型。

在 .NET Framework 中,可以使用 App.config 和 AppDomain.SetData 来扩展允许的类型列表。 在这种情况下,如果某个对象的类型出现在任一列表中,则 DataSetDataTable 允许将该对象反序列化为数据的一部分。

在审核模式下运行应用 (.NET Framework)

在 .NET Framework 中,DataSetDataTable 提供审核模式功能。 启用审核模式后,DataSetDataTable 会将传入对象的类型与允许的类型列表进行比较。 但是,如果出现了类型不被允许的对象,将不会引发异常。 在这种情况下,DataSetDataTable 将让任何附加的 TraceListener 实例知道出现了可疑类型,并允许 TraceListener 记录此信息。 不会引发异常,并且反序列化操作将会继续。

警告

在“审核模式”下运行应用应该只是一种在测试时使用的临时手段。 启用审核模式后,DataSetDataTable 不会强制实施类型限制,这可能会在应用中造成安全漏洞。 有关详细信息,请参阅标题为消除所有类型限制有关不受信任输入的安全性的部分。

可通过 App.config 启用审核模式:

  • 有关为 <configSections> 元素设置正确值的信息,请参阅本文档中的通过配置进行扩展部分。
  • 如以下标记中所示,使用 <allowedTypes auditOnly="true"> 启用审核模式。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <!-- See the section of this document titled "Extending through configuration" for the appropriate
         <sectionGroup> and <section> elements to put here, depending on whether you're running .NET
         Framework 2.0 - 3.5 or 4.0 - 4.8. -->
  </configSections>
  <system.data.dataset.serialization>
    <allowedTypes auditOnly="true"> <!-- setting auditOnly="true" enables audit mode -->
      <!-- Optional <add /> elements as needed. -->
    </allowedTypes>
  </system.data.dataset.serialization>
</configuration>

启用审核模式后,可以使用 App.config 将首选的 TraceListener 连接到 DataSet 的内置 TraceSource.。内置跟踪源的名称为 System .Data.DataSet。 以下示例演示如何将跟踪事件写入控制台以及磁盘上的日志文件。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.diagnostics>
    <sources>
      <source name="System.Data.DataSet"
              switchType="System.Diagnostics.SourceSwitch"
              switchValue="Warning">
        <listeners>
          <!-- write to the console -->
          <add name="console"
               type="System.Diagnostics.ConsoleTraceListener" />
          <!-- *and* write to a log file on disk -->
          <add name="filelog"
               type="System.Diagnostics.TextWriterTraceListener"
               initializeData="c:\logs\mylog.txt" />
        </listeners>
      </source>
    </sources>
  </system.diagnostics>
</configuration>

有关 TraceSourceTraceListener 的详细信息,请参阅文档如何:将 TraceSource 和筛选器与跟踪侦听器配合使用

备注

在 .NET Core 或 .NET 5 和更高版本中,无法在审核模式下运行应用。

消除所有类型限制

如果应用必须从 DataSetDataTable 中消除所有类型限制:

  • 可通过多个选项来消除类型限制。
  • 可用选项取决于应用所面向的框架。

警告

消除所有类型限制可能会在应用中造成安全漏洞。 使用此机制时,请确保应用不使用 DataSetDataTable 来读取不受信任的输入。 有关详细信息,请参阅 CVE-2020-1147 以及标题为有关不受信任输入的安全性的后续部分。

通过 AppContext 配置(.NET Framework 4.6 及更高版本、.NET Core 2.1 及更高版本、.NET 5 及更高版本)

AppContext 开关 Switch.System.Data.AllowArbitraryDataSetTypeInstantiation 设置为 true 时,会从 DataSetDataTable 中消除所有类型限制。

在 .NET Framework 中,可以通过 App.config 启用此开关,如以下配置中所示:

<configuration>
   <runtime>
      <!-- Warning: setting the following switch can introduce a security problem. -->
      <AppContextSwitchOverrides value="Switch.System.Data.AllowArbitraryDataSetTypeInstantiation=true" />
   </runtime>
</configuration>

在 ASP.NET 中,<AppContextSwitchOverrides> 元素不可用。 可以改为通过 Web.config 启用此开关,如以下配置中所示:

<configuration>
    <appSettings>
        <!-- Warning: setting the following switch can introduce a security problem. -->
        <add key="AppContext.SetSwitch:Switch.System.Data.AllowArbitraryDataSetTypeInstantiation" value="true" />
    </appSettings>
</configuration>

有关详细信息,请参阅 <AppContextSwitchOverrides> 元素。

在 .NET Core、.NET 5 和 ASP.NET Core 中,此设置由 runtimeconfig.json 控制,如以下 JSON 中所示:

{
  "runtimeOptions": {
    "configProperties": {
      "Switch.System.Data.AllowArbitraryDataSetTypeInstantiation": true
    }
  }
}

有关详细信息,请参阅“.NET Core 运行时配置设置”

除了使用配置文件以外,还可以通过 AppContext.SetSwitch 以编程方式设置 AllowArbitraryDataSetTypeInstantiation,如以下代码所示:

// Warning: setting the following switch can introduce a security problem.
AppContext.SetSwitch("Switch.System.Data.AllowArbitraryDataSetTypeInstantiation", true);

如果选择上述编程方法,对 AppContext.SetSwitch 的调用应在应用启动过程的早期阶段发生。

通过计算机范围的注册表 (.NET Framework 2.0 - 4.x)

如果 AppContext 不可用,可以使用 Windows 注册表禁用类型限制检查:

  • 管理员必须配置注册表。
  • 使用注册表是一项计算机范围的更改,会影响该计算机上运行的所有应用。
类型
注册表项 HKLM\SOFTWARE\Microsoft\.NETFramework\AppContext
值名称 Switch.System.Data.AllowArbitraryDataSetTypeInstantiation
值类型 REG_SZ
“数值数据” true

在 64 位操作系统上,可能需要为 64 位密钥(如上所示)和 32 位密钥添加此值。 32 位密钥位于 HKLM\SOFTWARE\WOW6432Node\Microsoft\.NETFramework\AppContext

有关使用注册表配置 AppContext 的详细信息,请参阅“库使用者的 AppContext”

有关不受信任输入的安全性

虽然 DataSetDataTable 确实对反序列化 XML 有效负载时允许存在的类型施加了默认限制,但在填充了不受信任的输入时,DataSetDataTable 通常是不安全的。下面是 DataSetDataTable 实例可能读取不受信任输入的方式的非详尽列表。

  • DataAdapter 引用数据库,DataAdapter.Fill 方法用于在 DataSet 中填充数据库查询的内容。
  • DataSet.ReadXmlDataTable.ReadXml 方法用于读取包含列和行信息的 XML 文件。
  • DataSetDataTable 实例将序列化为 ASP.NET (SOAP) Web 服务或 WCF 终结点的一部分。
  • XmlSerializer 等序列化程序用于从 XML 流反序列化 DataSetDataTable 实例。
  • JsonConvert 等序列化程序用于从 JSON 流反序列化 DataSetDataTable 实例。 JsonConvert 是流行的第三方 Newtonsoft.Json 库中的一个方法。
  • BinaryFormatter 等序列化程序用于从原始字节流反序列化 DataSetDataTable 实例。

本文档将讨论上述方案的安全注意事项。

使用 DataAdapter.Fill 从不受信任的数据源填充 DataSet

可以使用 DataAdapter.Fill 方法DataAdapter 填充 DataSet 实例,如以下示例所示。

// Assumes that connection is a valid SqlConnection object.
string queryString =
  "SELECT CustomerID, CompanyName FROM dbo.Customers";
SqlDataAdapter adapter = new SqlDataAdapter(queryString, connection);

DataSet customers = new DataSet();
adapter.Fill(customers, "Customers");

(以上代码示例摘自从 DataAdapter 填充 DataSet 中的一个较大示例。)

大多数应用都可以简化,并假定其数据库层是受信任的。 但是,如果你习惯于对应用进行威胁建模,则威胁模型可能会认为应用程序(客户端)与数据库层(服务器)之间存在信任边界。 在客户端与服务器之间使用相互身份验证AAD 身份验证是帮助解决与此相关的风险的一种方式。 本部分的余下内容将讨论客户端连接到不受信任服务器的可能后果。

DataAdapter 指向不受信任数据源的后果取决于 DataAdapter 本身的实现。

SqlDataAdapter

对于内置类型 SqlDataAdapter,引用不受信任的数据源可能会导致遭受拒绝服务 (DoS) 攻击。 而 DoS 攻击可能导致应用无响应或崩溃。 如果攻击者可以侵入应用的同时植入一个 DLL,则他们还能实现本地代码执行。

其他 DataAdapter 类型

第三方 DataAdapter 实现必须自行评估它们在面对不受信任的输入时提供的安全保证。 .NET 无法在这些实现方面做出任何安全保证。

DataSet.ReadXml 和 DataTable.ReadXml

DataSet.ReadXmlDataTable.ReadXml 方法与不受信任的输入一起使用时并不安全。 强烈建议使用者考虑改用本文档稍后所述的替代方法之一。

DataSet.ReadXmlDataTable.ReadXml 的实现最初是在序列化漏洞成为一个被充分了解的威胁类别之前创建的。 因此,代码不遵循当前的安全最佳做法。 这些 API 可用作攻击者对 Web 应用执行 DoS 攻击的载体。 这些攻击可能导致 Web 服务无响应或进程意外终止。 框架不针对这些攻击类别提供缓解措施,并且 .NET 将此行为看作“设计使然”。

.NET 已发布安全更新来缓解某些问题,例如 DataSet.ReadXmlDataTable.ReadXml 中的信息泄漏或远程代码执行。 .NET 安全更新可能无法针对这些威胁类别提供全面的保护。 使用者应该评估其各个应用场景,并考虑他们遇到这些风险的可能性。

使用者应该知道,这些 API 的安全更新在某些情况下可能会影响应用程序兼容性。 此外,还可能会在这些 API 中发现新的漏洞,而 .NET 实际上无法为此漏洞发布安全更新。

我们建议这些 API 的使用者:

  • 考虑使用本文档稍后所述的替代方法之一。
  • 在其应用中执行个人风险评估。

至于是否使用这些 API,完全由使用者负责决定。 使用者应评估使用这些 API 时可能附带的任何安全、技术和法律风险,包括管制要求。

通过 ASP.NET Web 服务或 WCF 处理 DataSet 和 DataTable

可以在 ASP.NET (SOAP) Web 服务中接受 DataSetDataTable 实例,如以下代码所示:

using System.Data;
using System.Web.Services;

[WebService(Namespace = "http://contoso.com/")]
public class MyService : WebService
{
    [WebMethod]
    public string MyWebMethod(DataTable dataTable)
    {
        /* Web method implementation. */
    }
}

这种做法的一种变化形式是不直接接受 DataSetDataTable 作为参数,而是接受它作为整个 SOAP 序列化对象图的一部分,如以下代码所示:

using System.Data;
using System.Web.Services;

[WebService(Namespace = "http://contoso.com/")]
public class MyService : WebService
{
    [WebMethod]
    public string MyWebMethod(MyClass data)
    {
        /* Web method implementation. */
    }
}

public class MyClass
{
    // Property of type DataTable, automatically serialized and
    // deserialized as part of the overall MyClass payload.
    public DataTable MyDataTable { get; set; }
}

或者,使用 WCF 而不是 ASP.NET Web 服务:

using System.Data;
using System.ServiceModel;

[ServiceContract(Namespace = "http://contoso.com/")]
public interface IMyContract
{
    [OperationContract]
    string MyMethod(DataTable dataTable);
    [OperationContract]
    string MyOtherMethod(MyClass data);
}

public class MyClass
{
    // Property of type DataTable, automatically serialized and
    // deserialized as part of the overall MyClass payload.
    public DataTable MyDataTable { get; set; }
}

在所有这种情况下,威胁模型和安全保证与 DataSet.ReadXml 和 DataTable.ReadXml 部分中所述相同。

通过 XmlSerializer 反序列化 DataSet 或 DataTable

开发人员可以使用 XmlSerializer 来反序列化 DataSetDataTable 实例,如以下代码所示:

using System.Data;
using System.IO;
using System.Xml.Serialization;

public DataSet PerformDeserialization1(Stream stream) {
    XmlSerializer serializer = new XmlSerializer(typeof(DataSet));
    return (DataSet)serializer.Deserialize(stream);
}

public MyClass PerformDeserialization2(Stream stream) {
    XmlSerializer serializer = new XmlSerializer(typeof(MyClass));
    return (MyClass)serializer.Deserialize(stream);
}

public class MyClass
{
    // Property of type DataTable, automatically serialized and
    // deserialized as part of the overall MyClass payload.
    public DataTable MyDataTable { get; set; }
}

在这种情况下,威胁模型和安全保证与 DataSet.ReadXml 和 DataTable.ReadXml 部分中所述相同

通过 JsonConvert 反序列化 DataSet 或 DataTable

可以使用流行的第三方 Newtonsoft 库 Json.NET 来反序列化 DataSetDataTable 实例,如以下代码所示:

using System.Data;
using Newtonsoft.Json;

public DataSet PerformDeserialization1(string json) {
    return JsonConvert.DeserializeObject<DataSet>(data);
}

public MyClass PerformDeserialization2(string json) {
    return JsonConvert.DeserializeObject<MyClass>(data);
}

public class MyClass
{
    // Property of type DataTable, automatically serialized and
    // deserialized as part of the overall MyClass payload.
    public DataTable MyDataTable { get; set; }
}

以这种方式从不受信任的 JSON Blob 反序列化 DataSetDataTable 并不安全。 此模式容易遭受拒绝服务攻击。 这种攻击可能导致应用崩溃或无响应。

备注

Microsoft 不为 Newtonsoft.Json 等第三方库的实现提供保证或支持。 提供这些信息是为了保持文档完整性,在撰写本文时这些信息是准确的。

通过 BinaryFormatter 反序列化 DataSet 或 DataTable

不得使用 BinaryFormatterNetDataContractSerializerSoapFormatter 或相关的不安全格式化程序从不受信任的有效负载反序列化 DataSetDataTable 实例:

  • 这很容易遭受完整远程代码执行攻击。
  • 使用自定义的 SerializationBinder 不足以防范此类攻击。

安全替换

对于以下任何一种应用:

  • 通过 .asmx SOAP 终结点或 WCF 终结点接受 DataSetDataTable
  • 将不受信任的数据反序列化为 DataSetDataTable 的实例。

请考虑替换对象模型以使用实体框架。 实体框架:

  • 是一个功能丰富的面向对象的新式框架,它可以代表关系数据。
  • 带来了多样化的数据库提供者生态系统,使你可以通过实体框架对象模型轻松投影数据库查询。
  • 当反序列化来自不受信任源的数据时提供内置保护。

对于使用 .aspx SOAP 终结点的应用,请考虑将这些终结点更改为使用 WCF。 WCF 可以替代 .asmx Web 服务,并且功能更全面。 可以通过 SOAP 公开 WCF 终结点,以便与现有的调用方兼容。

代码分析器

编译源代码时运行的代码分析器安全规则有助于在 C# 和 Visual Basic 代码中查找与此安全问题相关的漏洞。 Microsoft.CodeAnalysis.FxCopAnalyzers 是在 nuget.org 上分发的一个代码分析器 NuGet 包。

有关代码分析器的概述,请参阅源代码分析器概述

启用以下 Microsoft.CodeAnalysis.FxCopAnalyzers 规则:

  • CA2350:不要将 DataTable.ReadXml() 与不受信任的数据一起使用
  • CA2351:不要将 DataSet.ReadXml() 与不受信任的数据一起使用
  • CA2352:可序列化类型中的不安全 DataSet 或 DataTable 容易受到远程代码执行攻击
  • CA2353:可序列化类型中的不安全 DataSet 或 DataTable
  • CA2354:反序列化对象图中的不安全 DataSet 或 DataTable 容易受到远程代码执行攻击
  • CA2355:在可反序列化对象图中找到不安全的 DataSet 或 DataTable 类型
  • CA2356:Web 可反序列化对象图中的不安全 DataSet 或 DataTable 类型
  • CA2361:确保不要将自动生成的包含 DataSet.ReadXml() 的类与不受信任的数据一起使用
  • CA2362:自动生成的可序列化类型中的不安全 DataSet 或 DataTable 容易受到远程代码执行攻击

有关配置规则的详细信息,请参阅使用代码分析器

以下 NuGet 包中提供了新的安全规则:

  • Microsoft.CodeAnalysis.FxCopAnalyzers 3.3.0:适用于 Visual Studio 2019 版本 16.3 或更高版本
  • Microsoft.CodeAnalysis.FxCopAnalyzers 2.9.11:适用于 Visual Studio 2017 版本 15.9 或更高版本