日期:2009-11-28  浏览次数:20473 次

前提条件:读者应当熟悉公共语言运行库和 Microsoft(R) .NET 框架,以及基于证据的安全性和代码访问安全性的基本知识。



基于证据的安全性和代码访问安全性

结合使用两项单独的技术来保护托管代码:

• 基于证据的安全性决定将什么权限授予代码。

• 代码访问安全性负责检查堆栈上的所有代码是否拥有执行某项操作的所需权限。

权限将这两个技术绑定在一起:权限是执行某个特定受保护操作的权利。例如,“读取 c:\temp”是一个文件权限;“连接到 www.msn.com”是一个网络权限。

基于证据的安全性决定授予代码的权限。证据是有关用作安全策略机制输入的任何程序集(授予权限的单位)的已知信息。假如将证据作为输入,系统将评估由管理员设置的安全策略,以决定可以将什么权限授予代码。代码本身可以使用权限请求来影响被授予的权限。权限请求被表示为使用自定义属性语法的程序集级别的声明性安全性。但是,代码不能以任何方式获取多于或少于策略系统所允许的权限。权限授予只发生一次,指定程序集中所有代码的权利。要查看或编辑安全策略,请使用 .NET 框架配置工具 (Mscorcfg.msc)。

下表列出了策略系统用来向代码授予权限的某些常见证据类型。除了这里列出的标准证据类型(它们是由安全系统提供的)以外,还可以使用用户定义的新类型来扩展证据集合。

证据 说明

哈希值

程序集的哈希值

出版商

AuthentiCode(R) 签名者

强名称

公钥+名称+版本

站点

代码来源的 Web 站点

Url

代码来源的 URL

区域

代码来源的 Internet Explorer 区域

代码访问安全性负责处理执行所授予权限的安全检查。这些安全检查的独特方面是,它们不仅会检查试图执行受保护操作的代码,而且会沿堆栈检查它的所有调用方。要让检查获得成功,所有被检查的代码都必须具有所需权限(可以重写)。

安全检查是有好处的,因为它们可以防止引诱攻击。引诱攻击是指未经授权的代码调用您的代码,并引诱您的代码代替未经授权的代码执行某些操作。假设您有一个读取文件的应用程序,并且安全策略将读取文件的权限授予了您的代码。因为您的所有应用程序代码都拥有权限,所以会通过代码访问安全性检查。但是,如果无权访问文件的恶意代码以某种方式调用了您的代码,那么安全检查将失败,这是因为不受信任的代码调用了您的代码,从而在堆栈上可见。

需要注意的是,该安全性的所有方面都是基于允许代码执行什么操作这一机制的。基于登录信息对用户进行授权是基础操作系统完全独立的安全功能。请将这两个安全系统看作多层防御:例如,要访问一个文件,必须要通过代码授权和用户授权。虽然在许多依赖用户登录信息或其他凭据来控制某些用户可以和不可以执行某项操作的应用程序中,用户授权很重要,但这种类型的安全不是本文讨论的重点。

安全编码的目标

我们假设安全策略是正确的,并且潜在的恶意代码不具有授予信任代码的权限,该权限允许受信任代码安全地执行功能更强大的操作。(如果进行其他假设,将使一种类型的代码无法与其他类型的代码区分开,从而使问题不会发生。)使用强制 .NET 框架的权限和代码中实施的其他措施时,您必须建立障碍来防止恶意代码获得您不希望它得到的信息,或防止它执行恶意的操作。此外,在受信任代码的所有预期情况中,必须在代码的安全性和可用性之间找到一种平衡。

基于证据的安全策略和代码访问安全性为实现安全性提供了非常强大的显式机制。大多数应用程序代码只需要使用 .NET 框架实现的基础结构。在某些情况下,还需要其他特定于应用程序的安全性,该安全性是通过扩展安全系统或使用新的特殊方法生成的。

安全编码的方法

这些安全技术的一个优点是,您通常可以忘记它们的存在。如果将代码执行任务所需的权限授予代码,那么一切将正常工作(同时,您将能够抵御潜在的攻击,例如,前面描述的引诱攻击)。但是,在某些特定情况下,您必须显式地处理安全问题。下面的几个部分描述了这些方法。即使这些部分并不直接适用于您,但了解这些安全问题总是有用的。

安全中立代码

