`

了解 .NET Framework 2.0 中“代码访问安全性”(CAS) 的新特点

 
阅读更多

Mike Downen

本文基于 .NET Framework 2.0 的预发布版本撰写而成。文中包含的所有信息均有变更可能。

本文将介绍以下内容:

CAS 概述

沙箱技术与信任级别

开发宿主与框架

AppDomain 与安全性

本文涉及以下技术:

.NET Framework 2.0、Visual Studio 2005

*
本页内容
为何采用 CAS? 为何采用 CAS?
了解沙箱权限 了解沙箱权限
宿主和框架 宿主和框架
AppDomain 与安全性 AppDomain 与安全性
定义沙箱 定义沙箱
如何托管 如何托管
CAS 和框架 CAS 和框架
安全透明代码 安全透明代码
使用透明性 使用透明性
小结 小结

Microsoft® .NET Framework 采用了多种安全性技术:例如,基类库 (BCL) 和 ASP.NET 中基于角色的安全性、BCL 中的 Cryptography 类,以及新增的对使用访问控制列表 (ACL) 的支持功能,这些仅仅是其中的几种。代码访问安全 (CAS) 是公共语言运行库 (CLR) 提供的 .NET 安全系列技术之一。本文将探讨 CAS 在 .NET 安全性中的作用以及 .NET Framework 2.0 中 CAS 的一些主要的新增功能及变化。

大多数使用 .NET Framework 的开发人员只要知道 CAS 的存在即可,而无需进行详细了解。如同浮点数一样,作为 Framework 的一个特色功能,CAS 对于某些应用场合来说非常有必要深入详细地了解,但对于大多数应用场合则不必如此。

为何采用 CAS?

人们向我询问有关访问控制的问题时,通常是想更多地了解基于角色的安全性,即基于用户身份控制资源的访问。CAS 理解起来可能很难,因为它不是基于用户身份,而是基于所运行代码的身份实现安全保护,其中包括代码来源(例如来自本地计算机或 Internet)、代码构建者以及代码签名者等信息。系统根据与代码及其身份相关联的“证据”,来限制代码能够执行哪些操作、访问哪些资源。

为什么要基于代码身份对代码的活动进行限制?通常情况下是不需要这样做的。例如,假设您在运行一种高级而昂贵的图形编辑程序,允许它访问您(作为运行程序的用户)能够访问的计算机上的任何资源(文件、注册表设置等等)。这时,您了解并信任软件的发布者,并愿意允许软件对运行它的平台进行高级别的访问。虽然为了避免计算机被意外损坏,您通常希望应用程序运行时使用的用户帐户具有尽可能低的权限,但在上述情况下,应用程序本身的信任级别并不是安全性的考量点。

但在有些情况下,您需要运行代码,却并不了解或者不完全信任它的作者。目前在浏览 Web 时最常遇到此类情况。现代的浏览器经常要运行来自所访问的 Web 站点的代码。这些代码通常是 JavaScript 或 DHTML,但也可能是浏览器能够识别的其他形式的可执行文件。当浏览器在计算机上运行这些代码时,会进入到沙箱环境中。沙箱是用于对代码有权访问的计算机资源进行控制的受限执行环境。沙箱是必须使用的;否则,来自 Internet 的相对匿名的代码就可能会每天读取您的私人文件并将病毒安装到您的计算机上。许多浏览器基于所运行代码的来源提供不同级别的沙箱处理。例如,Microsoft Internet Explorer 定义了区域的概念。与来自“受信”站点区域的代码相比,来自 Internet 区域的代码能够执行的活动相对较少。当实施沙箱规则的软件存在缺陷时,或者用户在被欺骗的情况下批准在沙箱外执行代码时,就会出现安全漏洞。

对于沙箱处理和基于代码身份的安全性而言,在浏览器中运行来自 Web 站点的代码是常见情况。CLR 中的 CAS 提供了基于代码身份进行代码沙箱处理的常规机制。这是 CAS 的主要优势及主要用途。如今,Internet Explorer 使用 CAS 对浏览器中运行的来自 Web 站点的托管代码(控件或独立的应用程序)进行沙箱处理。CAS 也可用来对从本地 intranet 网络共享位置运行的应用程序进行沙箱处理。此外,Visual Studio® Tools for Office (VSTO) 还将它作为 Microsoft Office 文档托管加载项的宿主。就服务器而言,ASP.NET 将它用于 Web 应用程序,SQL ServerTM 2005 将它用于托管存储过程。CAS 在许多情况下都会弹出,这通常发生在后台。

了解沙箱权限

开发人员经常会问他们需要了解 CAS 的哪些方面。对于应用程序开发人员,答案取决于编写的应用程序(如 Internet Explorer 中的托管控件)是否要在沙箱中运行。如果是在沙箱中运行,则需要了解以下内容:

您可以在沙箱中执行哪些操作

这些权限是否足以让应用程序成功运行

如果必要,如何提升应用程序的信任级别,以获取更多权限

例如,浏览器中的托管控件使用一组默认的沙箱权限:Internet 权限集。就资源访问而言,此沙箱允许应用程序创建“安全的”用户界面元素(例如,透明窗口被视为非安全元素,因为它们可以用于实施电子欺骗或中间人攻击)、使 Web 连接返回其起始站点、在用户同意的情况下访问文件系统及打印机、在隔离存储区中存储有限的数据(类似于 Internet Explorer 的 cookie)。它不允许应用程序读取文件系统或注册表的随机部分或环境变量。沙箱既不允许应用程序与 SQL Server 数据库建立连接,也不允许调用 COM 对象或其他非托管代码。不同的宿主可以定义不同的沙箱。例如,SQL Server 和 ASP.NET 可以针对其低信任级应用程序定义各自不同的权限集。图 1 概述了这些沙箱权限。

一旦了解了沙箱的各种选项,您就可以确定某个沙箱应用程序是否符合您的需要。例如,在编写 Internet Explorer 中的托管控件时,如果打算与 SQL 数据库连接,那么默认情况下您的控件是不能在沙箱中运行的。您要么改变计划(例如使用部署在控件服务器上的 Web 服务访问数据),要么提升控件在客户计算机上的信任级别。对于浏览器中的托管控件,要提升信任级别,需要部署 CAS 策略,使控件的来源站点和控件本身都得到信任。其他宿主具有不同的应用程序受信机制。要更改 ASP.NET 应用程序的信任级别,必须更改配置文件中的设置。

在某些情况下,您的应用程序必须在沙箱中运行,因为用户不太可能以其他方式安装和运行它。假定您正在为某个 Web 站点编写简单的调查控件。该控件向用户提出几个问题,并记录下用户的反馈信息。大多数人不会为了如此简单的事情下载并运行完全信任的应用程序。但有时候,尽管您编写的是受信的应用程序,但让它在沙箱中运行仍然是十分有益的。图 2 显示了有沙箱处理和无沙箱处理情况下的信任级别。如果应用程序必须在完全信任的情况下运行,请确保它在部分信任的环境中仍能良好运行 [有关详细信息,请参阅提要栏“妥善应对部分信任”(英文)]。

通常,部署沙箱应用程序要容易得多。例如,利用 ClickOnce,沙箱应用程序只需运行即可,无需用户的任何操作,而高信任级别的应用程序则或者必须使用信任证书签名,或者向用户显示提示 [有关 ClickOnce 的详细信息,请参阅 ClickOnce:使用中央服务器部署和更新智能客户端项目(英文)和摆脱 DLL Hell:使用 ClickOnce 和免注册 COM 简化应用程序部署(英文)]。

对应用程序的沙箱处理还可以提高可靠性。例如,在最低信任级别的 SQL Server 中运行尽可能多的托管存储过程,意味着将减少具有完全服务器访问权限的代码,进而也将减少可以(有意或无意地)破坏服务器的代码。

在支持构建使用 ClickOnce 部署的沙箱客户端应用程序方面,Visual Studio 2005 有了很大的改进。项目属性页的“安全性”窗格是执行大多数操作的地方(请参阅图 3)。可以在此处启用安全性设置,并为应用程序设置目标区域。对于沙箱应用程序,通常选择 Internet 区域。然后,可以使用 Calculate Permissions(计算权限)按钮粗略估计应用程序所需的权限及其是否适合在沙箱中运行(此计算功能也可通过 .NET Framework 2.0 SDK 中的 permcalc 命令行工具获得)。

a

3 Visual Studio 2005 安全性窗格

需要清楚的是,计算结果只是根据纯静态分析得到的一个粗略(通常是保守的)估计。必须使用应用程序运行所用的权限对其进行测试。为此,只需在设置目标区域后调试应用程序即可。Visual Studio 将以您在安全性窗格中指定的权限运行应用程序。

宿主和框架

如果要编写宿主或框架,则对沙箱的了解远不止于以上的基础知识。宿主开发人员需要知道如何托管低信任级别代码。框架开发人员需要知道如何编写允许最低信任级别代码访问各项功能的框架或库。

但在讨论这些问题以前,我想先探讨一个引起普遍困惑的问题,即使用最低权限运行代码。使用最低权限运行代码是指使用完成作业所需的最小权限运行代码。这样可以在代码中存在可利用的漏洞时限制可能造成的损失。为保证 Windows® 的安全性,尽可能使用具有最低权限的用户身份(例如普通用户,而非管理员用户)运行代码始终不失为上策。对于 CAS,使用最低权限运行意味着运行代码时使用应用程序或库执行任务所需的最小 CAS 权限集。尽管使用最低权限运行代码通常是非常好的做法,但它也有其局限性。例如,几乎所有的宿主或框架代码都必须使用一些默认沙箱之外的强大权限才能运行。此类代码需要频繁地调用 Win32® API 或机器上的其他托管代码和控件资源,如文件、注册表项、进程及其他系统对象。由于此类代码已经要求了很高的权限,又鉴于许多强大的权限可以提升至完全信任级别,因此从安全角度来说,尝试去除一个或两个权限通常得不偿失。

您可以将时间更有效地用在审计和测试代码上,以确保代码在面对恶意的调用者时是安全的。(在 CAS 环境中,使用完全信任级别运行意味着:运行代码的用户可以在系统上执行哪些操作,代码就可以执行哪些操作。如果运行完全信任代码的进程没有管理员权限,那么即使是完全信任的代码也不能无限制地访问机器的资源。)为了更加简便,.NET Framework 2.0 新增了一种称为安全透明代码的新技术。我将在稍后将这项技术与框架开发的其他问题放在一起探讨。不过现在,先让我们回到托管问题上来。

一般而言,托管是指在其他应用程序环境中使用 CLR 执行代码。例如,SQL Server 2005 托管运行时程序,来执行以托管代码形式编写的存储过程。这里,我将从回顾 AppDomain 入手,重点讨论托管在安全性方面的作用。当然,托管的作用远不止于此。有关更详细的信息,请参阅 Stephen Pratschner 编著的定制 Microsoft .NET Framework 公共语言运行库(英文)(Microsoft Press®, 2005)。

AppDomain 与安全性

AppDomain 为托管代码提供子进程隔离。也就是说,每个 AppDomain 都有自己的状态集。一个 AppDomain 中的可验证代码不能干扰另一个 AppDomain 中的代码或数据,除非由托管环境创建的接口允许它们进行交互。这是如何实现的呢?可验证的类型安全代码(默认情况下由 C# 和 Visual Basic® .NET 编译器产生)不能任意访问内存。每个指令都由运行时使用一组验证规则进行检查,以确保指令以类型安全的方式访问内存。因此,当运行可验证代码时,运行时可以保证 AppDomain 的隔离,而在代码不可验证时则可以阻止其运行。

这一隔离功能使宿主可以在同一进程中以不同的信任级别安全地运行代码。低信任级别的代码与受信宿主代码或其他低信任级别的代码可以运行于不同的 AppDomain 中。托管低信任级别代码所需的 AppDomain 数取决于宿主的隔离语义。例如,对于托管控件,Internet Explorer 会为每个站点建立一个 AppDomain。来自同一个站点的多个控件可以在相同的 AppDomain 中交互,但不能干扰(或恶意利用)来自另一个站点的控件。

a

4 AppDomain 防火墙

CAS 允许在加载代码时指定代码的信任级别(通过策略将证据映射到权限)。此外,CAS 还允许在创建 AppDomain 时指定其信任级别。在 .NET Framework 1.1 中,可以通过为 AppDomain 设置证据来达到这一目的。此证据通过策略映射到权限集。每当控制流过渡到 AppDomain 时,如同程序集的权限许可一样,AppDomain 的权限许可会被推至栈中。当进行堆栈审核时,会考虑 AppDomain 的权限许可,来评估来自该 AppDomain 的请求。这种机制称为 AppDomain 防火墙(请参见图 4),它可以防止跨 AppDomain 的诱饵式攻击,并提供了另一种隔离机制来隔离 AppDomain 间的代码。

CAS 允许以不同信任级别(权限)将代码(程序集)载入 AppDomain,但不建议采用这种方式对代码进行安全沙箱处理。因为如果某个 AppDomain 中的所有代码共享状态,则很难防止该 AppDomain 内多个程序集间的权限提升。例如,假设您加载一个仅具有执行权限的程序集和另一个具有 LocalIntranet 权限集的程序集。前者(仅以执行权限运行)可能会提升至 LocalIntranet 权限集,除非在编写该代码时谨慎考虑了如何应对这一情况。而且您也享受不到 AppDomain 防火墙的好处。因此如前所述,创建多个 AppDomain 来托管需要隔离的代码是非常有用的方法,即使代码在相同信任级别下运行(例如 Internet Explorer 对来自不同站点的控件使用相同信任级别)也是如此。

因此,我不建议将多个程序集以不同信任级别加载到单个 AppDomain 中,而是建议对每个低信任级别代码的逻辑区块(例如,Internet Explorer 中的每个站点)使用单独的 AppDomain,并简化安全模型。每个 AppDomain 都应有两个信任级别:

第一信任级别是完全信任平台代码,它包括 .NET Framework 和与低信任级别代码交互的宿主信任代码。

第二信任级别是低信任级别代码本身,它以单一信任级别而不是混合信任级别运行。

a

5 AppDomain 信任

此模型更易于理解,同时也更安全。这是 ClickOnce 之所以使用它的一个原因。对于任何 ClickOnce 应用程序,在 AppDomain 中总有两个信任级别的代码运行:以完全信任级别运行的平台代码和以在应用程序清单中指定的信任级别运行的应用程序代码。CLR 安全系统已针对此模式进行了优化,因此,符合此模型的代码将运行良好。有关两种信任级别的说明,请参见图 5。

定义沙箱

托管的另一个任务是定义要授予低信任级别代码的沙箱或权限集。如前所述,如同其他宿主(如 ASP.NET)一样,CLR 也会为沙箱定义某些权限集。由 CLR 定义的两个相关权限集是 Internet 权限集和 LocalIntranet 权限集。Internet 权限集用于确保运行匿名、有恶意可能的代码的安全性。针对这一目的,Microsoft 已对它进行了审核,它也是唯一针对这一情况进行了全面测试的权限集。因此,它应该成为托管低信任级别代码的默认选择。而 LocalIntranet 权限集虽然可以防止恶意代码控制计算机,但它会泄露用户不希望匿名代码获得的某些信息,如当前登录用户的用户名等等。因此,实际上它更适用于在一定程度上可信的代码,如贵组织 intranet 上的代码。

定义了自己的权限集的宿主(如 ASP.NET)又如何?例如,ASP.NET 中等信任级别使用 CAS 和 Windows 基于身份的安全技术对代码进行限制,从而提供安全性。我们团队的一个成员针对此情况发明了很贴切的词:沙丘处理。“沙丘”是一组受限权限,它本身并不会阻止恶意代码对计算资源进行未授权的访问。但是,它在与其他安全执行机制组合后以及在保持可信代码的可信性方面很有用。如果多个应用程序共享计算资源,而您要防止某个不良运行的应用程序占用服务器,为了获得可靠性,通常要采用这一技术。

如何托管

这些要如何实现呢?在 .NET Framework 1.1 中,创建一个 AppDomain,确保传入证据。此证据将用于计算 AppDomain 的权限。然后,设置 AppDomain 策略级别,用以向平台和宿主代码授予完全信任级别,向所有其他代码授予低信任级别。接着,在 AppDomain 上设置 AppDomain 策略级别。最后,调用新 AppDomain 中的受信宿主代码,以自举运行低信任级别代码。

在 .NET Framework 2.0 中,此过程得到了简化:

1.

使用简单的新沙箱处理 API 创建 AppDomain。只需这一步,即可在不创建 AppDomain 策略级别的情况下,设置 AppDomain 信任级别、沙箱权限集以及受信宿主程序集的列表。

2.

调用新 AppDomain 中的受信宿主代码,以自举运行低信任级别代码。

有关这些主题的全部详细信息,请参阅 Shawn Farkas 在本期发表的文章。Shawn 介绍了如何实现上述操作,并讨论了安全实现上述操作的所有技巧。虽然对于许多情况而言,简单的沙箱处理模型足矣,但您可以通过 AppDomainManager 类进一步控制 AppDomain 的创建。Shawn 的文章介绍了如何在需要时使用 AppDomainManager 实现自定义的策略行为。

CAS 和框架

现在,我要换个话题,讨论另一个开发人员遇到的与 CAS 有关的情况,即框架构建。框架是可复用类的库,这些类专供其他应用程序使用。框架可以是具有几种类型的一个程序集,也可以是具有多种类型的一大组程序集(例如 .NET Framework)。框架通常在“全局程序集缓存”(GAC) 中部署。这样,这些框架就可以由多个应用程序使用。它们是以完全信任级别运行的平台组件,可以使用系统提供的所有功能。此主题与宿主开发人员也有关,因为他们通常必须至少构建一个与托管代码交互的受限框架(如果不是成熟的框架)。

如果框架开发人员希望将功能提供给非完全信任级别的代码,就必须了解 CAS。以构建一个声音数据库为例。您可以定义一个自定义的权限(如 SoundPermission),在调用播放声音的方法时请求该权限。如果请求成功,您将声明权限以调用未托管代码,然后调用 Win32 API 以播放声音。

另一种更常见的情况是在构建框架时与现有系统权限交互。例如,假设您要实施一个可从低信任级别代码访问的数学库。如果您的库仅进行计算,而不访问任何系统资源,则它基本可以忽略 CAS。但是假设在数学库的初始化过程中,您通过读取某些环境变量来帮助决定如何对计算进行优化。您就需要审核该代码,以确保在低信任级别代码环境下的调用是安全的。对于低信任级别代码,调用必须是完全不透明的,也就是说低信任级别代码不能控制检查哪些环境变量,也不能获取环境变量的值。为了在低信任级别环境下成功实现,您必须声明环境权限才能读取环境变量。

声明是权限的提升。当框架代码执行声明后,它将可以执行调用者通常无权执行的操作(但框架必须已经有该权限以及执行声明的权限)。当框架代码执行声明时,必须仔细审核该代码才可确保声明的安全性。这通常意味着要执行这些检查:检查传入的参数确保它们已通过验证并且是规范的(如果适用);确保没有不适当的数据泄漏回低信任级别代码;保证低信任级别代码不会通过不适当地更改高信任级别代码的状态,来诱骗它在以后执行不安全的操作(称为诱饵式攻击)。声明通常非常易于发现。可以通过搜索源代码来找到这些声明,声明也有一些相关的 FxCop 规则。

满足链接请求是一种更不易发现、因而也更具潜在危险的权限提升。当某个方法含有链接请求时,如果该方法是实时 (JIT) 编译的,则将对其直接调用者进行检查。因此,这实际上是一级请求。它可能是危险的,因为受信代码可以满足某个链接请求,然后转过来在未意识到其满足了链接请求的情况下,将该功能提供给低信任级别代码。为确保安全性,受信代码可以尝试的一个策略是:避免执行任何声明,而只是让所有请求流至调用者。但是对于链接请求,受信代码执行的是隐式声明。为确保完全安全,受信代码还必须将所有链接请求转换为完全请求。虽然有一些 FxCop 规则能够帮助查找满足链接请求的代码,但您无法直观地审核代码,以了解其是否满足链接请求。这会使代码的审核工作更容易出错。

安全透明代码

透明性是 .NET Framework 2.0 的一个新特色,它可以帮助框架开发人员编写能够更安全地将功能提供给低信任级别代码的库。可以将整个程序集、程序集中的某些类或某个类中的某些方法标记成安全透明的。安全透明代码不能提升权限。这有三个具体的规则:

安全透明代码不能执行声明

将由安全透明代码满足的链接请求变为完全请求

必须在安全透明代码中执行的非安全(不可验证)代码将引发对跳过验证安全权限的完全请求

这些规则在执行期间由 CLR 实施。一般而言,安全透明代码将其调用的代码的所有安全请求传递给其调用者。请求只是流过代码,代码并不能将权限提升。因此,如果低信任级别的应用程序调用某些会引发高权限请求的安全透明代码,则该请求将流至低信任级别代码并失败。即使该请求想要停止,安全透明代码也不能将其停止。而从完全信任代码调用的相同安全透明代码则会引发成功的请求。

透明性有三个属性,大致情况如图 6 所示。使用透明性时,将代码加入安全透明方法和安全关键方法(与安全透明方法相对)。用于处理数据操作和逻辑的大部分代码通常都可以标记为安全透明的,而实际执行权限提升的一小部分代码将被标记为安全关键的。到目前为止,Microsoft 内采用透明性的小组可以将超过 80% 的代码标记为安全透明的。这样,他们就可以集中精力对 20% 的安全关键代码进行审核和测试。为了实现向后兼容,.NET Framework 2.0 及先前版本中所有未标注透明属性的代码均被视为安全关键代码。现在,您必须选择采用透明性。关于透明性,还有一些 FxCop 规则,可以帮助开发人员在初期构建代码时就能确保透明性规则的正确性,而不必在以后对运行时错误进行调试。

开始时,使用 SecurityTreatAsSafe 属性的理由可能并不明显。可以将程序集内的安全透明代码和安全关键代码看作实际上是分隔到了两个程序集中。安全透明代码无法看到安全关键代码的私有或内部成员。此外,在安全关键代码访问公共接口时通常要对安全关键代码进行审核。您不会希望在程序集外能够访问私有或内部状态,您会希望状态始终是隔离的。因此,为了实现安全透明代码与安全关键代码之间的状态隔离,而在必要时又能够覆盖,引入了 SecurityTreatAsSafe 属性。安全透明代码不能访问安全关键代码的私有或内部成员,除非这些成员以 SecurityTreatAsSafe 标记。在添加 SecurityTreatAsSafe 之前,关键代码的作者应审核该成员,就好像它是公开的一样。

使用透明性

我将以数学库为例说明透明性的语法。首先,要选择透明性,您必须将 SecurityCritical 属性作为程序集级属性添加:

using System.Security;

//选择低信任级别调用者
[assembly:AllowPartiallyTrustedCallers]
//选择透明性,但某些代码为关键代码
[assembly:SecurityCritical]

现在,默认情况下,程序集中的所有类型都是安全透明的。但是,假设数学库类的构建函数需要声明才能读取环境变量,而库中的所有其他方法可以是安全透明的。类定义将如图 7 所示。

透明性允许您构建这样的框架:在这种框架中,大部分代码在其调用者环境中运行,同时明确标记出哪些代码可以提示权限。这样,您可以集中精力解决最敏感代码的安全性(随着代码库的扩大,这一点变得非常重要),也可以降低暴露给低信任级别调用者的代码的构建和维护成本。

小结

上面讨论了默认沙箱的诸多可能性。希望托管低信任级别代码或扩展平台的开发人员必须了解关于 CAS 的更多信息。利用 .NET Framework 的新增功能可以更容易地托管低信任级别代码、更安全地扩展平台。随着托管代码越来越广泛的应用,CAS 也在不断演进,使低信任级别代码的沙箱处理更容易,使开发人员能够构建更安全的平台 [有关 .NET Framework 2.0 中有关 CAS 的其他变化,请参阅提要栏“CAS 的其他新特色”(英文)]。.NET Framework 2.0 的 CAS 旨在使更多开发人员能够充分利用这些方案,实现更安全的扩展。

Mike Downen 是 CLR 团队负责安全性的项目经理。他负责“代码访问安全性”、cryptography 类及 ClickOnce 安全模型方面的工作。您可以在 blogs.msdn.com/CLRSecurity 上阅读 Mike 的 Blog 并与他联系。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics