保护连接字符串和其他配置信息72
简介
ASP.NET 应用程序的配置信息同通常存储在一个名为Web.config 的 XML 文件中。前面的教程中我们已经数次更新Web.config 文件了。例如,在第一篇教程 中创建 Northwind Typed DataSet 时,就在 <connectionStrings> 部分向 Web.config 自动添加了连接字符串信息。稍后的母版页与网站导航教程中,我们手动更新了 Web.config ,添加了一个 <pages> 元素,表示项目中的所有 ASP.NET 页面应使用 DataWebControls Theme 。
由于 Web.config 可能包含连接字符串等机密数据,因此确保Web.config 的安全性并将其对未授权查看者隐藏相当重要。默认情况下,对扩展名为.config 的文件的任何 HTTP 请求都是用 ASP.NET 引擎处理的。返回的消息内容为“This type of page is not served ”,如图 1 所示。这表示访问者如果仅仅在浏览器的地址栏中输入“ http://www.YourServer.com/Web.config ”,就无法查看您的 Web.config 文件的内容。
图1 :通过浏览器访问 Web.config 文件将返回“This type of page is not served ”消息
但如果黑客可以找到其他方法来查看您的Web.config 文件内容将会怎么样?如果黑客处理此信息,我们又能采取什么办法来进一步保护Web.config 文件中的机密信息?幸运的是,Web.config 文件中大部分内容都不是机密信息。如果黑客知道您的ASP.NET 页面使用的默认Theme 的名称,他们会造成什么样的危害?
但Web.config 文件某些部分中的机密信息可能包含连接字符串、用户名称、密码、服务器名称、密钥等。这些信息通常位于Web.config 文件的如下部分:
- <appSettings>
- <connectionStrings>
- <identity>
- <sessionState>
本教程中,我们将介绍保护此类机密配置信息的方法。我们将了解到,.NET Framework 的 2.0 版本提供了一个受保护的配置系统,通过它可以轻松通过编码对选中的文件部分进行加密和解密。
注意: 本教程将介绍 Microsoft 推荐的用于从ASP.NET 应用程序连接到数据库的方法。除了加密连接字符串外,您还可以通过确保连接到数据库的方式的安全性来增强系统保护。
步骤1:了解ASP.NET 2.0 的受保护配置选项
ASP.NET 2.0 包含一个受保护的配置系统,用于对配置信息进行加密和解密。包括.NET Framework 中可使用编程方式加密或解密配置信息的方法。受保护的配置系统通过 提供程序模型 允许开发人员选择使用哪种加密实现方式。
.NET Framework 提供了两个受保护的配置提供程序:
- RSAProtectedConfigurationProvider:使用不对称的 RSA 算法进行加/ 解密;
- DPAPIProtectedConfigurationProvider:使用 Windows 数据保护 API (DPAPI) 进行加/ 解密。
由于受保护的配置系统实现了提供程序设计模式,所以有可能创建您自己的受保护提供程序并将其插入您的应用程序。关于此过程的更多信息,请参见 实现受保护配置提供程序 。
RSA 和 DPAPI 提供程序使用密钥执行加密和解密例程。这些密钥可在机器级或用户级存储。使用机器级密钥的理想场景是web 应用程序在其自己专用的服务器上运行,或一个服务器上有多个应用程序需要共享加密信息。用户级密钥在共享宿主环境中则是一个更为安全的选择。使同一服务器上的其他应用程序无法对您的应用程序的受保护配置部分进行加密。
本教程中的示例将使用 DPAPI 提供程序及机器级密钥。具体地说,我们将学习加密Web.config 中的 <connectionStrings> 部分,虽然受保护的配置系统可用于加密大多数的Web.config 文件部分。有关使用用户级密钥或使用RSA 提供程序的信息,请查阅本教程末尾“进阶阅读”部分的相关内容。
注意:RSAProtectedConfigurationProvider 和 DPAPIProtectedConfigurationProvider 提供程序在 machine.config 文件中分别使用 RsaProtectedConfigurationProvider 和 DataProtectionConfigurationProvider 提供程序名称注册。加密和解密配置信息时,我们需要提供相对应的提供程序名称(RsaProtectedConfigurationProvider 或 DataProtectionConfigurationProvider ),而非实际的类型名称(RSAProtectedConfigurationProvider 和 DPAPIProtectedConfigurationProvider )。可以看到,machine.config 文件位于$WINDOWS$\Microsoft.NET\Framework\version\CONFIG 文件夹中。
步骤2:通过编码加/解密配置部分
我们可以通过几行代码使用特定的提供程序对特定配置部分进行加密或解密。稍后将介绍的代码只需通过编码引用相应的配置部分、调用其ProtectSection 或 UnprotectSection 方法、然后再调用 Save 方法保存更改即可。此外,.NET Framework 还提供了一个有用的命令行实用程序,可用于加 / 解密配置信息。我们将在步骤 3 中深入探讨此命令行实用程序。
为了便于演示通过编码保护配置信息,我们将创建一个ASP.NET 页面,其中包含用于加/ 解密 Web.config 文件中 <connectionStrings> 部分的按钮。
打开 AdvancedDAL 文件夹中的 EncryptingConfigSections.aspx 页面。从 Toolbox 将一个 TextBox 控件拖动到 Designer ,将其 ID 、TextMode 、Width 和 Rows 属性分别赋值为 WebConfigContents 、MultiLine 、95% 和 15 。此 TextBox 控件将显示 Web.config 文件中的内容,以便快速查看内容是否已被加密。当然,在实际的应用程序中,您将从不希望显示Web.config 文件的内容。
在 TextBox 下方添加两个 Button 控件,分别命名为 EncryptConnStrings 和 DecryptConnStrings 。 将这两个控件的 Text 属性赋值为 Encrypt Connection Strings 和 Decrypt Connection Strings 。
此时屏幕应类似图 2 所示。
图2 :向页面添加一个 TextBox 和两个 Button Web 控件
接下来,我们需要编写代码以加载和显示首次加载该页面时 WebConfigContents TextBox 中 Web.config 文件的内容。将下列代码加入页面的内含代码类。此代码添加了一个名为DisplayWebConfig 的方法,并在 Page.IsPostBack 为 False 时从 Page_Load event handler 调用它:
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 文件的物理路径。这 3 个类都位于 System.IO 名称空间 。因此,您需要向内含代码类的顶端添加一个 using System.IO 语句,或者,在这些类的名称前添加 "System.IO" 。
接下来,我们需要为这两个 Button 控件的 Click events 添加 event handler ,并通过 DPAPI 提供程序使用一个机器级的密钥添加适当的代码,以加 / 解密 <connectionStrings> 部分。在 Designer 中双击每个 Button ,以便在内含代码类中添加一个 Click event handler ,然后添加如下代码:
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();
}
}
两个 event handler 中使用的代码几乎是完全一样的。它们都从通过 WebConfigurationManager 类 的 OpenWebConfiguration 方法 获取关于当前应用程序的 Web.config 文件信息开始。此方法返回指定虚拟路径的web 配置文件。接下来,通过Configuration 类 的 GetSection(sectionName) 方法 访问 Web.config 文件的 <connectionStrings> 部分,返回ConfigurationSection 对象。
ConfigurationSection 对象包含一个 SectionInformation 属性 ,提供关于配置部分的其他信息及功能。如上面的代码所示,我们可以通过检查SectionInformation 属性的 IsProtected 属性来确定配置部分是否被加密。此外,还可通过SectionInformation 属性的 ProtectSection(provider) 和 UnprotectSection 方法来加/ 解密该部分。
输入字符串指定加密时使用的受保护配置提供程序的名称时,接受此信息的是ProtectSection(provider) 方法。我们在 EncryptConnString Button 的 event handler 中将 DataProtectionConfigurationProvider 传递到 ProtectSection(provider) 方法中,以便使用 DPAPI 提供程序。UnprotectSection 方法可以确定用于加密配置部分的提供程序,因此不需任何输入参数。
调用 ProtectSection(provider) 或 UnprotectSection 方法后,您必须调用 Configuration 对象的 Save 方法 以保存更改。一旦配置信息被加密或解密且更改被保存,我们就需要调用DisplayWebConfig 以将更新后的 Web.config 内容载入 TextBox 控件。
输入上述代码后,在浏览器中访问 EncryptingConfigSections.aspx 页面进行测试。首先出现的页面应列出Web.config 文件的内容,其中 <connectionStrings> 部分显示为纯文本(见图 3 )。
图 3 :向页面添加一个 TextBox 和两个 Button Web 控件
现在单击 Encrypt Connection Strings 按钮。如果启用了请求验证,从 WebConfigContents TextBox 回传的标记将产生一个 HttpRequestValidationException ,显示内容为“A potentially dangerous Request.Form value was detected from the client ”的消息。ASP.NET 2.0 中默认启用的请求验证阻止包含未编码HTML 的回传,专为防止脚本插入攻击而设计。在页面上或应用程序级可以禁用此检查。要在该页面禁用此功能,需要在@Page 指令中将 ValidateRequest 设置赋值为 False 。@Page 指令位于页面声明标记的顶端。
<%@ Page ValidateRequest="False" ... %>
有关请求验证及其功能、如何在页面级和应用程序级禁用此功能、以及如何HTML 编码标记的更多信息,请参见 请求验证:防止脚本攻击 。
禁用了页面的请求验证后,试试再次单击Encrypt Connection Strings 按钮。产生回传时,将访问配置文件,其<connectionStrings> 部分将通过 DPAPI 提供程序被加密。TextBox 随后将被更新并显示新的Web.config 内容。如图 4 所示,<connectionStrings> 信息现在已被加密。
图4 :单击 Encrypt Connection Strings 按钮将加密<connectionString> 部分
以下是笔者计算机上生成的加密的 <connectionStrings> 部分,不过为精简起见,删除了 <CipherData> 元素中的一些内容:
<connectionStrings
configProtectionProvider="DataProtectionConfigurationProvider">
<EncryptedData>
<CipherData>
<CipherValue>AQAAANCMnd8BFdERjHoAwE/...zChw==</CipherValue>
</CipherData>
</EncryptedData>
</connectionStrings>
注意 :<connectionStrings> 元素指定了用来执行加密使用的提供程序(DataProtectionConfigurationProvider) 。此信息将在单击Decrypt Connection Strings 按钮时为 UnprotectSection 方法所使用。
从Web.config 文件中访问连接字符串信息时,无论是通过我们编写的代码、从SqlDataSource 控件中访问,或通过自动生成的代码从我们的Typed DataSet 的 TableAdapter 中访问,它是自动解密的。简而言之,我们不需要添加任何其他代码或逻辑以解密已加密的<connectionString> 部分。为了便于演示,现在我们来访问前面学习过的教程,如Basic Reporting 一节中的 Simple Display 教程(~/BasicReporting/SimpleDisplay.aspx )。如图 5 所示,教程如预期的那样工作正常,这说明被加密的连接字符串已被ASP.NET 页面自动解密了。
图5 :数据访问层自动解密连接字符串信息
要将<connectionStrings> 部分还原成其纯文本表示形式,单击 Decrypt Connection Strings 按钮即可。产生回传时,应能看见 Web.config 中以纯文本显示的连接字符串。现在您的屏幕应与第一次访问此页面时的显示类似(如图3 所示)。
步骤3:使用aspnet_regiis.exe加密配置信息
.NET Framework 的 $WINDOWS$\Microsoft.NET\Framework\version\ 文件夹中包含多种命令行工具。例如,在 使用 SQL缓存依赖项 教程中,我们学习了使用 aspnet_regsql.exe 命令行工具添加 SQL 缓存依赖项所需的基础架构。此文件夹中另一个有用的命令行工具就是 ASP.NET IIS Registration 工具 (aspnet_regiis.exe) 。顾名思义,ASP.NET IIS Registration 工具主要用来将ASP.NET 2.0 应用程序注册到 Microsoft 的专业级Web 服务器IIS 。除了提供 IIS 相关特性,ASP.NET IIS Registration 工具还可用于加密或解密 Web.config 文件中特定的配置部分。
下面的语句是使用aspnet_regiis.exe 命令行工具加密配置部分的常用语法:
aspnet_regiis.exe -pef section physical_directory -prov provider
其中,section 为要加密的配置部分 (如connectionStrings),physical_directory 为指向web 应用程序的根目录的完整物理路径 ,provider 为要使用的受保护配置提供程序的名称 (如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 提供程序使用特定于计算机的密钥,因此您必须在提供web 页面的同一台机器上运行 aspnet_regiis.exe 。例如,如果从用作开发的本地机器上运行此命令行程序,然后将被加密的Web.config 文件上载到生产服务器上,生产服务器将无法解密连接字符串信息,因为用于加密该信息的密钥是使用特定于您的开发机器的。而由于RSA 密钥可以导出到其他机器上,因此对于RSA 提供程序而言则没有此限制。
了解数据库验证选项
任何应用程序在能够发布SELECT 、INSERT 、UPDATE 或 DELETE 查询到Microsoft SQL Server 数据库之前,都必须首先使数据库能识别请求者。此过程即为验证,SQL Server 提供两种验证方法:
- Windows验证:运行 应用程序的进程用于与数据库通信 。 通过 Visual Studio 2005 的 ASP.NET Development Server 运行 ASP.NET 应用程序时,ASP.NET 应用程序将采用当前登录用户的身份。 Microsoft Internet Information Server (IIS) 上的 ASP.NET 应用程序通常采用的身份为 domainName\ MachineName 或 domainName\ NETWORK SERVICE ,当然这也可以定制。
- SQL Authentication:将提供用户的 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
这里的 “Integrated Security=True”以及没有用户名及密码说明使用的是Windows 验证。在某些连接字符串中使用的是Trusted Connection=Yes 或Integrated Security=SSPI,而非Integrated Security=True,不过这三种都表明使用的是Windows 验证。
下例显示了使用SQL验证的连接字符串。注意,凭据是嵌入到连接字符串中的:
Server=serverName; Database=Northwind; uid=userID; pwd=password
假设黑客可以查看您的应用程序的 Web.config 文件。如果您使用SQL 验证连接到在通过 Internet 可访问的数据库,黑客就可以使用此连接字符串通过SQL Management Studio 或从他们的网站的ASP.NET 网页连接到您的数据库。为避免此类威胁,应使用受保护的配置系统,对Web.config 文件中的连接字符串信息进行加密。
注意 :有关SQL Server 中可用验证类型的更多信息 ,请参阅 构建安全的 ASP.NET 应用程序:验证、授权和安全通信 。访问ConnectionStrings.com可深入了解演示Windows验证与SQL验证语法差异的连接字符串示例。
小结
默认情况下,不能通过浏览器访问 ASP.NET 应用程序中带有 .config 扩展名的文件。因为它们可能包含机密信息,如数据库连接字符串、用户名及密码等,所以不会返回这类文件。.NET 2.0 中的受保护配置系统可以对特定的配置部分进行加密,从而进一步保护机密信息。有两类内置的受保护配置提供程序:使用RSA 算法的提供程序以及使用Windows 数据保护 API (DPAPI) 的提供程序。
本教程中,我们介绍了如何使用 DPAPI 提供程序加/ 解密配置设置。这一过程可通过编码完成,如步骤2 所示;也可通过aspnet_regiis.exe 命令行工具完成,如步骤 3 所示。有关使用用户级密钥或或使用RSA 提供程序进行加/ 解密的更多信息,请参看“进阶阅读”部分的相关内容。
快乐编程!