桦山涧

桦山涧
Asp.net ---->知识改变命运!
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

构建安全的数据访问(2)[转]

Posted on 2006-12-08 15:16  桦林  阅读(459)  评论(0编辑  收藏  举报

敏感数据

许多 Web 应用程序都在数据库中以某种形式存储敏感数据。如果攻击者设法针对您的数据库执行查询,则务必要适当地加密所有敏感数据项(如信用卡号)。

加密需要存储的敏感数据。

确保网络上敏感数据的安全。

使用带有salt 的哈希值存储密码。

加密需要存储的敏感数据

尽可能避免存储敏感数据。如果必须存储敏感数据,请对其进行加密。

使用 3DES 加密

要将敏感数据(如信用卡号)存储在数据库中,请使用强对称加密算法,如 3DES。

在开发过程中,要启用 3DES 加密

1.

使用 RNGCryptoServiceProvider 类来生成强(192 位,24 字节)加密密钥。

2.

备份加密密钥,并将备份副本存储在物理安全的位置。

3.

使用 DPAPI 对密钥进行加密,并将其存储在注册表项中。使用下面的 ACL 来确保注册表项的安全:

管理员:完全控制
                        进程帐户(例如,ASPNET):读取

在运行时,要将加密数据存储在数据库中

1.

获取要加密的数据。

2.

从注册表中检索经过加密的加密密钥。

3.

使用 DPAPI 对加密密钥进行解密。

4.

结合使用 TripleDESCryptoServiceProvider 类和加密密钥来加密数据。

5.

将加密数据存储在数据库中。

在运行时,要对加密的机密进行解密

1.

从数据库中检索加密数据。

2.

从注册表中检索经过加密的加密密钥。

3.

使用 DPAPI 对加密密钥进行解密。

4.

使用 TripleDESCryptoServiceProvider 类对加密数据进行解密。

在此过程中,如果用来对加密密钥进行加密的 DPAPI 帐户被损坏,则可以从备份位置检索 3DES 密钥的备份,并在新帐户下使用 DPAPI 对该备份进行加密。新的加密密钥可以存储在注册表中,数据库中的数据仍可以进行解密。

有关创建托管 DPAPI 库的详细信息,请参阅“Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication”中“How To”部分中的“How To: Create a DPAPI Library”,其网址为:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/secnetlpMSDN.asp(英文)。

保护网络上敏感数据的安全

通过网络传入和传出数据库服务器的敏感数据可能包括应用程序特定的数据或数据库登录凭据。为了确保网络上的数据的私密性和完整性,可以使用平台级解决方案(如由安全数据中心提供的解决方案,在这种解决方案中,在服务器之间使用 IPSec 加密的通信通道),也可以对您的应用程序进行配置,以便与数据库建立 SSL 连接。后一种方法需要在数据库服务器上安装服务器证书。

有关使用 SSL 和 IPSec 的详细信息,请参阅“Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication”的“How To”部分中的“How To: Use IPSec to Provide Secure Communication Between Two Servers”和“How To:Use SSL to Secure Communication to SQL Server 2000”,其网址为:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/secnetlpMSDN.asp(英文)

使用带有salt 的哈希值存储密码

如果您需要实现一个包含用户名和密码的用户存储,请不要以明文或加密格式来存储密码。存储增加了 salt 的不可逆哈希值(而不是存储密码)可以降低词典攻击的风险。

注意 salt 值是密码形式的强随机数字。

创建 Salt 值

下面的代码显示了如何通过使用随机数字生成功能(此功能由 System.Security.Cryptography 命名空间中的 RNGCryptoServiceProvider 类提供)来生成 salt 值。

public static string CreateSalt(int size)
{
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] buff = new byte[size];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}

创建 Hash 值(带有 Salt)

下面的代码片断显示了如何从所提供的密码和 salt 值生成哈希值。

public static string CreatePasswordHash(string pwd, string salt)
{
string saltAndPwd = string.Concat(pwd, salt);
string hashedPwd =
FormsAuthentication.HashPasswordForStoringInConfigFile(
saltAndPwd, "SHA1");
return hashedPwd;
}

详细信息

有关实现用户存储(使用带有salt 的哈希值存储密码)的详细信息,请参阅“Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication”的“How To”部分中的“How To: Use Forms Authentication with SQL Server 2000”,其网址为:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/secnetlpMSDN.asp(英文)。