安全中立代码不对安全系统执行任何显式操作。它只使用获得的权限来运行。尽管无法捕获受保护操作(例如,使用文件、网络等)的安全异常会导致不良的用户体验(这是指包含许多细节的异常,但对大多数用户来说这些细节是完全模糊的),但这种方式利用了安全技术,因为即使是高度受信任的代码也无法降低安全保护的程度。可能发生的最坏情况是,调用方将需要许多权限,否则会被安全机制禁止运行。

安全中立库具有您应当了解的特殊特征。假设此库提供的 API 元素需要使用文件或调用非托管代码;如果您的代码没有相应的权限,将无法按描述运行。但是,即使该代码拥有权限,调用它的任何应用程序代码也必须拥有相同的权限才能运行。如果呼叫代码没有正确的权限,那么安全异常将作为代码访问安全性堆栈审核的结果出现。如果可以要求调用方对库所执行的所有操作都拥有权限,那么这将是实现安全性的简单且安全的方式,因为它不涉及危险的安全重写。但是,如果想让调用库的应用程序代码不受权限要求的影响,并减少对非常强大的权限的需要,您必须了解与受保护资源配合工作的库模型,这部分内容将在本文的“公开受保护资源的库代码”部分中进行描述。

不属于可重用组件的应用程序代码

如果您的代码是不会被其他代码调用的应用程序的一部分,那么安全性很简单,并且可能不需要特殊的编码。但请记住,恶意代码可以调用您的代码。虽然代码访问安全性机制可以阻止恶意代码访问资源,但此类恶意代码仍然可以读取可能包含敏感信息的字段或属性的值。

此外,如果您的代码可以从 Internet 或其他不可靠的来源接受用户输入,则必须小心恶意输入。

有关详细信息,请参阅本文的确保状态数据的安全和用户输入。

本机代码实现的托管包装程序

通常在此情况下,某些有用的功能是在本机代码中实现的,并且您想在不改写它的情况下将其用于托管代码。托管包装程序很容易编写为平台调用或使用 COM 互操作。但是,如果您这样做,包装程序的调用方必须拥有非托管代码的权利,调用才能成功。在默认策略下,这意味着从 Intranet 和 Internet 下载的代码将不会与包装程序配合工作。

更好的方法是将这些非托管代码权利只授予包装程序代码,而不要授予使用这些包装程序的所有应用程序。如果基础功能很安全(不公开任何资源)并且实现也很安全,则包装程序只需要断言它的权利,这将使任何代码都能够通过它进行调用。如果涉及到资源,则安全编码应与下一部分中描述的库代码情况相同。因为包装程序向调用方潜在地公开了这些问题,所以需要对本机代码的安全性进行仔细验证,这是包装程序的责任。

有关详细信息,请参阅本文的非托管代码和评估权限部分。

公开受保护资源的库代码

这是功能最强大因此也是潜在最危险(如果方法不正确)的安全编码方式:您的库充当了其他代码用来访问特定资源(这些资源以其他方式是不可用的)的接口,正如 .NET 框架类对它们所使用的资源施加权限一样。无论在哪里公开资源,代码都必须先请求适用于资源的权限(即,执行安全检查),然后通常需要断言它执行实际操作的权利。

有关详细信息,请参阅本文的非托管代码和评估权限部分。

安全编码的最佳做法

注 除非另行指定,代码示例都是用 C# 编写的。

权限请求是使代码获得安全性的好方法。这些请求可让您做两件事:

• 请求代码运行所必需的最低权限。

• 确保代码所接收的权限不会超过它实际需要的权限。

例如:

[assembly:FileIOPermissionAttribute

(SecurityAction.RequestMinimum, Write="C:\\test.tmp")]

[assembly:PermissionSet

(SecurityAction.RequestOptional, Unrestricted=false)]

…SecurityAction.RequestRefused_

该示例告诉系统:除非代码收到写入 C:\test.tmp 的权限,否则不应当运行。如果代码遇到没有授予该权限的安全策略,那么将引发 PolicyException,并且代码不会运行。您可以确保代码将被授予该权限,并且不必担心由于权限太少而导致的错误。

该示例还告诉系统:不需要其他权限。除此之外,代码将被授予策略选择要授予它的任何权限。虽然额外的权限不会导致损害,但如果某处有安全问题,则拥有较少的