正文

第 12 章 – 数据访问安全性2005-12-29 19:26:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/iamben250/9046.html

分享到:

第 12 章 – 数据访问安全性

更新日期: 2004-4-20 0:00:00
本页内容
本章内容 本章内容
目标 目标
适用范围 适用范围
如何使用本章内容 如何使用本章内容
数据访问安全性简介 数据访问安全性简介
身份验证 身份验证
授权 授权
安全通信 安全通信
用最少权限连接 用最少权限连接
创建一个权限最少的数据库帐户 创建一个权限最少的数据库帐户
安全存储数据库连接字符串 安全存储数据库连接字符串
为数据库验证用户身份 为数据库验证用户身份
SQL 注入式攻击 SQL 注入式攻击
审核 审核
SQL Server 的进程标识 SQL Server 的进程标识
总结 总结

本章内容

数据存储(如 Microsoft SQL Server 2000)在大多数分布式 Web 应用程序中均扮演着重要的角色。这种数据存储可以包含所有类型的数据,包括用户应用程序首选项、机密的人事记录与医疗记录、审核日志与安全日志,甚至还包括用户访问应用程序时所需的凭据。显而易见,在存储这类数据以及对其执行读/写操作时都需要保证它们的安全,以确保只有具备相应授权权限的用户才能对其进行访问。

本章将介绍与访问数据存储相关的重要安全问题的解决方案,并提供从分布式 Web 应用程序访问数据的一些建议。本章主要侧重于 SQL Server 2000,但其中的许多主题也同样适用于其他数据存储。

返回页首返回页首

目标

本章的目标是:

生成安全数据访问组件。

使用 Windows 身份验证或 SQL Server 身份验证从 ASP.NET 连接到 SQL Server。

选择一种适于您的应用程序的 SQL Server 2000 授权机制。

使用 SSL 或 IPSec 来确保客户端应用程序与数据源之间数据传输的安全性。

创建一个权限最少的数据库帐户,以供 SQL Server 2000 使用。

安全存储数据库连接字符串和凭据。

使用密码哈希值和 Salt 值在用于身份验证的数据库中安全存储用户凭据。

保护 SQL 数据库,以防 SQL 注入式攻击。

在 SQL Server 2000 上启用审核。

返回页首返回页首

适用范围

本章适用于以下产品和技术:

Windows XP 或 Windows 2000 Server (Service Pack 3) 和更高版本的操作系统

.NET Framework 版本 1.0 (Service Pack 2) 和更高版本

Visual C# .NET

SQL Server 2000 (Service Pack 2) 和更高版本

返回页首返回页首

如何使用本章内容

若要学好本章内容:

您必须具有使用 Visual C# .NET 进行编程的经验。

您必须具有开发和配置 ASP.NET Web 应用程序的经验。

您必须具有配置 SQL Server 2000 安全性的经验。

您必须具有配置 Windows 安全性和使用 Windows 管理工具创建用户帐户的经验。

阅读第 1 章简介。这一章说明了身份验证、授权和安全通信对于分布式 Web 应用程序的重要性。

阅读第 2 章 ASP.NET 应用程序的安全模型。这一章简要介绍了创建分布式 ASP.NET Web 应用程序所采用的体系结构和技术,并重点说明了身份验证、授权和安全通信在此体系结构中的作用。

阅读第 3 章身份验证和授权。这一章提供了有关模拟、受信任的子系统和 SQL Server 2000 角色的信息,本章将进一步讨论这些主题。

阅读第 4 章安全通信。这一章介绍了 SSL 和 IPSec,您可以使用它们来确保数据存储与其客户端应用程序之间通信通道的安全性。

阅读第 8 章 ASP.NET 安全性。这一章深入讨论了与 ASP.NET 相关的安全性问题,其中特别强调了进程标识、调用方标识和模拟的问题,这些问题会影响 ASP.NET 应用程序对数据存储进行身份验证时可用的选择。

请阅读以下章节,其中介绍了如何实现本章所述的许多技术:

如何创建自定义帐户来运行 ASP.NET

如何使用 IPSec 在两个服务器之间进行安全通信

如何使用 SSL 来确保与 SQL Server 2000 安全通信

如何创建 DPAPI 库

如何在 ASP.NET 中使用 DPAPI(计算机存储)

如何在具有企业服务的 ASP.NET 中使用 DPAPI(用户存储)

如何在注册表中存储加密的连接字符串

如何将窗体身份验证用于 SQL Server 2000

返回页首返回页首

数据访问安全性简介

图 12.1 显示了与数据访问相关的重要安全性问题。

重要的数据访问安全性问题

图 12.1
重要的数据访问安全性问题

下面概括了图 12.1 中所示的重要问题(本章的其余部分将详细讨论这些问题):

1.

安全存储数据库连接字符串。如果您的应用程序使用 SQL 身份验证连接到 SQL Server 或者连接到需要显式登录凭据的非 Microsoft 数据库,则这一点尤其重要。在这些情况下,连接字符串包括明文形式的用户名和密码。

2.

使用正确的标识来访问数据库。您可以通过使用调用进程的进程标识、一个或多个服务标识或者原调用方的标识(使用模拟/委派)来执行数据访问。标识的选择根据数据访问模型是受信任的子系统还是模拟/委派来确定。

3.

确保通过网络传输的数据的安全。例如,保护登录凭据以及传入和传出 SQL Server 的机密数据。

注意:只有当您使用 SQL 身份验证而非 Windows 身份验证时,才能在网络上公开登录凭据。

SQL Server 2000 利用服务器证书支持 SSL。IPSec 还可用于对客户端计算机(如 Web 或应用程序服务器)与数据库服务器之间的通信进行加密。

4.

对数据库调用方进行身份验证。SQL Server 支持 Windows 身份验证(使用 NTLM 或 Kerberos)和 SQL 身份验证(使用 SQL Server 的内置身份验证机制)。

5.

对数据库调用方进行授权。权限与各个数据库对象相关联。权限可以与用户、组或角色相关联。

SQL Server 网关守卫

图 12.2 重点说明了 SQL Server 数据访问的重要网关守卫。

SQL Server 网关守卫

图 12.2
SQL Server 网关守卫

重要网关守卫包括:

所选的用于保存数据库连接字符串的数据存储。

SQL Server 登录(由连接字符串中指定的服务器名确定)。

数据库用户(SQL Server 登录映射到的数据库内用户上下文)及关联的数据库角色。

附加到各个数据库对象的权限。
权限可分配给用户、组或角色。

受信任的子系统与模拟/委派

数据库访问权限的细化程度是一个需要考虑的关键因素。您必须考虑您是需要在数据库一端进行用户级授权(这需要模拟/委派模型),还是可以在应用程序中间层使用应用角色逻辑向用户授权。后者需要受信任的子系统模型。

如果数据库要求用户级授权,那么您需要模拟原调用方。虽然支持这种模拟/委派模型,但建议您使用受信任的子系统模型;在该模型中,会在 IIS/ASP.NET 入口对原调用方进行检查,将它映射到某个角色,然后根据角色成员身份向它授权。然后,以应用程序或角色级别使用服务帐户或使用应用程序的进程标识(如 ASPNET 帐户)向应用程序的系统资源授权。

图 12.3 显示了这两种模型。

数据库访问的受信任的子系统模型和模拟/委派模型

图 12.3
数据库访问的受信任的子系统模型和模拟/委派模型

当连接到 SQL Server 进行数据访问时,有许多应该考虑的重要因素。下面概括了这些因素(后面各节中将详细阐述):

应当使用什么类型的身份验证?虽然 Windows 身份验证提供增强的安全性,但防火墙和非信任域问题可能会迫使您使用 SQL 身份验证。如果是这种情况,您应该确保应用程序尽可能安全使用 SQL 身份验证,该主题将在本章后面的“SQL 身份验证”一节中讨论。

单用户角色与多用户角色。根据应用程序的用户,您的应用程序是需要使用一个在数据库内拥有一组固定权限的帐户访问 SQL,还是需要多个(基于角色的)帐户?

调用方标识。数据库需要通过调用上下文来接收原调用方的标识才能执行授权或审核,还是您能以应用程序级别使用一个或多个受信任的连接并传递原调用方标识?
对于要传递原调用方的标识的操作系统,需要在中间层使用模拟/委派。这样会大大降低连接池的效率。虽然连接池仍处于启用状态,但它会产生许多小池(用于每个单独的安全性上下文),几乎不重用任何连接。

您是否正在向数据库服务器发送或从数据库服务器发送机密数据?虽然 Windows 身份验证可以让您不必通过网络向数据库服务器传递用户凭据,但是,如果您的应用程序数据属于机密数据(例如,雇员详细资料或工资表数据),就应该使用 IPSec 或 SSL 予以保护。

返回页首返回页首

身份验证

本节将讨论在连接到 SQL Server 之前应如何对 SQL Server 的客户端进行身份验证,以及应如何在客户端应用程序中选择用于数据库访问的标识。

Windows 身份验证

Windows 身份验证比 SQL 身份验证更安全,原因是前者有以下优点:

为您管理凭据,并且不通过网络传输凭据。

您可以避免在连接字符串中嵌入用户名和密码。

通过设置密码到期期限、最小长度以及在多次无效登录请求后锁定帐户提高登录安全性。这样可以减轻字典攻击带来的威胁。

在下列情况下应使用 Windows 身份验证:

您使用了受信任的子系统模型并且使用一个固定标识连接到 SQL Server。如果您从 ASP.NET 进行连接,这就意味着 Web 应用程序没有为模拟进行配置。
在这种情况下,请使用 ASP.NET 进程标识或服务组件标识(从用于运行企业服务服务器应用程序的帐户获得)。

您打算通过使用委派来委派原调用方的安全性上下文(并准备通过放弃数据库连接池来牺牲应用程序的可伸缩性)。

当您使用 Windows 身份验证连接到 SQL Server 时,请考虑以下要点:

使用 ASP.NET 进程帐户的最少权限原则。避免向 ASP.NET 进程帐户授予“作为操作系统的一部分”权限来启用 LogonUser API 调用。

确定哪些代码需要额外的权限,然后将这些代码放在进程外企业服务应用程序中运行的服务组件内。

更多信息

有关从 ASP.NET 访问网络资源以及选择和配置适当的帐户来运行 ASP.NET 的详细信息,请参见第 8 章 ASP.NET 安全性。

使用 Windows 身份验证

使用 Windows 身份验证从 ASP.NET 应用程序(或 Web 服务,或者 ASP.NET 托管的远程组件)连接到 SQL Server 时,有以下选择:

使用 ASP.NET 进程标识。

使用 ASP.NET 中的固定标识。

使用服务组件。

使用 LogonUser API 并模拟特定标识。

使用原调用方的标识。

使用匿名 Internet 用户帐户。

建议

建议通过在 Web 服务器上将密码更改为某个已知值来配置本地 ASP.NET 进程标识,然后在数据库服务器上通过创建具有相同用户名和密码的本地用户来创建镜像帐户。下面详细介绍这一过程及其他方法。

使用 ASP.NET 进程标识

如果您直接从 ASP.NET 应用程序(或 Web 服务,或者是 ASP.NET 托管的远程组件)连接到 SQL Server,请使用 ASP.NET 进程标识。这是一种常用的方法,并且应用程序定义了信任边界,也就是说,数据库信任 ASP.NET 帐户访问数据库对象。

您有三种选择:

使用镜像的 ASPNET 本地帐户。

使用镜像的自定义本地帐户。

使用自定义域帐户。

使用镜像的 ASPNET 本地帐户

这是一种最简单的方法,通常在您拥有目标数据库(并且可以控制本地数据库服务器帐户的管理)时使用。如果选择此方法,您将使用 ASPNET 最少权限的本地帐户来运行 ASP.NET,然后在数据库服务器上创建一个重复的帐户。

注意:这种方法还有一个优点,它可以跨非信任域、通过防火墙发挥作用。防火墙打开的端口数可能不足以支持 Windows 身份验证。

使用镜像的自定义本地帐户

此方法与前面的方法基本相同,只是不使用默认的 ASPNET 帐户。这有两方面的含义:

您需要创建一个有适当权限和特权的自定义本地帐户。
有关详细信息,请参见本指南中如何创建自定义帐户来运行 ASP.NET 一章的内容。