异常管理

异常条件可能会由配置错误、代码中的错误或恶意输入引起。如果没有正确的异常管理,这些条件可能会透露有关数据源位置和特性的敏感信息,以及有价值的连接详细信息。下面的建议适用于数据访问代码:

捕获和记录 ADO.NET 异常。

确保数据库连接总是处于断开状态。

在 ASP.NET 应用程序中使用一般错误页面。

捕获和记录 ADO.NET 异常

将数据访问代码放在 try/catch 块中并处理异常。在编写 ADO.NET 数据访问代码时,由 ADO.NET 生成的异常类型取决于数据提供程序。例如:

SQL Server .NET Framework 数据提供程序生成 SqlException

OLE DB .NET Framework 数据提供程序生成 OleDbException

ODBC .NET Framework 数据提供程序生成 OdbcException

捕获异常

下面的代码使用 SQL Server .NET Framework 数据提供程序,并显示应该如何捕获类型为 SqlException 的异常。

try
{
// 数据访问代码
}
catch (SqlException sqlex) // 比较具体
{
}
catch (Exception ex) // 比较一般
{
}

记录异常

还应该记录来自 SqlException 类的详细信息。此类公开那些包含异常条件详细信息的属性。这些属性包括 Message 属性(用来描述错误)、Number 属性(用来唯一标识错误类型)以及 State 属性(其中包含其他信息)。State 属性通常用来指示特定错误条件出现的具体位置。例如,如果某个存储过程从多个行中生成同一错误,则 State 属性可以指出错误出现的具体位置。最后,Errors 集合中包含 SqlError 对象,这些对象提供详细的 SQL 服务器错误信息。

下面的代码片断显示了如何通过使用 SQL Server .NET Framework 数据提供程序来处理 SQL Server 错误条件:

using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
// 由数据访问层 (DAL) 组件公开的方法
public string GetProductName( int ProductID )
{
SqlConnection conn = new SqlConnection(
"server=(local);Integrated Security=SSPI;database=products");
// 将所有的数据访问代码包含在 try 块中
try
{
conn.Open();
SqlCommand cmd = new SqlCommand("LookupProductName", conn );
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@ProductID", ProductID );
SqlParameter paramPN =
cmd.Parameters.Add("@ProductName", SqlDbType.VarChar, 40 );
paramPN.Direction = ParameterDirection.Output;
cmd.ExecuteNonQuery();
// 在该方法返回之前先执行 finally 代码
return paramPN.Value.ToString();
}
catch (SqlException sqlex)
{
// 处理数据访问异常条件
// 记录具体的异常详细信息
LogException(sqlex);
// 将当前异常包装在一个相关性更强的
// 外部异常中,并重新引发新异常
throw new Exception(
"Failed to retrieve product details for product ID: " +
ProductID.ToString(), sqlex );
}
finally
{
conn.Close(); // 确保连接处于断开状态
}
}
// Helper 例程,该例程将 SqlException 详细信息记录到
// 应用程序事件日志中
private void LogException( SqlException sqlex )
{
EventLog el = new EventLog();
el.Source = "CustomAppLog";
string strMessage;
strMessage = "Exception Number :" + sqlex.Number +
"(" + sqlex.Message + ") has occurred";
el.WriteEntry( strMessage );
foreach (SqlError sqle in sqlex.Errors)
{
strMessage = "Message:" + sqle.Message +
" Number:" + sqle.Number +
" Procedure:" + sqle.Procedure +
" Server:" + sqle.Server +
" Source:" + sqle.Source +
" State:" + sqle.State +
" Severity:" + sqle.Class +
" LineNumber:" + sqle.LineNumber;
el.WriteEntry( strMessage );
}
}

确保数据库连接总是处于断开状态

如果发生异常,一定要断开数据库连接,并释放其他所有受限制的资源。使用 finally 块或 C# using 语句,可以确保无论是否发生了异常条件,都会断开数据库连接。上面的代码阐释了 finally 块的用法。还可以按如下方式使用 C# using 语句:

using ((SqlConnection conn = new SqlConnection(connString)))
{
conn.Open();
// 在以下情况下将断开连接:生成异常或者控制流
// 通常会离开 using 语句的使用范围
}

