构建安全的数据访问
本页内容
![]() |
本模块内容 |
![]() |
目标 |
![]() |
适用范围 |
![]() |
如何使用本模块 |
![]() |
威胁与对策 |
![]() |
设计注意事项 |
![]() |
输入验证 |
![]() |
SQL 注入 |
![]() |
身份验证 |
![]() |
授权 |
![]() |
配置管理 |
![]() |
敏感数据 |
![]() |
异常管理 |
![]() |
构建安全的数据访问组件 |
![]() |
代码访问安全性注意事项 |
![]() |
部署注意事项 |
![]() |
小结 |
![]() |
其他资源 |
本模块内容
数据访问是使用几个可用的 ADO.NET 数据提供程序之一从 ASP.NET Web 应用程序访问数据库的过程。
此数据库是应用程序级攻击的主要目标。应用程序级攻击用于利用数据访问代码中的漏洞并获取对数据库未授权的访问。如果所有其他攻击方法都已失效,应用程序的前门(即端口 8)将变成攻击者窃取、操纵和破坏数据的可选路径。
本模块说明如何构建安全的数据访问代码,以及如何避免常见的漏洞和缺陷。本模块提供了一系列对策和防御技术,您可以在自己的数据访问代码中使用它们来减少与数据访问有关的主要威胁。
目标
使用本模块可以实现:
• |
设计、构建和部署安全的数据访问代码。 |
• |
使用代码访问安全性和基于角色的安全性可以限制未经授权的调用方或代码的访问。 |
• |
安全地验证用户的身份。 |
• |
防止 SQL 注入攻击。 |
• |
确保数据库连接字符串的安全。 |
• |
使用加密机制来保护存储在数据库中的数据。 |
• |
确保通过网络发送到数据库以及从数据库发送的数据的安全。 |
• |
使用带有salt 的哈希值将密码安全地存储在数据库中。 |
• |
实现安全的异常处理。 |
• |
了解如何使用代码访问安全性,允许中等信任 Web 应用程序使用 OLE DB、Oracle 和 ODBC 数据提供程序(这些提供程序需要完全信任)。 |
• |
了解应使用哪些对策来解决常见的数据访问威胁,包括 SQL 注入、配置数据泄露、敏感的应用程序数据泄露、数据库架构和连接详细信息的泄露、未授权的访问和网络窃听。 |
适用范围
本模块适用于下列产品和技术:
• |
Microsoft® Windows® 2000 Server 和 Microsoft Windows Server™ 2003 |
• |
Microsoft .NET Framework 1.1 和 ASP.NET 1.1 |
• |
Microsoft SQL Server™ |
如何使用本模块
为了充分理解本模块内容,请先阅读下列模块或与本模块结合起来阅读:
• |
阅读模块 2 威胁与对策。这将使您更广泛深入地了解 Web 应用程序所面临的潜在威胁及其对策。 |
• |
阅读模块 4 Web 应用程序安全设计指南。在本模块中,您将了解构建安全解决方案时所面临的体系结构和设计挑战,以及构建准则。 |
• |
阅读模块 18 保证数据库服务器的安全。阅读模块 18 可了解如何确保数据库服务器的安全。 |
• |
阅读模块 7 构建安全的程序集。模块 7 中构建安全程序集和开发安全托管代码的准则和建议同样应该适用于数据访问代码 |
• |
使用评估模块。要在产品周期的不同阶段检查数据访问的安全性,请参见下列模块中的“Web 服务”部分:模块 5 安全性体系结构和设计审查、模块 21 代码审查以及模块 22 部署审查。 |
• |
使用检查表。本指南“检查表”部分中的检查表:保护数据访问包括一个便于参考的检查表。可以将此基于任务的检查表用作本模块中各个建议的摘要。 |
请注意,在当前版本的 .NET Framework (1.1) 中,只有 ADO.NET SQL Server 数据访问提供程序才支持部分信任调用方,并且可以安全地用在部分信任 Web 应用程序中。OLE DB、Oracle 和 ODBC ADO.NET 数据提供程序需要完全信任。
威胁与对策
要构建安全的数据访问代码,需要了解数据访问代码中的威胁是什么、常见漏洞是如何产生的以及如何使用适当的对策来降低风险。
数据访问代码面临的主要威胁包括:
• |
SQL 注入 |
• |
配置数据的泄漏 |
• |
敏感应用程序数据的泄漏 |
• |
数据库架构和连接详细信息的泄露 |
• |
未授权的访问 |
• |
网络窃听 |
图 14.1 阐明了这些主要威胁。
![数据访问代码面临的威胁和攻击](http://img.microsoft.com/china/technet/security/guidance/images/SGFG08701.jpg)
图 14.1
数据访问代码面临的威胁和攻击
SQL 注入
SQL 注入攻击利用易受攻击的数据访问代码,并允许攻击者在数据库中执行任意命令。如果应用程序使用数据库中不受限制的帐户,由于攻击者可以更自由地执行查询和命令,因此受到的威胁会更大。
漏洞
使数据访问代码容易受到 SQL 注入攻击的常见漏洞包括:
• |
弱输入验证 |
• |
在不使用类型安全的参数时动态构造 SQL 语句 |
• |
使用特权过高的数据库登录 |
对策
要应对 SQL 注入攻击,请务必:
• |
限制和净化输入数据。 |
• |
使用类型安全的 SQL 参数进行数据访问。这些参数可以与存储过程一起使用,也可以是动态构造的 SQL 命令字符串。参数执行类型和长度检查,并同时确保注入数据库中的代码被视为文本数据(而非可执行语句)。 |
• |
使用在数据库中具有有限权限的帐户。理想情况下,只应向数据库中的选定存储过程授予执行权限,且不提供直接的表格访问权限。 |
配置数据泄露
数据访问代码所使用的最敏感的配置数据是数据库连接字符串。如果泄漏的连接字符串包括用户名和密码,后果将不堪设想。
漏洞
下列漏洞会增加与泄漏的配置数据相关的安全风险:
• |
使用 SQL 身份验证,这要求在连接字符串中指定凭据 |
• |
代码中嵌入的连接字符串 |
• |
配置文件中的明文连接字符串 |
• |
无法加密连接字符串 |
对策
要防止配置数据的泄漏:
• |
使用 Windows 身份验证,以便连接字符串中不包含凭据。 |
• |
加密连接字符串,并限制对已加密数据的访问。 |
敏感应用程序数据的泄漏
许多应用程序都存储敏感的数据(如客户的信用卡号),一定要保护此类数据的私密性和完整性。
漏洞
下列编码做法可能会导致泄漏敏感的应用程序数据:
• |
存储没有加密的数据 |
• |
弱授权 |
• |
弱加密 |
对策
要防止泄漏敏感的应用程序数据:
• |
使用强加密机制来确保数据的安全。 |
• |
在执行数据访问之前先为每个调用方授权,以便用户只能看到其各自的数据。 |
数据库架构和连接详细信息的泄露
如果您的代码向客户端返回异常详细信息,恶意用户可能会使用这些信息来攻击服务器。数据访问代码中的异常可能会透露敏感信息,如数据库架构详细信息、数据存储的特性以及 SQL 代码片断。
漏洞
下列漏洞可能会导致信息泄漏:
• |
不充分的异常处理 |
• |
薄弱的 ASP.NET 配置(允许未经处理的异常详细信息返回到客户端) |
对策
要防止这种泄漏:
• |
捕获、记录和处理数据访问代码中的数据访问异常。 |
• |
向调用方返回一般的错误消息。这要求对 Web.config 或 Machine.config 配置文件中的 <customErrors> 元素进行适当的配置。 |
未授权的访问
在授权不足的情况下,用户也许能够看到另一个用户的数据,并且能够访问其他受限制的数据。
漏洞
下列做法可能会允许未授权的访问:
• |
数据访问代码中缺乏授权,从而提供了不受限制的访问 |
• |
数据库帐户的特权过高 |
对策
要防止未授权的访问:
• |
按照主体权限需求为调用用户授权。 |
• |
按照代码访问安全权限需求为调用代码授权。 |
• |
使用受限制的权限来限制应用程序登录到数据库,并防止直接访问表格。 |
网络窃听
大多数应用程序的部署体系结构中都包括数据访问代码与数据库服务器之间的物理隔离。因此,必须防止窃听者通过网络窃听敏感数据(如应用程序特定的数据或数据库登录凭据)。
漏洞
下列做法会增加网络窃听的漏洞:
• |
在 SQL 身份验证过程中通过网络传递的明文凭据 |
• |
进出数据库服务器的未加密敏感应用程序数据 |
对策
要限制网络窃听的漏洞:
• |
使用 Windows 身份验证来避免通过网络发送凭据。 |
• |
在数据库服务器上安装一个服务器证书。这会导致自动加密网络上的 SQL 凭据。 |
• |
在 Web 服务器和数据库服务器之间使用 SSL 连接来保护敏感的应用程序数据。这需要一个数据库服务器证书。 |
• |
在 Web 和数据库服务器之间使用 IPSec 加密通道。 |
设计注意事项
在开始编写代码之前,需要在设计时考虑许多重要的问题。主要的注意事项包括:
• |
使用 Windows 身份验证。 |
• |
使用最小特权帐户。 |
• |
使用存储过程。 |
• |
保护所存储的敏感数据。 |
• |
使用单独的数据访问程序集。 |
使用 Windows 身份验证
理想情况下,在设计中应该使用 Windows 身份验证,以增加安全性好处。使用 Windows 身份验证,您不必存储具有嵌入凭据的数据库连接字符串,凭据不通过网络传递,而且您可以受益于安全帐户和密码管理策略。但是,您需要认真考虑在使用 Windows 身份验证时,将使用哪个帐户连接到 SQL Server。
有关详细信息,请参阅本模块后面的身份验证。
使用最小特权帐户
您的应用程序应该使用在数据库中具有有限权限的最小特权帐户。请确保对应用程序的数据库登录进行了适当的授权和限制。有关详细信息,请参阅本模块后面的授权。
使用最小特权帐户可以降低风险,并在您的帐户发生泄漏或者注入了恶意代码时限制潜在的损害。对于 SQL 注入,该命令将在由应用程序登录定义的安全上下文中执行,并遵守该登录在数据库中拥有的相关权限。如果您使用特权过高的帐户(例如,作为 SQL Server sysadmin 角色的成员)进行连接,攻击者能够在服务器上的任何数据库中执行任意操作。这包括插入、更新和删除数据;删除表;执行操作系统命令。
要点 不要使用 sa 帐户或者 SQL Server sysadmin 或 db_owner 角色的任何成员帐户连接到 SQL Server。
使用存储过程
存储过程提供性能、维护和安全性好处。应尽可能使用参数化存储过程进行数据访问。安全性好处包括:
• |
可以限制应用程序数据库登录,以便它只具有执行指定存储过程的权限。没有必要授予直接的表格访问权限。这有助于降低由 SQL 注入攻击造成的风险。 |
• |
针对传递到存储过程的所有输入数据执行长度和类型检查。同样,不能将参数视为可执行代码。这也会降低 SQL 注入风险。 |
如果由于某种原因,您无法使用参数化存储过程,但是您需要动态构造 SQL 语句,请使用类型化参数和参数占位符来构造这样的语句,以确保检查输入数据的长度和类型。
保护所存储的敏感数据
标识需要保证私密性和完整性的存储数据。如果您只是为了验证而将密码存储到数据库中,请考虑使用单向哈希。如果密码表发生泄漏,则不能使用哈希来获取明文密码。
如果您存储用户提供的敏感数据(如信用卡号),请使用强对称加密算法(如三重 DES (3DES))来加密数据。使用 Win32 数据保护 API (DPAPI) 加密 3DES 密钥,然后将已加密的密钥存储在具有受限 ACL 的注册表项中,只有管理员和应用程序进程帐户才能使用该注册表项。
为什么不使用 DPAPI?
尽管建议使用 DPAPI 来加密连接字符串和其他可在计算机出现故障时手动恢复和重新构造的机密(如帐户凭据),但仍不太适合存储信用卡号之类的数据。这是由于可恢复性问题(如果密钥丢失,则无法恢复加密数据)和 Web 场问题。相反,应该使用对称加密算法(如 3DES)并使用 DPAPI 加密密钥。
下面概述了造成 DPAPI 不太适合在数据库中存储敏感数据的主要问题:
• |
如果 DPAPI 与计算机密钥一起使用,而且您将 CRYPTPROTECT_LOCAL_MACHINE 传递到 CryptProtectData 和 CryptUnprotectData 函数,则计算机帐户会生成加密密钥。这意味着 Web 场中的每台服务器都有一个不同的密钥,这会禁止一台服务器访问由另一台服务器加密的数据。同样,如果该 Web 服务器计算机被破坏,则密钥会丢失,而且加密的数据无法从数据库进行恢复。 |
• |
如果使用计算机密钥方法,则该计算机上的任何用户都可以对数据进行解密(除非您使用其他加密机制)。 |
• |
如果您将 DPAPI 与用户密钥一起使用,而且您使用的是本地用户帐户,就会为每台 Web 服务器上的每个本地帐户生成一个不同的安全标识符 (SID) 和一个不同的密钥,这会禁止一台服务器访问由另一台服务器加密的数据。 |
• |
如果您将 DPAPI 与用户密钥一起使用,而且您在 Web 场中的计算机之间使用漫游用户配置文件,则所有数据都将共享相同的加密/解密密钥。但是,如果负责漫游用户配置文件帐户的域控制器被损害或被破坏,则无法重新创建具有相同 SID 的用户帐户,而且不能从数据库中恢复加密的数据。 另外,对于漫游用户配置文件,如果某人设法检索该数据,则只要攻击者能够在特定的用户帐户下运行代码,就可以在网络中的任何计算机上解密该数据。这会增加潜在攻击的范围,因此不建议这样做。 |
使用单独的数据访问程序集
如果您可以进行选择,请避免将数据访问逻辑直接放在 ASP.NET 页或代码隐藏文件中。如果将数据访问逻辑放在一个单独的程序集中,并实现一个与应用程序的业务和表示逻辑分开的逻辑数据访问层,就会带来安全性、重复使用和维护好处。
从安全的角度看,您可以:
• |
对程序集使用强名称以提供可防篡改功能。 |
• |
使用沙盒技术来隔离数据访问代码,如果您的代码需要支持部分信任调用方(例如,部分信任 Web 应用程序),这一点十分重要。 |
• |
使用那些按照代码标识权限需求向调用代码授权的数据访问方法和类。 |
对于深层防御,请按照业务组件中的主体权限需求来执行基于主体的授权,并按照代码标识权限需求来为调用数据访问逻辑的代码授权,如图 14.2 所示。
![分开表示层、业务层和数据访问层](http://img.microsoft.com/china/technet/security/guidance/images/SGFG08702.jpg)
图 14.2
表示层、业务层和数据访问层的分离
有关数据访问代码授权的详细信息,请参阅本模块后面的授权部分。
输入验证
除了业务层需要确保数据库保持数据的有效性和一致性之外,还必须在将数据提交到数据库之前验证数据,以防 SQL 注入。如果数据访问代码从当前信任边界内部的其他组件接收其输入内容,而且您知道数据已经过验证(例如,由 ASP.NET 网页或业务组件验证),则数据访问代码会忽略广泛的数据验证。但是,请确保在数据访问代码中使用 SQL 参数,这些参数验证输入参数的类型和长度。下一部分将讨论 SQL 参数的用法。
SQL 注入
当应用程序使用输入内容来构造动态 SQL 语句以访问数据库时,会发生 SQL 注入攻击。如果代码使用存储过程,而这些存储过程作为包含未筛选的用户输入的字符串来传递,也会发生 SQL 注入攻击。SQL 注入可能导致攻击者能够使用应用程序登录在数据库中执行命令。如果应用程序使用特权过高的帐户连接到数据库,这种问题会变得很严重。
注意 传统的安全措施(如使用 SSL 和 IPSec)不能防止 SQL 注入攻击。
防止 SQL 注入
使用下列对策来防止 SQL 注入攻击:
• |
限制输入。 |
• |
使用类型安全的 SQL 参数。 |
限制输入
验证输入内容的类型、长度、格式和范围。如果您不希望获得数值,则不要接受它们。应该考虑输入内容来自何处。如果它来自受信任源,而且您知道已针对该来源执行过彻底的输入验证,则可以选择在数据访问代码中忽略数据验证。如果数据来自不受信任源或者用于深层防御,则数据访问方法和组件应该验证输入。
使用类型安全的 SQL 参数
SQL 中的 Parameters 集合提供类型检查和长度验证。如果您使用 Parameters 集合,则输入内容将被视为文本值,SQL 不会将其视为可执行代码。使用 Parameters 集合还有一个好处,那就是可以强制进行类型和长度检查。超出范围的值会触发异常。这是深层防御的一个有力示例。
要点 SSL 不能防止 SQL 注入。对于任何应用程序来说,如果它在没有正确的输入验证和适当的数据访问技术的情况下访问数据库,都很容易受到 SQL 注入攻击。
尽可能使用存储过程,并使用 Parameters 集合来调用它们。
结合使用 Parameters 集合和存储过程
下面的代码片断阐释了 Parameters 集合的用法:
SqlDataAdapter myCommand = new SqlDataAdapter("AuthorLogin", conn); myCommand.SelectCommand.CommandType = CommandType.StoredProcedure; SqlParameter parm = myCommand.SelectCommand.Parameters.Add( "@au_id", SqlDbType.VarChar, 11); parm.Value = Login.Text;
在本例中,@au_id 参数被视为文本值,而非可执行代码。另外,还针对参数进行了类型和长度检查。在上例中,输入值不能长于 11 个字符。如果数据不遵循由参数定义的类型或长度,就会生成异常。
请注意,使用存储过程不一定会防止 SQL 注入。重要的是结合使用参数和存储过程。如果不使用参数,则在存储过程使用未筛选的输入内容时,它们很容易受到 SQL 注入攻击。例如,下面的代码片断很容易受到攻击:
SqlDataAdapter myCommand = new SqlDataAdapter("LoginStoredProcedure '" + Login.Text + "'", conn);
要点 如果使用存储过程,请确保同时使用参数。
结合使用 Parameters 集合和动态 SQL
如果您不能使用存储过程,仍可以使用参数,如下面的代码片断所示:
SqlDataAdapter myCommand = new SqlDataAdapter( "SELECT au_lname, au_fname FROM Authors WHERE au_id = @au_id", conn); SqlParameter parm = myCommand.SelectCommand.Parameters.Add("@au_id", SqlDbType.VarChar, 11); parm.Value = Login.Text;
使用参数批处理
通常会产生如下误解:如果将几个 SQL 语句连接在一起,以便在单个往返中向服务器发送一批语句,则不能使用参数。但是,如果您能确保参数名不重复,则可以使用这种技术。通过在 SQL 文本连接过程中,向每个参数名中添加一个数字或其他某个唯一值,可以方便地执行此操作。
使用筛选例程
用来防止 SQL 注入攻击的另一种方法是开发筛选例程,以便向具有特殊 SQL 含义的字符添加转义符,如单撇号字符。下面的代码片断阐释了一个用来添加转义符的筛选例程:
private string SafeSqlLiteral(string inputSQL) { return inputSQL.Replace("'", "''"); }
这种例程存在着一定的问题,而且您不应完全依赖它们,因为攻击者可以使用 ASCII 十六进制字符来回避检查。但是应该筛选输入内容,并将其作为深层防御策略的一部分。
注意 不要依赖筛选输入。
使用 LIKE 子句
请注意,如果您使用 LIKE 子句,通配符仍需要转义符。下面的代码片断阐释了这种技术:
s = s.Replace("[", "[[]"); s = s.Replace("%", "[%]"); s = s.Replace("_", "[_]");
身份验证
当应用程序连接到 SQL Server 数据库时,可以在 Windows 身份验证或 SQL 身份验证之间进行选择。Windows 身份验证更安全。如果必须使用 SQL 身份验证(可能由于必须使用许多不同的帐户连接到数据库,并且希望避免调用 LogonUser),请执行其他几个步骤,以尽可能降低额外的风险。
注意 如果使用 LogonUser 创建模拟令牌,需要在 Microsoft Windows 2000 上具有功能强大的“作为操作系统的一部分工作”特权,因此应避免使用此方法。
考虑下面的建议:
• |
使用 Windows 身份验证。 |
• |
保护 SQL 身份验证的凭据。 |
• |
使用最小特权帐户进行连接。 |
使用 Windows 身份验证
Windows 身份验证不通过网络发送凭据。如果对 Web 应用程序使用 Windows 身份验证,请尽可能使用服务帐户或进程帐户(如 ASPNET 帐户)连接到数据库。Windows 和 SQL Server 都必须能够识别您在数据库服务器上使用的帐户。该帐户必须被授予 SQL Server 登录权限,而且需要具有与访问数据库相关的登录权限。
在使用 Windows 身份验证时,必须使用受信任连接。下面的代码片断显示了几个使用 Windows 身份验证的典型的连接字符串。
下例使用面向 SQL Server 的 ADO.NET 数据提供程序:
SqlConnection pubsConn = new SqlConnection( "server=dbserver; database=pubs; Integrated Security=SSPI;");
下例使用面向 OLE DB 数据源的 ADO.NET 数据提供程序:
OleDbConnection pubsConn = new OleDbConnection( "Provider=SQLOLEDB; Data Source=dbserver; Integrated Security=SSPI;" + "Initial Catalog=northwind");
保护 SQL 身份验证的凭据
如果必须使用 SQL 身份验证,请确保凭据不以明文形式通过网络发送,并确保加密包含凭据的数据库连接字符串。
要使 SQL Server 能够自动加密通过网络发送的凭据,请在数据库服务器上安装服务器证书。也可以在 Web 服务器和数据库服务器之间使用 IPSec 加密通道,来确保进出数据库服务器的所有通信的安全。要确保连接字符串的安全,请使用 DPAPI。有关详细信息,请参阅本模块后面配置管理部分中的“确保连接字符串的安全”。
使用最小特权帐户进行连接
应用程序应该通过使用最小特权帐户连接到数据库。如果您使用 Windows 身份验证进行连接,则从操作系统的角度看,Windows 帐户应该是最小特权帐户,而且该帐户应该具有有限的特权和对 Windows 资源的有限的访问能力。另外,无论您使用 Windows 身份验证还是 SQL 身份验证,相应的 SQL Server 登录都应该受到数据库中的权限的限制。
有关如何创建最小特权数据库帐户以及使用 Windows 身份验证将 ASP.NET Web 应用程序连接到远程数据库的选项的详细信息,请参阅模块 19 确保 ASP.NET 应用程序和 Web 服务的安全中的“数据访问”。
授权
如果用户能够检索和操纵特定的数据,就会建立授权过程。有两种方法:数据访问代码可以使用授权来确定是否执行请求的操作,数据库可以通过执行授权来限制应用程序所使用的 SQL 登录的功能。
在授权不足的情况下,用户也许能够看到另一个用户的数据,未授权的用户也许能够访问受限制的数据。要去除这些威胁:
• |
限制未授权的调用方。 |
• |
限制未授权的代码。 |
• |
限制数据库中的应用程序。 |
图 14.3 概述了应该使用的授权点和技术。
![数据访问授权、程序集和数据库](http://img.microsoft.com/china/technet/security/guidance/images/SGFG08703.jpg)
图 14.3
数据访问授权、程序集和数据库
注意数据访问代码如何按照权限需求为调用用户或调用代码授权。代码标识需求是 .NET 代码访问安全性的一个特性。
要为数据库中的应用程序授权,请使用只具有执行选定存储过程权限的最小特权 SQL 服务器登录。除非有特殊理由,否则不应为应用程序授予如下权限:直接针对任何表执行创建、检索、更新、破坏/删除 (CRUD) 操作。
注意 存储过程在数据库系统的安全上下文中运行。尽管您可以通过为应用程序分配对特定存储过程的权限来限制它的逻辑操作,但不能限制存储过程所执行的操作的结果。存储过程是受信任代码。必须使用数据库权限来确保存储过程的接口安全。
限制未授权的调用方
您的代码在连接到数据库之前必须基于角色或标识为用户授权。角色检查通常用在应用程序的业务逻辑中,但是,如果您不能清楚地区分业务逻辑和数据访问逻辑,请对数据库访问方法使用主体权限需求。
以下属性确保只有作为 Manager 角色成员的用户才能调用 DisplayCustomerInfo 方法:
[PrincipalPermissionAttribute(SecurityAction.Demand, Role="Manager")] public void DisplayCustomerInfo(int CustId) { }
如果需要其他授权粒度,并且需要在数据访问方法内部执行基于角色的逻辑,请使用命令性主体权限需求或显式的角色检查,如下面的代码片断所示:
using System.Security; using System.Security.Permissions; public void DisplayCustomerInfo(int CustId) { try { // 用来验证调用方是 manager 的命令性主体权限 // 角色检查 PrincipalPermission principalPerm = new PrincipalPermission( null, "Manager"); // 仅在调用方是“Manager”角色的成员时才执行 // 随后的代码 } catch( SecurityException ex ) { . . . } }
下面的代码片断使用显式的程序设计角色检查来确保调用方是 Manager 角色的成员:
public void DisplayCustomerInfo(int CustId) { if(!Thread.CurrentPrincipal.IsInRole("Manager")) { . . . } }
限制未授权的代码
通过使用 .NET Framework 代码访问安全性(特别是代码标识需求),可以限制能够访问数据访问类和方法的程序集。
例如,如果您只希望由公司或特定开发组织编写的代码能够使用您的数据访问组件,请使用 StrongNameIdentityPermission ,并要求调用程序集具有一个带有指定公钥的强名称,如下面的代码片断所示:
using System.Security.Permissions; . . . [StrongNameIdentityPermission(SecurityAction.LinkDemand, PublicKey="002...4c6")] public void GetCustomerInfo(int CustId) { }
要提取给定程序集的公钥的文本表示形式,请使用下面的命令:
sn -Tp assembly.dll
注意 在 –Tp 开关中使用大写的“T”。
因为 Web 应用程序的程序集是动态编译的,所以对于这些程序集不能使用强名称。这使得很难将数据访问程序集的使用限制在特定的 Web 应用程序上。最佳方法是开发一个自定义权限,并要求该权限来自数据访问组件。完全信任 Web 应用程序(或任何完全受信任代码)可以调用您的组件。但是,部分信任代码只有在被授予了自定义权限之后,才能调用您的数据访问组件。
有关自定义权限的示例实现,请参阅本指南“如何”部分中的如何:创建自定义加密权限。
限制数据库中的应用程序
首选方法是为应用程序用来连接到数据库的 Windows 帐户创建一个 SQL Server 登录权限,然后将 SQL Server 登录映射到数据库中的某个数据库用户。将该数据库用户放在用户定义的数据库角色中,并授予该角色相应的权限。理想情况下,应该只为该角色授予对应用程序所使用的存储过程的执行访问权限。
有关如何配置此方法的详细信息,请参阅模块 19 确保 ASP.NET 应用程序和 Web 服务的安全中的“为 ASP.NET 应用程序配置数据访问权限”。
配置管理
数据库连接字符串是针对数据访问代码主要考虑的配置管理问题。应认真考虑这些字符串的存储位置以及如何保护它们(特别是当它们包括凭据时)。要提高加密管理安全性:
• |
使用 Windows 身份验证。 |
• |
确保连接字符串的安全。 |
• |
使用受限制的 ACL 确保 UDL 文件的安全。 |
使用 Window 身份验证
使用 Windows 身份验证时,系统会为您管理凭据,而且凭据不会通过网络传输。还可以避免将用户名和密码嵌入到连接字符串中。
确保连接字符串的安全
如果您需要使用 SQL 身份验证,连接字符串中将包含用户名和密码。如果攻击者利用 Web 服务器上的源代码泄漏这一漏洞或设法登录到该服务器,则攻击者可以检索连接字符串。同样,能够合法登录到该服务器的任何用户都可以查看它们。使用加密机制确保连接字符串的安全。
加密连接字符串
使用 DPAPI 加密连接字符串。使用 DPAPI 加密时,由于加密密钥由平台进行管理,并且绑定到特定的计算机或 Windows 用户帐户,因此可避免出现加密密钥管理问题。要使用 DPAPI,必须通过 P/Invoke 调用 Win32 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(英文)。
安全地存储加密的连接字符串
加密的连接字符串可以放在注册表中,也可以放在 Web.config 或 Machine.config 文件中。如果您使用 HKEY_LOCAL_MACHINE 下的注册表项,请将下面的 ACL 应用于该项:
管理员:完全控制 进程帐户:读取
注意 该进程帐户由运行数据访问程序集的进程来确定。这通常是 ASP.NET 进程,或者当您的解决方案使用企业服务中间层时,该进程是企业服务服务器进程。
还可以考虑使用 HKEY_CURRENT_USER,该注册表项提供受限制的访问。有关详细信息,请参阅模块 7 构建安全的程序集中的“注册表”部分。
注意 如果您使用 Microsoft Visual Studio® .NET 数据库连接向导,连接字符串将以明文属性值形式存储在 Web 应用程序代码隐藏文件或 Web.config 文件中。这两种方法都应该避免使用。
为了更容易部署,您可能希望将加密的字符串存储在 Web.config 中,尽管这可能不如使用受限制的注册表项安全。在本例中,将使用如下所示的自定义 <appSettings> 名称-值对:
<configuration> <appSettings> <add key="connectionString" value="AQA..bIE=" /> </appSettings> <system.web> ... </system.web> </configuration>
要从 <appSettings> 元素访问密码文本,请使用如下所示的 ConfigurationSettings 类:
using System.Configuration; private static string GetConnectionString() { return ConfigurationSettings.AppSettings["connectionString"]; }
不要将 Persist Security Info 设置为“True”或“Yes”
如果在连接字符串中包括 Persist Security Info 属性,将导致 ConnectionString 属性在密码返回给用户之前,将密码从连接字符串中去除。在建立与数据库的连接之后,默认设置 false(等同于忽略 Persist Security Info 属性)会丢弃该信息。
使用受限制的 ACL 确保 UDL 文件的安全
如果您的应用程序结合使用外部通用数据链接 (UDL) 文件和面向 OLE DB 的 ADO.NET 托管数据提供程序,请使用 NTFS 权限来限制访问。使用以下受限制的 ACL:
管理员:完全控制 进程帐户:读取
注意 UDL 文件未进行加密。一个更安全的方法是,使用 DPAPI 加密连接字符串,并将其存储在受限制的注册表项中。