您不再使用 .NET Framework 安装进程创建的默认帐户。您的公司可能有不允许使用默认安装帐户的策略。这种策略可能会提高应用程序对安全性的要求。
有关详细信息,请参见 Sans Top 20:“G2 – Accounts with No Passwords or Weak Passwords”(G2 – 没有密码或只有弱密码的帐户)(http://www.sans.org/top20.htm)。

使用自定义域帐户

这种方法与前一方法相似,不同的是,您使用最少权限的域帐户而不是本地帐户。这种方法假设客户端计算机和服务器计算机都位于同一个域或信任域内。它的主要优点是:不在计算机之间共享凭据,计算机只提供对域帐户的访问。而且,域帐户的管理也更容易。

实现镜像的 ASPNET 进程标识

为了使用镜像的帐户从 ASP.NET 连接到数据库,您需要执行以下操作:

使用 Web 服务器上的“用户管理器”将 ASPNET 帐户的密码重置为一个已知的强密码值。

重要说明:如果您将 ASPNET 密码更改为已知值,则本地计算机上的“本地安全机构”(LSA) 中的密码将不再与 Windows 安全帐户管理器 (SAM) 数据库中存储的帐户密码匹配。如果您需要恢复 AutoGenerate 默认值,必须执行以下操作:

运行 Aspnet_regiis.exe,将 ASP.NET 重置为它的默认配置。有关详细信息,请参见 Microsoft 知识库文章 Q306005:HOWTO: Repair IIS Mapping After You Remove and Reinstall IIS(HOWTO:删除和重新安装 IIS 之后修复 IIS 映射)。完成重置后,您会获得新的帐户和新的 Windows 安全标识符 (SID)。该帐户的权限设置为其默认值。因此,您需要显式重新应用原来为旧的 ASPNET 帐户设置的权限和特权。

在 Machine.config 中显式设置密码。使用 aspnet_setreg.exe 实用程序来加密受保护的注册表项中的 <processModel> 凭据。有关详细信息,请参见知识库文章 Q329290:HOWTO: Use the ASP.NET Utility to Encrypt Credentials and Session State Connection Strings(HOWTO:使用 ASP.NET 工具加密凭据和会话状态连接字符串)。

<processModel
       userName="registry:HKLM\SOFTWARE\YourSecureApp\
                 processModel\ASPNET_SETREG,userName"
       password="registry:HKLM\SOFTWARE\YourSecureApp\
                 processModel\ASPNET_SETREG,password" . . . />
   

您应该使用 Windows ACL 来防止 machine.config 受到未经授权的访问。例如,限制 IIS 匿名 Internet 用户帐户访问 Machine.config。

在数据库服务器上创建镜像帐户(具有相同的用户名和密码)。

在 SQL 数据库中,为本地 ASPNET 帐户创建服务器登录,再将该登录映射到所需数据库中的一个数据库用户。然后创建一个数据库用户角色,将数据库用户添加到该角色,并为该角色配置适当的数据库权限。
有关详细信息,请参见本章后面的创建一个权限最少的数据库帐户

使用 Windows 身份验证连接到 SQL Server

若要使用 Windows 身份验证连接到 SQL Server,请执行以下操作:

在客户端应用程序中,使用包含“Trusted_Connection=Yes”或“Integrated Security=SSPI”的连接字符串。这两个字符串是等效的,都会进行 Windows 身份验证(假定 SQL Server 已为 Windows 身份验证进行了配置)。例如:

SqlConnection conn = new SqlConnection(
          "server=YourServer; database=YourDatabase;" +
          "Trusted_Connection=Yes;");

-或-

SqlConnection conn = new SqlConnection(
          "server=YourServer; database=YourDatabase;" +
          "Integrated Security=SSPI;");

注意:发出请求的客户端(即由 SQL Server 进行身份验证的客户端)的标识由该客户端的线程模拟令牌(如果该线程当前正在进行模拟)或该客户端的当前进程令牌来确定。

使用 ASP.NET 内的固定标识

通过此方法,您可以使用 Web.config 中的 <identity> 元素将 ASP.NET 应用程序配置为模拟指定的固定标识。下面的示例显示了 <identity> 元素,在该元素中,使用 aspnet_setreg.exe 在注册表中对凭据进行加密,第 8 章 ASP.NET 安全性对此进行了介绍。

<identity
   impersonate="true"
   userName="registry:HKLM\SOFTWARE\YourSecureApp\
             identity\ASPNET_SETREG,userName"
   password="registry:HKLM\SOFTWARE\YourSecureApp\
             identity\ASPNET_SETREG,password" />

这成为您在连接到网络资源(包括数据库)时使用的默认标识。

在 Windows 2000 服务器上使用 .NET Framework 1.0 时,建议不要使用固定模拟标识。这是因为您需要授予 ASP.NET 进程帐户“作为操作系统的一部分”高权限。ASP.NET 进程需要此权限,因为它使用您提供的凭据执行 LogonUser 调用。

注意:Microsoft Windows .NET Server 2003 不需要“作为操作系统的一部分”权限来进行 LogonUser 调用。

而且,.NET Framework 1.1 会在 Windows 2000 上提供这个方案的增强功能。登录将由 IIS 进程执行,这样一来,ASP.NET 就不需要“作为操作系统的一部分”权限。

有关这一高权限的详细信息,请参见 Microsoft Systems Journal(Microsoft 系统期刊)1999 年 8 月期的“Security Briefs”(安全摘要)专栏 (http://www.microsoft.com/msj/0899/security/security0899.aspx)。

.NET Framework 1.1 会在 Windows 2000 上提供这个方案的增强功能。登录将由 IIS 进程执行,这样一来,ASP.NET 就不需要“作为操作系统的一部分”权限。

使用服务组件

您可以开发专门包含数据访问代码的服务组件。利用服务组件,您可以通过在使用特定标识运行的企业服务 (COM+) 服务器应用程序中托管您的组件来访问数据库,也可以编写使用 LogonUser API 的代码来执行模拟。

使用进程外的服务组件可以提高对安全性的要求,因为进程跳跃使攻击者更难以得手,特别是当进程以不同的标识运行时更是如此。另一个优点是,您可以将需要更多权限的代码与应用程序的其余部分隔开。

调用 LogonUser 并模拟特定的 Windows 标识

您不应直接从 ASP.NET 调用 LogonUser。在 Windows 2000 中,这种方法需要您向 ASP.NET 进程标识授予“作为操作系统的一部分”权限。

首选的方法是使用企业服务服务器应用程序中的服务组件在 ASP.NET 进程外调用 LogonUser,如上所述。

使用原调用方的标识

若要使用这种方法,您需要直接从 ASP.NET 或服务组件使用 Kerberos 委派并模拟数据库的调用方。

在 ASP.NET 中,将以下代码添加到应用程序的 Web.config 文件中:

<identity impersonate="true" />

从服务组件调用 CoImpersonateClient。有关使用 CoImpersonateClient 的详细信息,请参见第 9 章企业服务安全性。

使用匿名 Internet 用户帐户

作为前一方法的一种变化形式,对于应用程序使用窗体或 Passport 身份验证(即 IIS 匿名身份验证)的情形,您可以在应用程序的 web.config 中启用模拟,以便使用匿名 Internet 用户帐户来访问数据库。

<identity impersonate="true" />

对 IIS 进行了匿名身份验证的配置后,此配置会导致您的 Web 应用程序代码使用匿名 Internet 用户的模拟令牌运行。在 Web 托管环境中,这种方法的优点是允许您分别从多个 Web 应用程序审核和跟踪数据库访问。

更多信息

有关使用原调用方的标识的详细信息和实现细节,请参见第 5 章 Intranet 安全性中将原调用方传递到数据库一节。

有关如何将 IIS 配置为使用匿名用户帐户的详细信息,请参见第 8 章 ASP.NET 安全性。

何时不能使用 Windows 身份验证?

某些应用程序方案可能禁止使用 Windows 身份验证。例如:

您的数据库客户端和数据库服务器被一个禁止使用 Windows 身份验证的防火墙隔开。

您的应用程序需要使用多个标识来连接到一个或多个数据库。

您要连接到非 SQL Server 数据库。

您在 ASP.NET 中没有一种安全方式来以特定的 Windows 用户身份运行代码。或者您无法(或不想)传递原调用方的安全性上下文,或者您想使用专用的服务帐户(而不是向最终用户授予登录权限),或者同时存在以上两种事实。

在这些方案中,您需要使用 SQL 身份验证(或数据库的本机身份验证机制),并且必须:

在应用程序服务器上保护数据库用户凭据。

在从服务器传输到数据库的过程中保护数据库用户凭据。

如果您使用 SQL 身份验证,则有多种方法可以确保 SQL 身份验证更加安全。这些方法将在下一节详细介绍。

SQL 身份验证

如果您的应用程序需要使用 SQL 身份验证,则需要考虑以下要点:

使用权限最少的帐户连接到 SQL。

凭据通过网络传递,因此必须对其进行保护。

必须保护 SQL 连接字符串(它包含凭据)。

连接字符串类型

如果您使用凭据(用户名和密码)来连接到 SQL Server 数据库,则连接字符串如下所示:

使用 SQL Server .NET 数据提供程序:
SqlConnection conn = new SqlConnection(
          "server=YourServer; uid=YourUserName; pwd=YourStrongPwd;" +
          "database=YourDatabase");

使用 OLE DB .NET 数据提供程序:
OleDbConnection conn = new OleDbConnection(
          "Provider=SQLOLEDB; Data Source=YourServer;" +
          "uid=YourUserName; pwd=YourStrongPwd; Initial Catalog=YourDatabase");

如果您需要连接到安装在同一计算机上的 SQL Server 的特定实例(只在 SQL Server 2000 或更高版本中提供的功能),则连接字符串如下所示:

使用 SQL Server .NET 数据提供程序:
SqlConnection conn = new SqlConnection(
          "server=YourServer\Instance; uid=YourUserName; pwd=YourStrongPwd;" +
          "database=YourDatabase");

如果您通过使用显式凭据(用户名和密码)连接到 Oracle 数据库,则连接字符串如下所示:

OleDbConnection conn = new OleDbConnection(
          "Provider=MSDAORA; Data Source=YourDatabaseAlias;" +
          "User ID=YourUserName; Password=YourStrongPwd;");

更多信息

有关在连接中使用通用数据链接 (UDL) 文件的详细信息,请参见 Microsoft 知识库文章 Q308426 HOW TO: Use Data Link Files with the OleDbConnection Object in Visual C# .NET(HOW TO:在 Visual C# .NET 中将数据链接文件用于 OleDbConnection 对象)。

选择用于连接的 SQL 帐户

不要将内置的 sa 帐户或作为 SQL Server sysadmin 固定服务器角色或 db_owner 固定数据库角色的成员的任何帐户用于数据访问。sysadmin 的成员可以在 SQL Server 中执行任何活动。db_owner 的成员在数据库中的权限不受限制。应该使用具有强密码的最少权限帐户。

避免使用以下连接字符串:

SqlConnectionString = "Server=YourServer\Instance;
                       Database=YourDatabase; uid=sa; pwd=;"

使用具有最少权限的、采用强密码的帐户,例如:

SqlConnectionString= "Server=YourServer\Instance;
                      Database=YourDatabase;
                      uid=YourStrongAccount; 
                      pwd=YourStrongPassword;"

请注意,这并没有解决在 Web.config 文件中以明文形式存储凭据的问题。到目前为止,您所做的一切是利用具有最少权限的帐户来限制在发生危害时可能造成的损失的范围。若要进一步提高安全性要求,您应该对凭据进行加密。

注意:如果您在安装 SQL Server 时选择了区分大小写的排序顺序,那么您的登录 ID 也将区分大小写。

在网络上传递凭据

当您连接到使用 SQL 身份验证的 SQL Server 时,用户名和密码在网络上以明文形式传递。这可能会带来严重的安全隐患。有关如何保护应用程序或 Web 服务器与数据库服务器之间的通道的详细信息,请参见本章后面的安全通信

保护 SQL 连接字符串

用户名和密码不应以明文形式存储在配置文件中。有关如何安全存储连接字符串的详细信息,请参见本章后面的安全存储数据库连接字符串

对非 SQL Server 数据库进行身份验证

您在连接到非 SQL 数据库时可能遇到的典型问题与您需要使用 SQL 身份验证时的情形很相似。如果目标资源不支持 Windows 身份验证,则可能需要提供显式凭据。若要确保这种方案的安全性,您必须安全存储连接字符串,此外,还必须保护网络上的通信(防止凭据被截取)。

返回页首返回页首

授权

SQL Server 提供了许多基于角色的授权方法。这些方法都围绕 SQL Server 支持的以下三种角色:

用户定义的数据库角色。这些角色用于将数据库中拥有相同安全权限的用户组合成组。您可以创建 SQL Server 登录,并将它们映射到特定的数据库用户。然后再将数据库用户添加到数据库角色,并使用这些角色来确定各个数据库对象(存储过程、表、视图等)的权限。

应用程序角色。这些角色与用户数据库角色的相似之处是:在创建对象权限时需要使用它们。但是,与用户数据库角色不同的是:它们不包含用户或组,而是必须由一个使用内置存储过程的应用程序激活。激活后,授予角色的权限决定了应用程序的数据访问能力。
应用程序角色允许数据库管理员向指定的数据库对象授予访问所选应用程序的权限。这与向用户授予权限不同。

固定的数据库角色。SQL Server 还提供了固定的服务器角色,例如 db_datareaderdb_datawriter。这些内置的角色存在于所有数据库中,并可用于将数据库中一组特定的(及其他常用的)读取权限快速授予某个用户。

有关这些不同角色类型(以及与固定的数据库角色相似但在服务器级别而非数据库级别上应用的固定服务器角色)的详细信息,请参见“SQL Server 联机丛书”(http://www.microsoft.com/sql/techinfo/productdoc/2000/books.asp)。

使用多个数据库角色

如果您的应用程序有多个用户类别,而同一类别内的用户需要数据库中的相同权限,那么您的应用程序就需要多个角色。各个角色分别需要数据库中不同的一组权限。例如,Internet 用户角色的成员需要对数据库中的大多数表具有只读权限,而管理员或操作员角色的成员则可能需要读/写权限。

在这种情况下,您可以使用多个用户定义的 SQL Server 数据库角色。这些角色用于为那些在数据库中拥有相同权限的用户组分配对数据库对象的权限。使用此方法时,您必须:

创建多个服务帐户,以用于数据库访问。

为每个帐户创建 SQL Server 登录。

创建数据库用户,以授予对数据库的登录访问权限。

将每个数据库用户添加到用户定义的数据库角色。

为数据库中的每个角色授予必要的数据库权限。

在应用程序(ASP.NET Web 应用程序、Web 服务或中间层组件)中向用户授权,然后使用数据访问层中的应用逻辑来确定使用哪个帐户连接到数据库。这基于调用方的角色成员身份。
您可以用声明方式将各个方法配置为只允许那些属于一组角色的用户使用。然后,在方法代码中添加强制角色检查,以确定正确的角色成员身份(该成员身份决定了要使用的连接)。

图 12.4 说明了这种方式。

使用多个 SQL 用户数据库角色连接到 SQL Server

图 12.4
使用多个 SQL 用户数据库角色连接到 SQL Server

若要将首选的 Windows 身份验证用于此方案,您可以在进程外的服务组件中开发代码(使用 LogonUser API),以模拟一组 Windows 标识中的某个标识。

对于 SQL 身份验证,您应根据应用程序中基于角色的逻辑来使用其他的连接字符串(包含不同的用户名和密码)。

返回页首返回页首

安全通信

在大多数应用程序方案中,您需要保证应用程序服务器与数据库之间通信链路的安全。您必须能够保证:

消息机密性。必须对数据进行加密,确保其保密性。

消息完整性。必须为数据签名,确保其不被篡改。

有些方案需要保护在应用程序服务器与数据库服务器之间传递的所有数据;而有些方案则需要保护通过特定连接发送的所选数据项。例如:

在 Intranet 人力资源应用中,在客户端与数据库服务器之间传递的某些雇员资料属于机密数据。

在 Internet 应用(如安全银行业务应用程序)中,在应用程序服务器与数据库服务器之间传递的所有数据都必须得到保护。

如果您使用的是 SQL 身份验证,还应保护通信链路的安全,确保用户名和密码不会被网络监视软件泄露。

选项

有两个选项可以确保应用程序服务器与数据库服务器之间网络链路的安全性:

IPSec

SSL(在 SQL Server 计算机上使用服务器证书)

注意:您必须运行 SQL Server 2000 才能支持使用 SSL。早期的版本不支持 SSL。客户端必须已经安装了 SQL Server 2000 客户端库。

选择一种方法

是否应使用 IPSec 或 SSL 取决于许多重要的环境因素,比如防火墙注意事项、操作系统和数据库版本等。

注意:IPSec 的目的不是取代应用程序级安全。现在,它用作一种深层防御机制,或用于在不更改应用程序的情况下保护不安全的应用程序,以及保护非 TLS(例如 SSL)协议免受网络线路的攻击。

更多信息

有关配置 IPSec 的详细信息,请参见本指南中的如何使用 IPSec 在两个服务器之间进行安全通信。

有关配置 SSL 的详细信息,请参见本指南中的如何使用 SSL 来确保与 SQL Server 2000 安全通信。

有关 SSL 和 IPSec 的更多一般性信息,请参见第 4 章安全通信。

返回页首返回页首

用最少权限连接

用最少权限连接数据库意味着,您建立的连接在数据库中仅拥有您所需的最少权限。简单地说,您不使用 sa 帐户、sysadmin 角色的成员或 db_owner 角色的成员来连接数据库。理想状态下,如果未授权当前用户添加或更新记录,则用于其连接的相应帐户(该帐户可以合成为代表特定角色的某个标识)就无法在数据库中添加或更新记录。

当您连接到 SQL Server 时,您使用的方式必须支持数据库授权所需的权限细化程度。您需要考虑数据库信任的内容。它可以信任:

应用程序。

应用程序定义的角色。

原调用方。

数据库信任应用程序

我们以一个您授权其使用数据库的财务应用程序为例。财务应用程序负责管理用户身份验证并授予访问权限。在这种情况下,您可以通过单个受信任的帐户(该帐户对应于 SQL 登录或映射到 SQL 登录的 Windows 帐户)来管理连接。如果您使用的是 Windows 身份验证,这通常意味着允许调用应用程序的进程标识(如 ASP.NET 辅助进程或企业服务服务器应用程序标识)来访问数据库。

从授权的角度来看,这种方法很粗略,因为运行连接所用的标识对应用程序所需的所有数据库对象和资源都具有访问权限。这种方法的优点是:您可以使用连接池,并且简化了管理,因为您只向单个帐户授权。缺点是所有用户都以相同的连接权限运行。

数据库信任不同的角色

您可以使用独立的连接所组成的连接池来连接数据库,这些连接与您的应用程序定义的角色相对应,例如,一个连接供出纳员使用,而另一个供经理使用等。

这些连接可以使用 Windows 身份验证,也可以不用。Windows 身份验证的优点是:它可以处理凭据管理,并且不在网络上发送凭据。不过,虽然 Windows 身份验证可以在进程级别或应用程序级别上使用,但如果您需要多个标识(每个角色一个标识),就会出现其他问题。

使用 LogonUser API 创建线程级别的 Windows 访问令牌来建立安全性上下文的应用程序面临着凭据管理问题,因为应用程序必须安全存储帐户用户名和密码。这类应用程序还面临着权限问题,因为必须为进程帐户授予“作为操作系统的一部分”高权限。

使用 SQL 身份验证的应用程序也面临着凭据管理问题,此外,它还需要一个到数据库的安全通信通道来保护凭据。

数据库信任原调用方

在这种情况下,您需要将原调用方通过多个层传送到数据库。这意味着客户端需要网络凭据,以便从一台计算机跳到另一台计算机。这需要 Kerberos 委派。

虽然此解决方案在数据库内提供细化程度的授权,但由于您知道原调用方的标识,并能够对数据库对象建立每用户权限,因此它会影响应用程序的性能和可伸缩性。虽然连接池仍处于启用状态,但效率很低。

返回页首返回页首

创建一个权限最少的数据库帐户

下面的步骤是一个简单示例,向您说明如何创建具有最少权限的数据库帐户。虽然大多数数据库管理员都很熟悉这些步骤,但许多开发人员可能还不太了解这些,而是使用 sa 帐户来强制运行其应用程序。

在从开发环境转移到测试环境、然后再进入生产环境时,这可能会出现问题,因为应用程序从高度开放的环境转移到了控制较严格的设置中,而这样的设置会妨碍应用程序正常运行。

您应通过为 SQL 帐户或 Windows 帐户(用户或组)创建 SQL 登录来进行启动。然后,通过创建数据库用户来授予对数据库的登录访问权限,将数据库用户添加到用户定义的数据库角色,并给该角色分配权限。

为 SQL 设置数据访问帐户

1.

创建新的 Windows 用户帐户并将该帐户添加到 Windows 组中。如果您要管理多个用户,应使用用户组。如果您要处理单个应用程序帐户(如重复的 ASP.NET 进程帐户),则可以选择不向 Windows 组添加该帐户。

2.

为用户/组创建 SQL Server 登录。

1.

启动“企业管理器”,找到您的数据库服务器,然后展开“安全性”文件夹。

2.

右键单击“登录”,然后单击“新建登录”。

3.

在“名称”字段中输入 Windows 帐户名称,然后单击“确定”关闭“SQL Server 登录属性”对话框。

3.

在相应的数据库中新建一个数据库用户,授予对数据库的登录访问权限。

1.

使用“企业管理器”并展开“数据库”文件夹,然后展开该登录要求访问的数据库。

2.

右键单击“用户”,然后单击“新建数据库用户”。

3.

选择以前创建的登录名。

4.

输入用户名。

4.

新建一个要为其分配权限的数据库角色。

1.

右键单击“数据库”文件夹下面的“角色”,然后单击“新建数据库角色”。

2.

输入角色名。

3.

单击“添加”,将数据库用户添加到角色中。

4.

按下面介绍的方法配置权限。

5.

对于需要访问的表,向数据库用户授予“选择”权限;对于任何相关的存储过程,向该用户授予“执行”权限。

注意:如果存储过程和表都由同一个人拥有,并且只通过存储过程访问该表(不需要直接访问表),那么,只授予对存储过程的执行权限就足够了。其原因可以用所有者关系链的概念来解释。有关详细信息,请参见“SQL Server 联机丛书”。

6.

如果您希望用户帐户拥有对数据库中所有视图和表的访问权限,请将这些视图和表添加到 db_datareader 固定数据库角色中。

注意:Public 角色存在于所有其他数据库用户和角色所属的每个数据库中,并且不能被删除。您应该撤销或拒绝对此角色的权限,因此授权完全由与用户定义的数据库角色相关联的权限决定。

返回页首返回页首

安全存储数据库连接字符串

您可以在许多位置使用多种方式来存储数据库连接字符串,它们的安全程度和配置灵活性各不相同。

选项

下表列出了用于存储连接字符串的主要选项:

用 DPAPI 加密

在 Web.config 或 Machine.config 中采用明文形式

UDL 文件

自定义文本文件

注册表

COM+ 目录

使用 DPAPI

Windows 2000 和更高版本的操作系统均提供了 Win32® 数据保护 API (DPAPI) 来对数据进行加密和解密。DPAPI 是加密 API (Crypto API) 的一部分,它在 Crypt32.dll 中实现。它由两种方法构成:CryptProtectDataCryptUnprotectData

DPAPI 特别有用,因为它能够解决使用加密的应用程序所带来的密钥管理问题。虽然加密能确保数据安全,但您必须采取额外的步骤来确保密钥的安全性。DPAPI 使用与调用 DPAPI 函数的代码相关联的用户帐户的密码,以便派生加密密钥。因此,操作系统(而不是应用程序)可以管理密钥。

为何不使用 LSA?

许多应用程序均使用本地安全机构 (LSA) 来存储机密数据。DPAPI 在以下方面优于 LSA 方法:

若要使用 LSA,进程要求具备管理权限。这会造成安全隐患,因为它大大增加了设法破坏进程的攻击者可能造成的损失。

LSA 只为机密存储提供数量有限的插槽,而许多插槽已经被系统占用。

计算机存储与用户存储

DPAPI 能够与计算机存储或用户存储(需要一个已加载的用户配置文件)配合使用。DPAPI 默认设置为用户存储,但您可以通过将 CRYPTPROTECT_LOCAL_MACHINE 标志传递给 DPAPI 函数来指定使用计算机存储。

这种用户配置文件方法提供了额外的安全层,因为它限制了哪些用户能够访问机密数据。只有加密数据的用户才能解密数据。但是,从 ASP.NET Web 应用程序使用 DPAPI 时,使用用户配置文件需要您完成额外的开发工作,因为您需要采取明确的步骤来加载和卸载用户配置文件。ASP.NET 不会自动加载用户配置文件。

计算机存储方法更易于开发,因为它不需要管理用户配置文件。但是,除非使用额外的熵参数,否则并不安全,因为计算机上的任何用户都可以解密数据。熵是一个设计用于使解密机密数据更为困难的随机值。使用额外熵参数的问题在于,它必须由应用程序安全存储起来,这带来了另一个密钥管理问题。

注意:如果您将 DPAPI 与计算机存储一起使用,则加密字符串仅适用于给定的计算机,因此您必须在每台计算机上生成加密数据。不要在场或群集中跨计算机复制加密数据。

如果将 DPAPI 与用户存储一起使用,则可以使用漫游用户配置文件在任何一台计算机上解密数据。

DPAPI 实现解决方案

本节介绍两种实现解决方案,说明如何从 ASP.NET Web 应用程序使用 DPAPI 来保护连接字符串或任何类型的机密数据。本节介绍的实现解决方案包括:

从企业服务使用 DPAPI。此解决方案允许您将 DPAPI 用于用户存储。

直接从 ASP.NET 使用 DPAPI。此解决方案允许您将 DPAPI 用于计算机存储,这样更容易开发解决方案,因为可以直接从 ASP.NET Web 应用程序调用 DPAPI。

从企业服务使用 DPAPI

ASP.NET Web 应用程序无法调用 DPAPI 和使用用户存储,因为这需要有一个已加载的用户配置文件。通常用来运行 Web 应用程序的 ASPNET 帐户是非交互帐户,而非交互帐户没有用户配置文件。此外,如果 ASP.NET 应用程序正在进行模拟,则 Web 应用程序线程将以当前已验证的用户身份运行,该线程因请求的不同而异。

这种情况为要使用 DPAPI 的 ASP.NET Web 应用程序提出了以下问题:

从默认的 ASPNET 帐户下运行的 ASP.NET 应用程序调用 DPAPI 会失败。这是因为 ASPNET 帐户没有用户配置文件,它不会用于交互式登录。

如果将 ASP.NET Web 应用程序配置为模拟其调用方,则 ASP.NET 应用程序线程会有一个关联的线程模拟令牌。与该模拟令牌相关联的登录会话是一种网络登录会话(在服务器上用来表示调用方)。网络登录会话不会导致加载用户配置文件。

若要解决这个问题,您可以创建一个服务组件(在进程外企业服务 (COM+) 服务器应用程序内)来调用 DPAPI。您可以确保用来运行组件的帐户具有用户配置文件,并且可以使用 Win32 服务自动加载配置文件。

注意:通过在该服务组件中调用 Win32 配置文件管理函数(LoadUserProfileUnloadUserProfile),可以避免使用 Win32 服务。

该方法有两个缺点。其一,每个请求依次调用这些 API 会严重影响性能。其二,这些 API 要求执行调用的代码在本地计算机上具有管理权限,这违背了企业服务进程帐户的最少权限原则。

图 12.5 显示了企业服务 DPAPI 解决方案。

ASP.NET Web 应用程序使用 COM+ 服务器应用程序与 DPAPI 进行交互

图 12.5
ASP.NET Web 应用程序使用 COM+ 服务器应用程序与 DPAPI 进行交互

在图 12.5 中,事件的运行时顺序如下:

1.

Windows 服务控制管理器启动 Win32 服务,并自动加载与运行该服务的帐户相关联的用户配置文件。使用同一 Windows 帐户运行企业服务应用程序。

2.

Win32 服务对服务组件调用启动方法,该方法将启动企业服务应用程序并加载服务组件。

3.

该 Web 应用程序从 Web.config 文件检索加密的字符串。
您可以利用 Web.config 中的 <appSettings> 元素来存储加密的字符串,如下所示。该元素支持任意的关键字-值对。

<configuration>
 <appSettings>
  <add key="SqlConnString"
       value="AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAABcqc/xCHxki3" />
 </appSettings>
</configuration>

您可以使用以下代码行检索加密的字符串:

string connString = ConfigurationSettings.AppSettings["SqlConnString"];

注意:您可以使用 Web.config 或 Machine.config 来存储加密的连接字符串。Machine.config 是首选项,因为它位于虚拟目录外的系统目录中。此内容将在下一节“使用 Web.config 和 Machine.config”中进一步讨论。

4.

该应用程序调用服务组件上的方法来解密连接字符串。

5.

服务组件与使用 P/Invoke 的 DPAPI 进行交互,以调用 Win32 DPAPI 函数。

6.

解密的字符串被返回到 Web 应用程序。

注意:若要首先在 Web.config 文件中存储加密的连接字符串,请编写一个可接受该连接字符串并调用服务组件的 EncryptData 方法的实用程序来获得加密字符串。当您用运行企业服务服务器应用程序所使用的帐户进行登录时,必须运行该实用程序。

直接从 ASP.NET 使用 DPAPI

如果您使用计算机存储并调用带有 CRYPTPROTECT_LOCAL_MACHINE 标志的 DPAPI 函数,则可以直接从 ASP.NET Web 应用程序调用 DPAPI 函数,因为您不需要用户配置文件。

但是,由于您使用的是计算机存储,因此任何可登录到计算机上的 Windows 帐户都能访问机密数据。增加熵是一种减小风险的方法,但这需要额外的密钥管理。

如果不想对计算机存储使用熵,请考虑以下可选方法:

使用 Windows ACL 限制对加密数据的访问(不管数据是存储在文件系统中还是存储在注册表中)。

考虑将熵参数硬编码到应用程序中,以避免密钥管理问题。

更多信息

有关创建与 .NET Web 应用程序一起使用的 DPAPI 库的详细信息,请参见本指南中如何创建 DPAPI 库一章的内容。

有关说明如何直接从 ASP.NET 使用 DPAPI 的详细实现步骤,请参见本指南中如何在 ASP.NET 中使用 DPAPI(计算机存储)一章的内容。

有关说明如何从企业服务使用 DPAPI 的详细实现步骤,请参见本指南中如何在具有企业服务的 ASP.NET 中使用 DPAPI(用户存储)一章的内容。

有关使用 DPAPI 进行 Windows 数据保护的详细信息,请参见 MSDN 文章 Windows Data Protection(Windows 数据保护)。

使用 Web.config 和 Machine.config

建议不要以明文形式在 Web.config 中存储密码。默认情况下,HttpForbiddenHandler 可防止文件被怀有恶意的用户下载和查看。但是,直接对包含配置文件的文件夹拥有访问权限的用户仍可看到用户名和密码。

Machine.config 被视为一个比 Web.config 更安全的存储位置,因为它位于 Web 应用程序的虚拟目录之外的系统目录(带有 ACL)中。有关保护 Machine.config 的详细信息,请参见第 8 章 ASP.NET 安全性。

使用 UDL 文件

OLE DB .NET 数据提供程序支持其连接字符串中的 UDL 文件名。若要引用 UDL 文件,请在连接字符串中使用“File Name=name.udl”。

重要说明:该选项仅在您使用 OLE DB .NET 数据提供程序连接到数据库时才可用。SQL Server .NET 数据提供程序不使用 UDL 文件。

建议不要在虚拟目录中将 UDL 文件与其他应用程序文件存储在一起。您应当将它们保存在 Web 应用程序的虚拟目录层次结构之外的目录中,然后利用 Windows ACL 保护该文件或包含该文件的文件夹。您还应当考虑将 UDL 文件存储在操作系统所在逻辑卷之外的单独逻辑卷上,以防止可能出现的文件规范化和目录遍历错误。

ACL 细化程度

如果您在使用 UDL 文件(或任何文本文件)时应用 ACL,可提供比 Machine.config 更高的细化程度。与 Machine.config 关联的默认 ACL 将向各种各样的本地用户和远程用户授予访问权限。例如,Machine.config 包含以下默认的 ACL:

MachineName\ASPNET:R
BUILTIN\Users:R
BUILTIN\Power Users:C
BUILTIN\Administrators:F
NT AUTHORITY\SYSTEM:F

相反,您可以进一步锁定自己的应用程序的 UDL 文件。例如,您可以将访问权限限于管理员、系统帐户和 ASP.NET 进程帐户(该帐户需要读权限),如下所示:

BUILTIN\Administrators:F
MachineName\ASPNET:R
NT AUTHORITY\SYSTEM:F  

注意:因为 UDL 文件可以在任何 ADO.NET 客户端应用程序的外部进行修改,所以每次打开连接时都会分析包含对 UDL 文件的引用的连接字符串,这会影响性能。因此,为实现最佳的性能,建议您使用不包括 UDL 文件的静态连接字符串。

创建新的 UDL 文件

1.

使用 Windows 资源管理器并导航到您要在其中创建 UDL 文件的文件夹。

2.

右键单击该文件夹,指向“新建”,然后单击“文本文档”。

3.

提供一个带有 .udl 文件扩展名的文件名。

4.

双击新文件以显示“UDL 属性”对话框。

更多信息

有关从 Microsoft C#® 开发工具程序使用 UDL 文件的详细信息,请参见 Microsoft 知识库文章 Q308426 HOW TO: Use Data Link Files with OleDbConnection Object in Visual C# .NET(HOW TO:在 Visual C# .NET 中将数据链接文件用于 OleDbConnection 对象)。

使用自定义文本文件

许多应用程序均使用自定义文本文件来存储连接字符串。如果您采用这种方式,请考虑以下建议:

将自定义文件存储在应用程序的虚拟目录层次结构之外的目录中。

考虑将文件存储在操作系统所在逻辑卷之外的单独逻辑卷上,以防止可能出现的文件规范化和目录遍历错误。

用受限的 ACL 保护文件,该 ACL 仅向您的应用程序的进程帐户授予读权限。

避免在文件中以明文形式存储连接字符串。应考虑使用 DPAPI 来存储加密的字符串。

使用注册表

您可以在 Windows 注册表中使用自定义项来存储连接字符串。存储的这些信息可以保存在 HKEY_LOCAL_MACHINE (HKLM) 或 HKEY_CURRENT_USER (HKCU) 注册表配置单元中。对于没有用户配置文件的进程标识(如 ASPNET 帐户),这些信息必须存储在 HKLM 中,以便允许 ASP.NET 代码对其进行检索。

如果您使用此方法,则应该:

使用 ACL 来保护使用 Regedt32.exe 的注册表项。

在存储之前对数据进行加密。

更多信息

有关在注册表中加密要存储的数据的详细信息,请参见本指南中如何在注册表中存储加密的连接字符串一章的内容。

使用 COM+ 目录

如果 Web 应用程序包括服务组件,则可以将连接字符串作为构造函数字符串存储在 COM+ 目录中。这样可以轻松管理这些字符串(使用“组件服务”工具),并且也很容易按组件代码进行检索。企业服务在实例化对象后就会立即调用该对象的 Construct 方法,并传递已配置的构造字符串。

COM+ 目录无法提供高度的安全性,因为信息没有进行加密;但是,与配置文件相比,由于有额外的进程跳跃,因此提高了安全性要求。

若要禁止通过“组件服务”工具访问该目录,则应该只将那些您想授予其权限的一组用户添加到“系统”应用程序中的 AdministratorReader 角色中。

以下示例说明了如何从服务组件检索对象构造函数字符串:

[ConstructionEnabled(Default="默认连接字符串")]
public class YourClass : ServicedComponent 
{
  private string _ConnectionString;
  override protected void Construct(string s) 
  {
    _ConnectionString = s; 
  }
}

为提高安全性,您可以在存储之前通过添加代码对构造字符串进行加密,然后在服务组件中进行解密。

更多信息

有关使用连接字符串的详细信息,请参见 Microsoft 知识库文章 Q271284 HOWTO: Access COM+ Object Constructor String in a Visual Basic Component(HOWTO:在 Visual Basic 组件中访问 COM+ 对象构造函数字符串)。

有关 .NET Framework SDK 提供的完整代码示例,请参见位于以下目录中的对象构造函数示例:\Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Samples\Technologies\ComponentServices\ObjectConstruction。

返回页首返回页首

为数据库验证用户身份

如果您要生成需要为某个数据库存储验证用户凭据的应用程序,请考虑以下几点:

存储带有随机 Salt 值的单向密码哈希。

在验证用户凭据时避免 SQL 注入。

存储单向密码哈希(带有 Salt 值)

使用窗体身份验证的 Web 应用程序常常需要在数据库中存储包含密码的用户凭据。为安全起见,您不应在数据库中存储密码(无论是采用明文还是加密的形式)。

您应该避免存储加密的密码,因为这会带来密钥管理问题 – 您可以通过加密来确保密码的安全性,但必须考虑如何存储加密密钥。如果密钥被破坏,攻击者就可以解密数据存储中的所有密码。

首选的方法是:

存储密码的单向哈希。在需要验证密码时重新计算哈希。

将密码哈希与 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);
}

创建哈希值(带有 Salt)

以下代码片段说明了如何从提供的密码和 Salt 值生成哈希值:

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

更多信息

有关全面实现这种方法的详细信息,请参见本指南中如何将窗体身份验证用于 SQL Server 2000 一章的内容。

返回页首返回页首

SQL 注入式攻击

如果您要对 SQL 数据库使用窗体身份验证,则应采取本节介绍的预防措施来防范 SQL 注入式攻击。SQL 注入式攻击是将额外的(恶意的)SQL 代码传递到某个应用程序中的行为,该代码通常被附加到该应用程序内包含的合法 SQL 代码中。所有 SQL 数据库都容易受到不同程度的 SQL 注入式攻击,但本章的侧重点是 SQL Server 数据库。

当您处理作为 SQL 命令组成部分的用户输入时,应特别注意可能发生的 SQL 注入式攻击。如果您的身份验证方案用于为 SQL 数据库验证用户身份(例如您对 SQL Server 使用窗体身份验证),则必须防范 SQL 注入式攻击。

如果您使用未筛选的输入来生成 SQL 字符串,则应用程序可能会遭受恶意用户输入(注意,切勿信任用户输入)的攻击。其中的危险在于,当您将用户输入插入一个将要成为可执行语句的字符串中时,恶意用户可以利用转义符将 SQL 命令附加到您想用的 SQL 语句中。

下面几节中的代码片段使用 SQL Server 附带的 Pubs 数据库来说明 SQL 注入式攻击的示例。

问题

当您将用户输入或其他未知数据添加到数据库查询中时,您的应用程序会很容易遭受 SQL 注入式攻击。例如,下面的两个代码片段都易于受到攻击。

您用未筛选的用户输入生成 SQL 语句。

SqlDataAdapter myCommand = new SqlDataAdapter(
          "SELECT au_lname, au_fname FROM authors WHERE au_id = '" + 
          Login.Text + "'", myConnection);

您通过生成包含未筛选的用户输入的单个字符串来调用存储过程。

SqlDataAdapter myCommand = new SqlDataAdapter("LoginStoredProcedure '" + 
                               Login.Text + "'", myConnection);

SQL 脚本注入式攻击的剖析

当您在应用程序中接受未筛选的用户输入值(如上所示)时,恶意用户可以使用转义符来追加自己的命令。

请看一个 SQL 查询,在该查询中,用户的输入是社会保险号码(如 172–32–xxxx)形式,得到的查询如下所示:

SELECT au_lname, au_fname FROM authors WHERE au_id = '172-32-xxxx'

恶意用户可以将以下文字输入您的应用程序输入字段(例如文本框控件)中:

' ; INSERT INTO jobs (job_desc, min_lvl, max_lvl) VALUES ('Important Job', 25, 
100)  -