在 ASP.NET 应用程序中使用一般错误页面

如果您的数据访问代码由 ASP.NET Web 应用程序或 Web 服务调用,则应该对 <customErrors> 元素进行配置,以防异常详细信息传播回到最终用户。还可以通过使用该元素来指定一般错误页面,如下所示。

<customErrors mode="On" defaultRedirect="YourErrorPage.htm" />

对于生产服务器设置 mode="On"。只有在发布之前开发和测试软件时才使用 mode="Off"。如果不这样做,将导致向最终用户返回大量错误信息(如图 14.4 中显示的信息)。这些信息可能包含数据库服务器的名称、数据库名称和连接凭据。

详细的异常信息会透露敏感数据

图 14.4
详细的异常信息会透露敏感数据

图 14.4 还显示了数据访问代码中接近导致异常的行的大量漏洞。特别是:

连接字符串是硬编码的。

特权极高的 sa 帐户用于连接到数据库。

sa 帐户有一个弱密码。

SQL 命令的构造容易受到 SQL 注入攻击;输入内容未进行验证,代码不使用参数化存储过程。

构建安全的数据访问组件

下面的代码显示了 CheckProductStockLevel 方法(用来在产品数据库中查询库存量)的示例实现,该代码阐释了本模块前面介绍的数据访问代码的许多重要安全功能。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using Microsoft.Win32;
using DataProtection;
public static int CheckProductStockLevel(string productCode)
{
int quantity = 0;
// (1) 由 try/catch 块保护的代码
try
{
// (2) 使用正则表达式验证的输入内容
//     应该从资源程序集中检索错误消息,以帮助实现
//     本地化。为简短起见,省略了 Localization(本地化)代码。
if (Regex.IsMatch(productCode, "^[A-Za-z0-9]{12}$") == false)
throw new ArgumentException("Invalid product code" );
//(3) using 语句确保连接被断开
using (SqlConnection conn = new SqlConnection(GetConnectionString()))
{
// (4) 使用参数化存储过程可以应对
//     SQL 注入攻击
SqlCommand cmd = new SqlCommand("spCheckProduct", conn);
cmd.CommandType = CommandType.StoredProcedure;
// 对参数的类型进行检查
SqlParameter parm =
cmd.Parameters.Add("@ProductCode",
SqlDbType.VarChar,12);
parm.Value = productCode;
// 定义输出参数
SqlParameter retparm = cmd.Parameters.Add("@quantity", SqlDbType.Int);
retparm.Direction = ParameterDirection.Output;
conn.Open();
cmd.ExecuteNonQuery();
quantity = (int)retparm.Value;
}
}
catch (SqlException sqlex)
{
// (5) 记录完整的异常详细信息。一般(安全的)错误消息
//     基于 SQL 错误代码返回调用方
//     为清楚起见,省略了日志和错误标识代码
throw new Exception("Error Processing Request");
}
catch (Exception ex)
{
// 记录完整的异常详细信息
throw new Exception("Error Processing Request");
}
return quantity;
}
// (6) 将加密的数据库连接字符串存保留在注册表中
private static string GetConnectionString()
{
// 从注册表中检索密码文本;进程帐户必须
// 由注册表项的 ACL 授予“读取”访问权限
string encryptedString = (string)Registry.LocalMachine.OpenSubKey(
@"Software\OrderProcessing\")
.GetValue("ConnectionString");
// 使用托管的 DPAPI helper 库对该字符串进行解密
DataProtector dp = new DataProtector(DataProtector.Store.USE_MACHINE_STORE);
byte[] dataToDecrypt = Convert.FromBase64String(encryptedString);
return Encoding.ASCII.GetString(dp.Decrypt(dataToDecrypt,null));
}

上面的代码显示出下列安全特征(由注释行中的数字进行标识)。

1.

数据访问代码放在 try/catch 块中。这是防止在出现异常时将系统级信息返回到调用方所必需的。调用 ASP.NET Web 应用程序或 Web 服务会处理异常,并向客户端返回适当的一般错误消息,但是数据访问代码不依赖这些消息。

2.

使用正则表达式验证输入。检查所提供的产品 ID,以便验证它只包含 A–Z 和 0–9 范围内的字符,而且不超过 12 个字符。这是旨在防止 SQL 注入攻击的一组对策中的第一个。

3.

SqlConnection 对象是在 Microsoft Visual C#® using 语句的内部创建的。这可确保无论是否发生了异常,都断开方法内部的连接。这会降低拒绝服务攻击的威胁,该威胁尝试使用到数据库的所有可用连接。通过使用 finally 块可以实现类似的功能。

4.

参数化存储过程用于数据访问。这是防止 SQL 注入的另一个对策。

5.

不向客户端返回详细的错误信息。记录异常详细信息,以便帮助诊断问题。

6.

加密的数据库连接字符串存储在注册表中。存储数据库连接字符串最安全的方法之一是,使用 DPAPI 加密该字符串,并将加密的密码文本存储在具有受限 ACL 的受保护的注册表项中。(例如,使用“管理员:完全控制”和“ASP.NET 或企业服务进程帐户:读取”,具体情况取决于由哪个进程托管该组件。)

注意 该代码显示了如何从注册表检索连接字符串,然后使用托管的 DPAPI helper 库对其进行解密。此库在“Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication”的“How To”部分中的 How To: Create a DPAPI Library (英文)中提供。

代码访问安全性注意事项

所有的数据访问都遵循代码访问安全权限需求。您所选择的 ADO.NET 托管数据提供程序可以确定精确的要求。下表显示了对于每个 ADO.NET 数据提供程序必须授予数据访问程序集的权限。

表 14.1:ADO.NET 数据提供程序所需的代码访问安全权限

ADO.NET 数据提供程序 所需的代码访问安全权限

SQL Server

SqlClient
PermissionSupports 部分信任调用方(包括中等信任 Web 应用程序)。

OLE DB

OleDbPermission*

Oracle

OraclePermission*

ODBC

OdbcPermission*

*撰写本文时,在 1.0 和 1.1 版本的 .NET Framework上,OLE DB、Oracle 和 ODBC 提供程序只支持完全信任调用方。要从部分信任 Web 应用程序中使用这些提供程序,必须将您的数据访问代码放在沙盒中,这需要一个专门的数据访问程序集。有关显示如何将数据访问代码放在沙盒中以及如何从中等信任 Web 应用程序中使用 OLE DB 数据提供程序的示例,请参阅模块 9 ASP.NET 代码访问安全性。

如果您使用 ADO.NET SQL Server 数据提供程序,您的代码就必须由代码访问安全策略授予 SqlClientPermission。完全和中等信任 Web 应用程序具有此权限。

代码能否连接到 SQL Server 由代码是否被授予了 SqlClientPermission 来确定。还可以使用权限对数据库连接字符串的使用进行限制。例如,可以强制应用程序使用集成安全性,或者可以确保在使用 SQL Server 安全性时不接受空白密码。如果违反通过 SqlClientPermission 指定的规则,将会导致运行时安全异常。

有关如何使用 SqlClientPermission 来限制数据访问的详细信息,请参阅模块 8 代码访问安全的实践中的“数据访问”。

部署注意事项

以安全方式设计和开发的数据访问组件如果不以安全的方式进行部署,仍然容易受到攻击。常见的部署做法是使数据访问代码和数据库驻留在单独的服务器上。这些服务器通常由内部防火墙隔开,这就引进了额外的部署注意事项。开发人员和管理员应该了解下列问题:

防火墙限制

连接字符串管理

登录帐户配置

登录审核

网络上的数据私密性和完整性

防火墙限制

如果您通过防火墙连接到 SQL Server,请配置防火墙、客户端和服务器。可通过使用 SQL Server 客户端网络实用程序来配置客户端,并使用服务器网络实用程序配置数据库服务器。在默认情况下,SQL Server 侦听 TCP 端口 1433,但您可以更改此设置。必须在防火墙上打开所选端口。

根据您所选择的 SQL Server 身份验证模式以及应用程序对分布式事务的使用方式,您可能需要在防火墙上打开几个其他端口:

如果应用程序使用 Windows 身份验证连接到 SQL Server,则必须打开支持 Kerberos 或 NTLM 身份验证所必需的端口。

对于不使用 Active Directory 的网络,TCP 端口 139 通常是 Windows 身份验证所必需的。有关端口要求的详细信息,请参阅 TechNet 文章“TCP and UDP Port Assignments”和“Security Considerations for Administrative Authority”,前者的网址为:http://www.microsoft.com/technet/prodtechnol/windows2000serv/reskit/tcpip/part4/tcpappc.asp(英文),后者的网址为:http://www.microsoft.com/technet/security/bestprac/bpent/sec2/seconaa.asp(英文)

如果您的应用程序使用分布式事务(例如,自动化 COM+ 事务),您可能还需要对防火墙进行配置,以便允许 DTC 通信在单独的 DTC 实例之间以及 DTC 和资源管理器(如 SQL Server)之间流动。

有关完整配置的详细信息,请参阅模块 18 保证数据库服务器的安全中的“端口”部分。

连接字符串管理

许多应用程序都将连接字符串存储在代码中,这主要是为了提高性能。但是,性能优势是可以忽略的,而且使用文件系统缓存有助于确保外部文件中的连接字符串能够提供相当的性能。使用外部文件存储连接字符串对于系统管理极其有益。

为了增加安全性,建议使用 DPAPI 来加密连接字符串。如果您的连接字符串包含用户名和密码,这一点尤为重要。然后,确定在何处存储加密的字符串。注册表是安全的存储位置,特别是在您使用 HKEY_CURRENT_USER 时,因为只有在相关用户帐户下运行的进程才能进行访问。为了使部署更加容易,还可以将加密的字符串存储在 Web.config 文件中。这两种方法已在本模块前面的配置管理部分讨论过。

登录帐户配置

一定要让您的应用程序使用最小特权帐户来连接到数据库,这是降低 SQL 注入攻击威胁的主要方法之一。

作为开发人员,您必须与数据库管理员进行协商,以确定应用程序登录需要访问的确切的存储过程和(可能的)表。在理想情况下,您应该只允许应用程序登录对随应用程序一起部署的一组有限的存储过程具有执行权限。

应该对 SQL 或 Windows 帐户、或应用程序连接到数据库所使用的帐户使用强密码。

有关针对数据库中的应用程序帐户的建议授权策略,请参阅本模块前面的授权部分。

登录审核

应该将 SQL Server 配置为记录失败的登录尝试和可能成功的登录尝试。审核失败的登录尝试有助于检测到尝试发现帐户密码的攻击者。

有关如何配置 SQL Server 审核的详细信息,请参阅模块 18 保证数据库服务器的安全。

网络上的数据私密性和完整性

如果您使用 SQL 身份验证连接到 SQL Server,请确保不通过网络暴露登录凭据。可以在数据库服务器上安装一个证书(这会导致 SQL Server 加密凭据),或者使用数据库的 IPSec 加密通道。

建议使用数据库的 IPSec 或 SSL 通道来保护传入和传出数据库的敏感的应用程序级数据。有关详细信息,请参阅模块 18 保证数据库服务器的安全。

小结

本模块显示了数据访问代码的几种主要威胁,并重点介绍了常见的漏洞。SQL 注入是应该注意的主要威胁之一。除非您使用本模块中讨论的正确对策,否则攻击者会利用您的数据访问代码在数据库中运行任意命令。传统的安全措施(如防火墙和 SSL)对 SQL 注入攻击不提供任何防御功能。您应该彻底验证自己的输入内容,并将参数化存储过程用作最基本的防御措施。

其他资源

有关详细信息,请参阅下列资源:

有关可打印的检查表,请参阅本指南“检查表”部分中的检查表:保护数据访问。

有关保护开发人员工作站的详细信息,请参阅本指南“如何”部分中的如何:保护开发人员工作站。

有关结合使用 SSL 和 SQL Server 的详细信息,请参阅“Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication”的“How To”部分中的“How To: Use SSL to Secure Communication with SQL Server 2000”,其网址为:http://msdn.microsoft.com/library/en-us/dnnetsec/html/SecNetHT19.asp(英文)。

有关使用 IPSec 的详细信息,请参阅“Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication”的“How To”部分中的“How To: Use IPSec to Provide Secure Communication Between Two Servers”,其网址为:http://msdn.microsoft.com/library/en-us/dnnetsec/html/SecNetHT18.asp(英文)。

有关使用 DPAPI 的详细信息,请参阅“Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication”的“How To”部分中的“How To: Create a DPAPI Library”,其网址为:http://msdn.microsoft.com/library/en-us/dnnetsec/html/SecNetHT07.asp(英文)。