保护连接字符串和其他配置信息 (C#)

作者 :斯科特·米切尔

下载 PDF

ASP.NET 应用程序通常将配置信息存储在 Web.config 文件中。 其中一些信息很敏感,需要保护。 默认情况下,此文件不会提供给网站访问者,但管理员或黑客可能会访问 Web 服务器的文件系统并查看文件的内容。 在本教程中,我们了解到,ASP.NET 2.0 允许通过加密 Web.config 文件的节来保护敏感信息。

介绍

ASP.NET 应用程序的配置信息通常存储在名为 Web.config 的 XML 文件中。 在这些教程的过程中,我们更新 Web.config 了几次。 在第一个教程中创建Northwind类型化数据集时,连接字符串信息会自动添加到Web.config<connectionStrings>部分中。 稍后,在 母版页和网站导航 教程中,我们手动更新 Web.config,并添加一个 <pages> 元素,指示项目中的所有 ASP.NET 页都应使用 DataWebControls 主题。

由于Web.config可能包含敏感数据(如连接字符串),Web.config因此请务必将内容安全且隐藏在未经授权的查看者中。 默认情况下,对扩展名为 .config 文件的任何 HTTP 请求都由 ASP.NET 引擎处理,该引擎返回 此类型的页面未送达 图 1 中显示的消息。 这意味着访问者只需输入http://www.YourServer.com/Web.config浏览器的地址栏即可查看文件Web.config内容。

通过浏览器访问 Web.config 将返回此类型的页面未送达消息

图 1:通过浏览器访问 Web.config 返回此类型的页面未提供消息(单击以查看全尺寸图像

但是,如果攻击者能够找到允许她查看 Web.config 文件内容的一些其他攻击,该怎么办? 攻击者可以使用此信息做什么,以及可以采取哪些步骤进一步保护其中的 Web.config敏感信息? 幸运的是,大多数 Web.config 部分不包含敏感信息。 如果攻击者知道 ASP.NET 页面使用的默认主题的名称,攻击者会受到什么伤害?

但是,某些Web.config部分包含可能包含连接字符串、用户名、密码、服务器名称、加密密钥等敏感信息。 此信息通常在以下 Web.config 部分中找到:

  • <appSettings>
  • <connectionStrings>
  • <identity>
  • <sessionState>

在本教程中,我们将介绍保护此类敏感配置信息的技术。 正如我们所看到的,.NET Framework 版本 2.0 包括一个受保护的配置系统,使以编程方式加密和解密所选配置部分是微风。

注意

本教程总结了Microsoft从 ASP.NET 应用程序连接到数据库的建议。 除了加密连接字符串之外,还可以通过确保以安全方式连接到数据库来帮助强化系统。

步骤 1:探索 ASP.NET 2.0 s 受保护的配置选项

ASP.NET 2.0 包括用于加密和解密配置信息的受保护配置系统。 这包括 .NET Framework 中可用于以编程方式加密或解密配置信息的方法。 受保护的配置系统使用提供程序模型,使开发人员可以选择使用哪种加密实现。

.NET Framework 附带两个受保护的配置提供程序:

由于受保护的配置系统实现了提供程序设计模式,因此可以创建自己的受保护的配置提供程序并将其插入应用程序。 有关此过程的详细信息,请参阅 “实现受保护的配置提供程序 ”。

RSA 和 DPAPI 提供程序使用密钥进行加密和解密例程,这些密钥可以存储在计算机或用户级别。 计算机级密钥非常适合 Web 应用程序在其自己的专用服务器上运行的情况,或者如果服务器上有多个应用程序需要共享加密信息。 用户级密钥是共享托管环境中的更安全选项,在同一服务器上其他应用程序不应解密应用程序的受保护配置部分。

在本教程中,我们的示例将使用 DPAPI 提供程序和计算机级密钥。 具体而言,我们将介绍加密 <connectionStrings> 部分 Web.config,尽管受保护的配置系统可用于加密大多数部分 Web.config 。 有关使用用户级密钥或使用 RSA 提供程序的信息,请参阅本教程末尾的“进一步阅读”部分中的资源。

注意

提供程序RSAProtectedConfigurationProviderDPAPIProtectedConfigurationProvider提供程序分别在machine.config文件中注册了提供程序名称和RsaProtectedConfigurationProviderDataProtectionConfigurationProvider提供程序。 加密或解密配置信息时,我们需要提供适当的提供程序名称(RsaProtectedConfigurationProviderDataProtectionConfigurationProvider),而不是实际的类型名称(RSAProtectedConfigurationProviderDPAPIProtectedConfigurationProvider)。 可以在文件夹中找到 machine.config 该文件 $WINDOWS$\Microsoft.NET\Framework\version\CONFIG

步骤 2:以编程方式加密和解密配置部分

通过几行代码,可以使用指定的提供程序加密或解密特定配置节。 正如我们很快就会看到的代码,只需以编程方式引用相应的配置部分,调用其 ProtectSectionUnprotectSection 方法,然后调用 Save 该方法来保留更改。 此外,.NET Framework 还包含一个有用的命令行实用工具,可用于加密和解密配置信息。 我们将在步骤 3 中探索此命令行实用工具。

为了以编程方式说明如何保护配置信息,让我们创建一个 ASP.NET 页,其中包含用于加密和解密部分中Web.config<connectionStrings>按钮。

首先打开 EncryptingConfigSections.aspx 文件夹中的页面 AdvancedDAL 。 将 TextBox 控件从工具箱拖到设计器上,将其ID属性MultiLine设置为WebConfigContents,将Width属性TextMode分别设置为 95% 和 Rows 15。 此 TextBox 控件将显示 Web.config 允许我们快速查看内容是否已加密的内容。 当然,在实际应用程序中,你永远不想显示其内容 Web.config

在 TextBox 下面,添加两个名为 EncryptConnStringsDecryptConnStrings. 将其文本属性设置为“加密连接字符串”和“解密连接字符串”。

此时,屏幕应类似于图 2。

显示 Visual Studio 打开的屏幕截图,其中EncryptingConfigSections.aspx页,其中包含一个新的 TextBox 和两个按钮控件。

图 2:向页面添加一个 TextBox 和两个按钮 Web 控件(单击以查看全尺寸图像

接下来,我们需要编写在首次加载页面时加载和显示 TextBox 中WebConfigContents内容Web.config的代码。 将以下代码添加到页面代码隐藏类。 此代码添加一个命名DisplayWebConfig的方法,并在以下false情况下Page.IsPostBackPage_Load事件处理程序调用该方法:

protected void Page_Load(object sender, EventArgs e)
{
    // On the first page visit, call DisplayWebConfig method
    if (!Page.IsPostBack)
        DisplayWebConfig();
}
private void DisplayWebConfig()
{
    // Reads in the contents of Web.config and displays them in the TextBox
    StreamReader webConfigStream = 
        File.OpenText(Path.Combine(Request.PhysicalApplicationPath, "Web.config"));
    string configContents = webConfigStream.ReadToEnd();
    webConfigStream.Close();
    WebConfigContents.Text = configContents;
}

该方法DisplayWebConfig使用File打开应用程序Web.config文件、StreamReader类内容读取到字符串中,以及Path生成文件的物理路径的Web.config类。 这三个类都位于命名空间System.IO。 因此,需要将语句添加到usingSystem.IO代码隐藏类的顶部,或者用这些类名System.IO.作为前缀。

接下来,我们需要为两个 Button 控件 Click 事件添加事件处理程序,并添加必要的代码,以便通过 DPAPI 提供程序使用计算机级密钥加密和解密 <connectionStrings> 节。 在设计器中,双击每个 Buttons 以在代码隐藏类中添加 Click 事件处理程序,然后添加以下代码:

protected void EncryptConnStrings_Click(object sender, EventArgs e)
{
    // Get configuration information about Web.config
    Configuration config = 
        WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath);
    // Let's work with the <connectionStrings> section
    ConfigurationSection connectionStrings = config.GetSection("connectionStrings");
    if (connectionStrings != null)
        // Only encrypt the section if it is not already protected
        if (!connectionStrings.SectionInformation.IsProtected)
        {
            // Encrypt the <connectionStrings> section using the 
            // DataProtectionConfigurationProvider provider
            connectionStrings.SectionInformation.ProtectSection(
                "DataProtectionConfigurationProvider");
            config.Save();
            
            // Refresh the Web.config display
            DisplayWebConfig();
        }
}
protected void DecryptConnStrings_Click(object sender, EventArgs e)
{
    // Get configuration information about Web.config
    Configuration config = 
        WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath);
    // Let's work with the <connectionStrings> section
    ConfigurationSection connectionStrings = 
        config.GetSection("connectionStrings");
    if (connectionStrings != null)
        // Only decrypt the section if it is protected
        if (connectionStrings.SectionInformation.IsProtected)
        {
            // Decrypt the <connectionStrings> section
            connectionStrings.SectionInformation.UnprotectSection();
            config.Save();
            // Refresh the Web.config display
            DisplayWebConfig();
        }
}

两个事件处理程序中使用的代码几乎完全相同。 它们首先通过WebConfigurationManager方法OpenWebConfiguration Web.config 获取有关当前应用程序文件的信息。 此方法返回指定虚拟路径的 Web 配置文件。 接下来,Web.config通过Configuration返回对象的类方法ConfigurationSection访问文件<connectionStrings>部分。GetSection(sectionName)

ConfigurationSection 对象包含一个 SectionInformation 属性,该属性 提供有关配置部分的其他信息和功能。 如上面的代码所示,我们可以通过检查 SectionInformation 属性的属性 IsProtected 来确定配置节是否已加密。 此外,可以通过属性和ProtectSection(provider)UnprotectSection方法对节进行加密或解密SectionInformation

该方法 ProtectSection(provider) 接受为输入字符串,指定加密时要使用的受保护配置提供程序的名称。 在 Button 事件处理程序中 EncryptConnString ,我们将 DataProtectionConfigurationProvider 传递到方法, ProtectSection(provider) 以便使用 DPAPI 提供程序。 该方法 UnprotectSection 可以确定用于加密配置节的提供程序,因此不需要任何输入参数。

调用 ProtectSection(provider)UnprotectSection 方法后,必须调用 Configuration 对象 Save 方法 来保留更改。 加密或解密配置信息并保存更改后,我们调用 DisplayWebConfig 将更新 Web.config 的内容加载到 TextBox 控件中。

输入上述代码后,通过浏览器访问 EncryptingConfigSections.aspx 页面对其进行测试。 最初应看到一个页面,其中列出了以纯文本形式显示的节的内容Web.config<connectionStrings>(请参阅图 3)。

显示 Web 浏览器中加载的EncryptingConfigSections.aspx页面的屏幕截图。

图 3:向页面添加一个 TextBox 和两个按钮 Web 控件(单击以查看全尺寸图像

现在,单击“加密连接字符串”按钮。 如果启用了请求验证,则从 WebConfigContents TextBox 回发的标记将生成一个显示消息的消息,从客户端检测到一个 HttpRequestValidationException潜在的危险 Request.Form 值。 请求验证在 ASP.NET 2.0 中默认启用,禁止包含未编码 HTML 的回发,旨在帮助防止脚本注入攻击。 可以在页面或应用程序级别禁用此检查。 若要关闭此页面,请将 ValidateRequest 设置设置为 false 指令中 @Page 。 该 @Page 指令位于页面声明性标记的顶部。

<%@ Page ValidateRequest="False" ... %>

若要详细了解请求验证及其用途、如何在页面和应用程序级别禁用它,以及如何对 HTML 编码标记进行禁用,请参阅 请求验证 - 防止脚本攻击

禁用页面请求验证后,请尝试再次单击“加密连接字符串”按钮。 在回发时,将访问配置文件及其部分,并使用 <connectionStrings> DPAPI 提供程序进行加密。 然后更新 TextBox 以显示新 Web.config 内容。 如图 4 所示, <connectionStrings> 信息现已加密。

单击“加密连接字符串”按钮加密 <connectionString> 节

图 4:单击“加密连接字符串”按钮加密 <connectionString> 分区(单击以查看全尺寸图像

在我的计算机上生成的加密 <connectionStrings> 部分遵循,尽管元素中的 <CipherData> 某些内容已被删除,但为简洁起见:

<connectionStrings 
    configProtectionProvider="DataProtectionConfigurationProvider">
  <EncryptedData>
    <CipherData>
      <CipherValue>AQAAANCMnd8BFdERjHoAwE/...zChw==</CipherValue>
    </CipherData>
  </EncryptedData>
</connectionStrings>

注意

<connectionStrings> 元素指定用于执行加密的提供程序(DataProtectionConfigurationProvider)。 单击“解密连接字符串”按钮时,此方法将使用 UnprotectSection 此信息。

从我们编写的代码、SqlDataSource 控件或 Typed DataSets 中的 TableAdapters 自动生成的代码访问连接字符串信息Web.config时,它会自动解密。 简言之,我们不需要添加任何额外的代码或逻辑来解密加密 <connectionString> 部分。 若要演示这一点,请访问当前前面的教程之一,例如“基本报告”部分中的“简单显示”教程(~/BasicReporting/SimpleDisplay.aspx)。 如图 5 所示,本教程的工作方式与预期完全相同,指示加密连接字符串信息将由 ASP.NET 页面自动解密。

数据访问层自动解密连接字符串信息

图 5:数据访问层自动解密连接字符串信息(单击以查看全尺寸图像

若要将分区还原 <connectionStrings> 回其纯文本表示形式,请单击“解密连接字符串”按钮。 在回发时,应会看到纯文本中的Web.config连接字符串。 此时,你的屏幕在首次访问此页面时应如下所示(如图 3 所示)。

步骤 3:使用 aspnet_regiis.exe 加密配置节

.NET Framework 包含文件夹中的各种命令行工具 $WINDOWS$\Microsoft.NET\Framework\version\例如,在“使用 SQL 缓存依赖项”教程中,我们查看了如何使用aspnet_regsql.exe命令行工具添加 SQL 缓存依赖项所需的基础结构。 此文件夹中的另一个有用的命令行工具是 ASP.NET IIS 注册工具 (aspnet_regiis.exe 。 顾名思义,ASP.NET IIS 注册工具主要用于向Microsoft专业级 Web 服务器 IIS 注册 ASP.NET 2.0 应用程序。 除了与 IIS 相关的功能外,ASP.NET IIS 注册工具还可用于加密或解密指定的 Web.config配置部分。

以下语句显示了用于使用命令行工具加密配置节的 aspnet_regiis.exe 常规语法:

aspnet_regiis.exe -pef section physical_directory -prov provider

是加密的配置部分(如 connectionStrings),physical_directory是 Web 应用程序根目录的完整物理路径,提供程序是要使用的受保护配置提供程序的名称(如 DataProtectionConfigurationProvider)。 或者,如果 Web 应用程序在 IIS 中注册,则可以使用以下语法输入虚拟路径,而不是物理路径:

aspnet_regiis.exe -pe section -app virtual_directory -prov provider

以下示例 aspnet_regiis.exe 使用 DPAPI 提供程序和计算机级密钥加密 <connectionStrings> 该部分:

aspnet_regiis.exe -pef
"connectionStrings" "C:\Websites\ASPNET_Data_Tutorial_73_CS"
-prov "DataProtectionConfigurationProvider"

同样, aspnet_regiis.exe 命令行工具可用于解密配置部分。 而不是使用 -pef 开关,请使用 -pdf (或而不是 -pe使用 -pd)。 此外,请注意,解密时不需要提供程序名称。

aspnet_regiis.exe -pdf section physical_directory
  -- or --
aspnet_regiis.exe -pd section -app virtual_directory

注意

由于我们使用特定于计算机的密钥的 DPAPI 提供程序,因此必须从提供网页的同一台计算机运行 aspnet_regiis.exe 。 例如,如果从本地开发计算机运行此命令行程序,然后将加密的 Web.config 文件上传到生产服务器,则生产服务器将无法解密连接字符串信息,因为它使用特定于开发计算机的密钥进行加密。 RSA 提供程序没有此限制,因为可以将 RSA 密钥导出到另一台计算机。

了解数据库身份验证选项

在任何应用程序可以向Microsoft SQL Server 数据库发出SELECTINSERTUPDATEDELETE查询之前,数据库必须先标识请求者。 此过程称为 身份验证 ,SQL Server 提供两种身份验证方法:

  • Windows 身份验证 - 运行应用程序的进程用于与数据库通信。 通过 Visual Studio 2005 s ASP.NET Development Server 运行 ASP.NET 应用程序时,ASP.NET 应用程序假定当前登录用户的标识。 对于Microsoft Internet Information Server(IIS)上的 ASP.NET 应用程序,ASP.NET 应用程序通常假定其标识domainName``\MachineNamedomainName``\NETWORK SERVICE,也可以自定义。
  • SQL 身份验证 - 用户 ID 和密码值作为身份验证凭据提供。 使用 SQL 身份验证时,连接字符串中提供了用户 ID 和密码。

Windows 身份验证优先于 SQL 身份验证,因为它更安全。 使用Windows 身份验证连接字符串可从用户名和密码中释放,如果 Web 服务器和数据库服务器位于两个不同的计算机上,则不会以纯文本形式通过网络发送凭据。 但是,使用 SQL 身份验证时,身份验证凭据在连接字符串中硬编码,并且以纯文本形式从 Web 服务器传输到数据库服务器。

这些教程使用了Windows 身份验证。 可以通过检查连接字符串来判断正在使用哪种身份验证模式。 教程中的Web.config连接字符串是:

Data Source=.\SQLEXPRESS; AttachDbFilename=|DataDirectory|\NORTHWND.MDF; Integrated Security=True; User Instance=True

集成安全性=True 且缺少用户名和密码表示正在使用Windows 身份验证。 在某些连接字符串使用术语“受信任的连接=是”或“集成安全性=SSPI”,而不是“集成安全性=True”,但这三个都表示使用Windows 身份验证。

以下示例演示使用 SQL 身份验证的连接字符串。 $CREDENTIAL_PLACEHOLDER$ 是密码密钥值对的占位符。 请注意,凭据嵌入在连接字符串中:

Server=serverName; Database=Northwind; uid=userID; $CREDENTIAL_PLACEHOLDER$

假设攻击者能够查看应用程序 Web.config 文件。 如果使用 SQL 身份验证连接到可通过 Internet 访问的数据库,攻击者可以使用此连接字符串通过 SQL Management Studio 或自己的网站上的 ASP.NET 页连接到数据库。 为了帮助缓解此威胁,请使用受保护的配置系统加密连接字符串信息Web.config

注意

有关 SQL Server 中可用的不同类型的身份验证的详细信息,请参阅 生成安全 ASP.NET 应用程序:身份验证、授权和安全通信。 有关说明 Windows 和 SQL 身份验证语法差异的进一步连接字符串示例,请参阅 ConnectionStrings.com

总结

默认情况下,无法通过浏览器访问 ASP.NET 应用程序中扩展名 .config 的文件。 这些类型的文件不会返回,因为它们可能包含敏感信息,例如数据库连接字符串、用户名和密码等。 .NET 2.0 中的受保护配置系统允许加密指定的配置节,从而进一步保护敏感信息。 有两个内置保护的配置提供程序:一个使用 RSA 算法,一个使用 Windows 数据保护 API(DPAPI)。

本教程介绍了如何使用 DPAPI 提供程序加密和解密配置设置。 这可以通过编程方式完成,正如我们在步骤 2 中看到的那样,以及通过 aspnet_regiis.exe 步骤 3 中介绍的命令行工具来实现。 有关改用用户级密钥或使用 RSA 提供程序的详细信息,请参阅“进一步阅读”部分中的资源。

快乐编程!

深入阅读

有关本教程中讨论的主题的详细信息,请参阅以下资源:

关于作者

斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0。 他可以通过他的博客联系到mitchell@4GuysFromRolla.com他,可以在该博客中找到http://ScottOnWriting.NET

特别感谢

本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是 Teresa Murphy 和 Randy Schmidt。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请把我扔一条线。mitchell@4GuysFromRolla.com