此示例中注入了一个 INSERT 语句(但是允许用于连接到 SQL Server 的帐户使用的任何语句都可以执行)。如果该帐户是 sysadmin 角色的成员(它允许执行使用 xp_cmdshell 的 shell 命令),并且 SQL Server 使用的域帐户拥有对其他网络资源的访问权限,则该代码的破坏性特别大。

上述命令生成以下组合的 SQL 字符串:

SELECT au_lname, au_fname FROM authors WHERE au_id = '';INSERT INTO jobs 
(job_desc, min_lvl, max_lvl) VALUES ('Important Job', 25, 100)  --

在这种情况下,恶意输入开头的 '(单引号)字符成为您的 SQL 语句中的当前字符串的结束字符。它仅在以下情况下才结束当前语句:下面被分析的标记不作为当前语句的继续标记,而是作为一个新语句的开始标记。

SELECT au_lname, au_fname FROM authors WHERE au_id = ' '

;(分号)字符告诉 SQL 您正在开始一个新语句,而紧跟在后面的就是恶意 SQL 代码:

; INSERT INTO jobs (job_desc, min_lvl, max_lvl) VALUES ('Important Job', 25, 100)  

注意:分隔 SQL 语句不一定需要使用分号。这与供应商/实现方法有关,但 SQL Server 不需要。例如,SQL Server 将以下语句分析为两个独立的语句:

SELECT * FROM MyTable DELETE FROM MyTable

最后,––(双短划线)字符序列是一个 SQL 注释符号,它告诉 SQL 忽略文本其余部分,在此示例中就是忽略 '(单引号)这个结束字符(否则,会导致 SQL 分析器错误)。

作为上述语句的结果,SQL 执行的完整文本是:

SELECT au_lname, au_fname FROM authors WHERE au_id = '' ; INSERT INTO jobs 
(job_desc, min_lvl, max_lvl) VALUES ('Important Job', 25, 100) --'

解决方案

以下几种方法可用于从应用程序安全调用 SQL:

在生成 SQL 语句时使用 Parameters 集合。

SqlDataAdapter myCommand = new SqlDataAdapter(
        "SELECT au_lname, au_fname FROM Authors WHERE au_id= @au_id", 
        myConnection);

SqlParameter parm = myCommand.SelectCommand.Parameters.Add(
                                             "@au_id",
                                             SqlDbType.VarChar, 11);
parm.Value= Login.Text;

在调用存储过程时使用 Parameters 集合。

// AuthorLogin 是一个存储过程,它接受名称为 Login 的参数
SqlDataAdapter myCommand = new SqlDataAdapter("AuthorLogin", myConnection);
myCommand.SelectCommand.CommandType = CommandType.StoredProcedure;
SqlParameter parm = myCommand.SelectCommand.Parameters.Add(
                                "@LoginId", SqlDbType.VarChar,11);
parm.Value=Login.Text;

如果您使用 Parameters 集合,则不管恶意用户在输入中包含什么内容,该输入都会当作文本进行处理。使用 Parameters 集合的另一个优势是您可以执行类型和长度检查。超出范围的值会触发异常。这是一个有效的深层防御示例。

从用户输入中筛选 SQL 字符。下面的方法说明了如何确保在简单的 SQL 比较(等于、小于、大于)语句中使用的任何字符串都是安全的。此方法是通过确保在字符串中使用的任何撇号上再附加一个撇号进行转义来实现这一点。在 SQL 字符串中,两个连续的撇号被视为字符串内撇号字符的实例,而不是分隔符。

private string SafeSqlLiteral(string inputSQL)
{
  return inputSQL.Replace("'", "''");
}
...
string safeSQL = SafeSqlLiteral(Login.Text);
SqlDataAdapter myCommand = new SqlDataAdapter(
       "SELECT au_lname, au_fname FROM authors WHERE au_id = '" + 
       safeSQL + "'", myConnection);

其他最佳的做法

下面是用于减少安全漏洞以及用于将可能造成的破坏限制在一定范围内的一些其他措施:

通过限制输入的大小和类型,在入口(前端应用程序)防止无效输入。通过限制输入的大小和类型,可大大降低破坏的可能性。例如,如果数据库查找字段长度为 11 个字符并且全部由数字字符组成,则强制执行该规则。

用具有最少权限的帐户运行 SQL 代码。这样可以大大减轻可能造成的损害。
例如,如果用户要注入 SQL,以便从数据库中删除表,但是 SQL 连接所使用的帐户没有相应的权限,则 SQL 代码将失败。这是不应将 sa 帐户或 db_owner 的成员用于应用程序的 SQL 连接的又一原因。

在 SQL 代码中出现异常时,不要向最终用户暴露数据库引起的 SQL 错误。应记录错误信息,但只显示用户友好信息。这样可以避免泄露可能对攻击者有帮助的不必要的详细信息。

保护模式匹配语句

如果输入将在“LIKE”子句的字符串中使用,则字符(撇号除外)还具有模式匹配的特殊含义。

例如,在 LIKE 子句中,% 字符表示“匹配零或更多字符”。为了将输入中的此类字符作为没有特殊含义的文字字符,它们也需要进行转义。如果不对它们进行特殊处理,查询就可能返回错误结果。在字符串开头或开头附近的非转义模式匹配字符可能也会使索引失效。

对于 SQL Server,应当使用以下方法来确保输入的内容有效:

private string SafeSqlLikeClauseLiteral(string inputSQL)
{
  // 进行以下替换:
  // '  变成  ''
  // [  变成  [[]
  // %  变成  [%]
  // _  变成  [_]

  string s = inputSQL;
  s = inputSQL.Replace("'", "''");
  s = s.Replace("[", "[[]");
  s = s.Replace("%", "[%]");
  s = s.Replace("_", "[_]");
  return s;
}  
返回页首返回页首

审核

在默认情况下,SQL Server 中的登录审核功能并不启用。您可以通过 SQL Server 企业管理器或使用注册表来进行此项配置。图 12.6 中的对话框显示了为用户登录的成功或失败启用的审核。

日志条目写入 SQL 日志文件中,默认情况下,该文件位于 C:\Program Files\Microsoft SQL Server\MSSQL\LOG。您可以使用任何文本阅读器(如记事本)来查看它们。

包含审核级别设置的“SQL Server 属性”对话框

图 12.6
包含审核级别设置的“SQL Server 属性”对话框

您还可以在注册表中启用 SQL Server 审核功能。若要启用 SQL Server 审核功能,请在注册表中创建下面的 AuditLevel 项,将其值设置为下面指定的 REG_DWORD 值之一:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\AuditLevel

您可以选择以下值之一,从而使您能够捕获所需的详细级别:

3 – 既捕获成功的登录尝试,也捕获失败的登录尝试

2 – 只捕获失败的登录尝试

1 – 只捕获成功的登录尝试

0 – 不捕获任何登录尝试

建议您打开失败登录审核功能,因为据此可以判断是否有人试图恶意攻击 SQL Server。记录失败的审核尝试对性能的影响很小,除非您正在遭受攻击,而在这种情况下您必须了解这些信息。

您还可以根据 SQL 数据库管理对象 (DMO) 编写脚本。下面的代码片段显示了某个 VBScript 代码示例:

Sub SetAuditLevel(Server As String, NewAuditLevel As SQLDMO_AUDIT_TYPE)
    Dim objServer As New SQLServer2  
    objServer.LoginSecure = True     '使用集成安全性
    objServer.Connect Server        '连接到目标 SQL Server
    '设置审核级别
    objServer.IntegratedSecurity.AuditLevel = NewAuditLevel       
    Set objServer = Nothing
End Sub

查阅 SQL Server 联机丛书后可以得知,枚举类型 SQLDMO_AUDIT_TYPE 的成员包括:

SQLDMOAudit_All      3  记录所有身份验证尝试,无论成功
                        还是失败 
SQLDMOAudit_Failure  2  记录失败的身份验证 
SQLDMOAudit_Success  1  记录成功的身份验证
SQLDMOAudit_None     0  不记录身份验证尝试
返回页首返回页首

SQL Server 的进程标识

使用具有最少权限的本地帐户运行 SQL Server。安装 SQL Server 时,您可以选择使用本地 SYSTEM 帐户或指定的帐户运行 SQL Server 服务。

不要使用 SYSTEM 帐户或管理员帐户。应该使用具有最少权限的本地帐户。您无需向此帐户授予任何特定权限,因为安装过程(或者,如果您在安装后重新配置 SQL 服务,则为 SQL Server 企业管理器)向指定的帐户授予了必要的权限。

如果 SQL Server 需要访问远程计算机,例如,为了进行网络备份和恢复或复制,您需要使用具有最少权限的域帐户或在远程服务器上创建具有相同用户名和密码的重复的本地帐户。可以为密码同步编写脚本。如果您需要访问没有信任关系的其他域中的服务器,就必须使用重复的帐户这一方法。

返回页首返回页首

总结

下面是本章的总结,其中强调了为 .NET Web 应用程序中的数据访问提供的建议。

尽可能对 SQL Server 使用 Windows 身份验证。

在数据库中使用具有最少权限的帐户。

在连接到 SQL Server 时使用具有最少权限的本地帐户来运行 ASP.NET/企业服务。

在数据库中使用用户定义的数据库角色来进行授权。

如果您使用的是 SQL 身份验证,则采取以下步骤来提高安全性:

使用具有强密码的自定义帐户。

使用数据库角色来限制 SQL Server 中每个帐户的权限。

向任何用于存储连接字符串的文件添加 ACL。

对连接字符串进行加密。

考虑对凭据存储使用 DPAPI。

当您对 SQL 使用窗体身份验证时,应采取预防措施来防范 SQL 注入式攻击。

不要将用户密码存储在数据库中以进行用户验证。相反,应存储带有 Salt 值的密码哈希,而不是明文密码或加密密码。

保护通过网络发送给 SQL Server 或从 SQL Server 中发出的机密数据。

Windows 身份验证可保护凭据,但不能保护应用程序数据。

使用 IPSec 或 SSL。

阅读(6194) | 评论(1)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

loading...
您需要登录后才能评论,请 登录 或者 注册