代码访问安全 (CAS) 和设计模式






4.86/5 (57投票s)
2004 年 5 月 11 日
19分钟阅读

165568

426
代码访问安全 (CAS) 和设计模式
引言
要被评定为一名优秀开发者,已不再仅仅是编写可管理且文档齐全的代码,而是根据今天的定义,还包括知道如何编写安全的代码。这比其他所有评估的品质都重要。去年“Web 服务”在技术领域引起轰动,但此后已被“安全性”取代。许多职位都已修改,将安全性作为主要职责。在企业层面的许多层级,都进行了更改以确保生产系统是安全的,并且黑客无法控制企业内的关键应用程序。说到“安全性”,它有许多层次;就像一个洋葱,每剥掉一层,就会显现出另一层,如此往复。要剥开洋葱的核心需要付出巨大的努力和大量的时间投入,安全性也是如此。在今天的这期内容中,我想从安全主题方面剥开最开始的一层。我遗憾自己去年还不知道这项技术,但今年我付出了很多努力去学习、操作和实施解决方案,掌握了这项需求量很大的技能。我的个人努力成果就是我将在下面分享的集体思考和学习心得。我的主要目标是将代码访问安全(简称 CAS)的复杂性转化为简单易懂的英语,并配以生动的图表来加深理解。一图胜千言!
什么是代码访问安全?
过去几年,世界发生了翻天覆地的变化。本周我在首都华盛顿特区,对全市为确保政府和历史古迹安全而采取的措施感到震惊。尽管一些建筑被巨大的花盆和美化的路障围绕,但当我走在街上时,我仍然感到一种被排斥的感觉。这是安全性的完美例子。我查阅了“安全性”一词的含义,找到了以下定义:“免受风险或危险”。说实话,我没想到在一个其核心词本身就包含“自由”一词的定义中看到“自由”这个词。经过一番思考,我还是明白了。几年前,在机场旅行时,乘客和行李不需要接受严格的扫描程序。而且,亲朋好友仍然可以在登机口与乘客告别。现在已经不是这样了;机场的安全性发生了巨大的变化。我们看到的是政府为防止再次发生 9/11 事件而采取的应对措施。作为开发者和计算机用户,我们已经看到了无害的计算机病毒如何演变成专门窃取我们个人信息、破坏系统和控制我们计算机的有害代码。当然,随着我们的政府和主要软件公司推出计算机安全对策,我们开发者也需要密切关注这个问题,这需要我们在自己的应用程序安全方面采取积极主动的方法。微软推出了“值得信赖”计划,该计划不仅包括为其自身产品开发安全代码,还为开发者编写可信代码提供了途径。然而,要编写安全代码,我们需要接受教育,并在开发者开始编写安全代码之前,整个范式转变都需要到位。微软在这方面的教育做得好吗?您可以根据自己的观点来回答;肯定还有很多工作可以做。最近,我参加了一个安全峰会,会上介绍了多个主题。研讨会反复强调的主题是让开发者开始思考开发安全代码所需的行动。如果安全是一系列最终防止我们一次又一次地暴露和易受攻击的行动,那么我们就需要了解黑客的技术和策略。教育在实现这一防御至关重要。
基于角色的安全是 Microsoft Windows 2000/XP 操作系统核心的一部分,但仅靠代码本身而不忽视用户技能和意识是不够的。这种安全模型关心用户如何安全访问资源,并且任何代码通常都在登录用户的凭据下运行。以下是 Windows 用户的一个常见场景:“约翰,一位会计师,需要将一些信息提交给合作伙伴网站,于是他在网页浏览器中输入了合作伙伴的 URL。接下来,他看到一个消息框弹出,上面写着:“为了运行此应用程序,我们需要在您的计算机上安装 ActiveX 控件,您信任我们吗?对约翰来说,这意味着:“今天我是否想提高工作效率?”他当然回答“是”,成功安装了这部分软件,但约翰并不知道该应用程序被授予了访问哪些安全资源的权限。这一切都仅仅因为他同意了一个弹出消息。这种方法有什么问题?我脑海中立刻想到了两点问题:
- 安装的 ActiveX 在约翰的用户安全权限集下运行,或者它几乎可以对系统执行约翰能执行的任何操作(删除、更新文件等)。
- 约翰不知道 ActiveX 做什么,而且最有可能的不是他会去想,但对约翰来说重要的是提高工作效率和确保他的计算机安全。
最近的病毒“Sasser”不需要用户交互即可感染计算机;只需将一台未受保护的机器连接到网络,几分钟内就会被感染。因此,代码访问安全弥补了基于角色的安全不足之处。它提供了基于代码编写者、来源或执行位置(证据)来保护代码的机制。这些证据被映射到权限(权利),这些权限可以由四种不同的策略来管理,这些策略对应于用户代表的角色:
- 域管理员 – 企业策略
- 机器管理员 – 机器策略
- 机器的实际用户 – 用户策略
- 开发人员 – 应用程序域策略
这些策略可以在应用程序部署后进行配置,并且可以随时修改。CAS 引入了一个重要概念——部分受信任的代码,即只被授予执行所需资源的访问权限,而不能更多的代码。放眼全局,代码访问安全和基于角色的安全都支持相同的模式,我称之为“2AR”,如下图所示。
安全身份模式“2AR”包括确定身份然后将其分配到一个组的过程,该组对应于可在安全资源上执行的权限集(权利)。此模式的关键是**加强**对安全资源的访问。加强过程不允许未经授权访问安全资源,而不经过**身份验证**和**授权**的过程。公共语言运行时 (CLR) 通过堆栈遍历来实现这一点,这可以与以下场景进行比较:
“19 岁的少年乔想和朋友们一起喝啤酒,但他不能合法地自己去商店购买,于是他请他年龄较大的朋友帮忙买。尽管比尔(20 岁)年龄更大,但他还不足以在商店购买啤酒,于是他让他的爸爸为他的朋友买。比尔的爸爸知道他在违法,但他还是这么做了。店员检查了比尔的驾照,发现他已超过 21 岁,于是卖给他一包啤酒。”
从乔 -> 比尔 -> 比尔的爸爸到店员的请求链代表了堆栈遍历的软件概念。
在现实世界中,啤酒会卖出,因为只有一次身份检查,并且请求链中的所有成员都不需要向店员出示有效身份证件,只需要比尔的爸爸。从系统的角度来看,乔和比尔代表部分受信任的代码,比尔的爸爸代表完全受信任的代码。当病毒通过引诱其进行肮脏工作来访问安全资源时,正是这种情况。在 .NET Framework 场景中,CLR 会阻止成功的堆栈遍历发生,因为它在链的每个级别都要求提供有效的 ID;因此,如果链中的某个人没有有效的 ID,则所有级别上的请求都会被拒绝。因此,如果 CLR 加强规则应用于现实生活场景,那么乔或比尔就永远得不到啤酒。以下场景将在到达比尔时失败。现实情况是,一些部分受信任的代码(没有完全访问系统的权限)有时需要访问完全受信任的资源,这就是堆栈遍历修改发挥作用的地方。
在上面的图示中,店员**拒绝**任何没有有效 ID(证据)的人卖啤酒,但比尔的爸爸**仅允许**他的儿子和儿子的朋友喝酒,因为他已接近法定年龄。以不正当手段绕过系统既不正确也不合法,仅仅是可能。当比尔拿到啤酒时,他与朋友分享(**断言**或凭证),他们在阳光明媚的日子里一起享用啤酒。代码访问安全提供了这种灵活性,但我们需要意识到它也带来了更大的安全风险;因此,请提前设计您的系统。我演示了如何使用 **Deny()**、**PermitOnly()** 和 **Assert()** 来修改堆栈遍历。这并非完整的修饰符列表。请参阅 .NET Framework SDK 获取更多信息(覆盖安全检查)。我只是想让你初识门径,剥洋葱的其余部分就由你来完成了!!
就代码访问安全及其模式而言,这就是最简单的了。现在,复杂性在于理解 CAS 的术语以及 .NET Framework 的实现。
通过 ASP.NET 实现学习代码访问安全
这个学习过程通常始于发现如何将代码访问安全用于现有应用程序,然后将其应用于您自己的项目。我阅读了大量的资源,但直到我开始使用 Microsoft SharePoint 2004(它扩展了 ASP.NET 体系结构并依赖 CAS 来保护其资源)时,我才真正理解。由于并非所有人都正在使用 SharePoint 服务器开发,微软希望通过此产品尽快占据门户市场。我选择通过 ASP.NET 技术来演示和解释 CAS 的主要概念。
演示代码访问安全最常见的场景是智能客户端应用程序。它已在内网下载,并具有一定的权限(权利),但 ASP.NET 已安装在该计算机上并在本地计算机上运行;因此,此场景并未完全解释 CAS。我们确定代码可以自行执行(智能客户端场景),或者由宿主程序集或非托管代码(IIS 过滤器)托管。这是证据来源的主要区别。
因此,程序集有两种加载方式:
- 用户单击可执行文件,代码执行。
- 宿主程序集通过反射加载您的程序集,或者非托管代码初始化 CLR。
大多数人没有意识到 CLR 不是 Win32 的原生部分;因此,它由非托管代码托管。在智能客户端场景中,程序集可执行文件自行运行,因此会调用默认证据收集过程,如下所示。
CLR 策略评估器会自动收集证据,此时您无法提供自己的证据。应用程序域策略是可选的。策略评估器收集到的内容是一组关于代码的证据。每次通过 JIT 执行代码时,策略评估器都会收集证据并授予一组权限。有 7 种默认证据类型,可以分为两组:
- **程序集证据** – 回答“程序集的作者是谁?”这个问题。例如,所有微软的 CLR 类都用相同的私钥/公钥对签名,这使得 CLR 能够确定代码是由微软开发人员编写的,并授予系统完全信任(完全控制)。
- **宿主证据** – 回答“程序集来自哪里?”这个问题。如果您通过引用 URL 位置启动了一个智能客户端,然后在稍后将可执行文件移动到本地硬盘驱动器,CLR 不会跟踪其位置的历史记录。
当然,像所有事物一样,也可以编写自己的证据类并为您的应用程序提供自定义证据对象。
ASP.NET 非托管 IIS 过滤器托管托管的 ASP.NET 进程,并将所需信息从非托管世界传递到托管进程。此场景是一个例外,很可能如果您需要托管程序集,您将使用托管代码并使用反射来加载程序集。Rockford Lhotka 的业务对象框架有一个很好的实用工具(NetRun),用于智能客户端,它基本上修改宿主智能客户端应用程序的机器策略。
ASP.NET 依赖应用程序域策略为应用程序配置提供额外的灵活性。这就是为什么我在上面的幻灯片中包含 ASP.NET 和 SharePoint。
什么是策略?它是一个配置文件,包含有关代码可以做什么的信息,这取决于代码的证据。应用程序有四种配置级别,基于管理需求。它们如下:
- 企业策略 – 默认设置允许所有代码获得完全信任。
- 机器策略 - 默认配置为授予安装在全局程序集缓存中的程序集完全信任和其他。
- 用户策略 – 如果用户愿意,可以根据这些设置限制其机器。
- 应用程序域策略 – 应用程序的安全配置。
评估不同策略级别之间权限(权利)的过程称为“交集”。交集是一个复杂的算法,用于确定最终的权限授予集。关于策略有两点需要记住:
- 策略基于层次结构;因此,如果策略的顶层未授予代码任何权限(权利),则其下方的策略也无法授予权限。
- 在授予最终权限集之前,所有策略都必须就权限达成一致。
为了简单演示策略如何协同工作,我绘制了以下图表。
此图包含正常应用程序没有的 ASP.NET 应用程序域策略级别。有 5 种默认 ASP.NET 应用程序策略:
- 满
- 高
- 媒体
- 低功耗
- 最小
所有策略都对应于一个物理文件,除了 Full,它有一个内置策略(完全控制)。我可以通过在 Machine.config 中添加条目来创建自己的策略文件,如下所示:
<location allowOverride="true">
<system.web>
<securityPolicy>
<trustLevel name="Full" policyFile="internal"/>
<trustLevel name="High" policyFile="web_hightrust.config"/>
<trustLevel name="Medium" policyFile="web_mediumtrust.config"/>
<trustLevel name="Low" policyFile="web_lowtrust.config"/>
<trustLevel name="Minimal" policyFile="web_minimaltrust.config"/>
<trustLevel name="Minimal_Web" policyFile="web_minimal_Web.config"/>
</securityPolicy>
<!-- level="[Full|High|Medium|Low|Minimal]" -->
<trust level="Full" originUrl=""/>
</system.web>
</location>
您知道 ASP.NET 默认以完全信任模式运行吗?您可以通过修改服务器的 Machine.Config 文件或虚拟目录的 Web.Config 文件中的 trust level 属性来更改它。
<trust level="Medium" originUrl=""/>
这里有一个非常简单的场景,供大家尝试和学习 CAS。
- 在 IIS 服务器的本地机器上创建一个 WebService,并添加一个简单的“SayHi”方法,该方法返回字符串“Hello World”。
- 创建一个 ASP.NET 应用程序,并向您的 WebService 添加 Web 引用。
- 添加一个按钮,将单击事件连接到调用“SayHi”的 WebService 方法,并将返回值显示在标签中。
- 生成并在浏览器中查看。它应该可以正常工作。
- 添加 trust level 属性并将其设置为 Minimal。
- 它还工作吗?
如果您遇到 Security Permission Exception,那么您就做得一切正确。如何解决?策略包含有关代码根据证据可以做什么的信息。通过将 ASP.NET 的应用程序域策略从 Full 更改为 Minimal,我们可以更改代码可以做什么,而这里就是理解代码组 --> 成员资格条件 --> 权限集和权限的开始。
好了,首先是基础知识;代码组是权限(权利)的容器,代码可以拥有这些权限(权利),这取决于作为策略配置文件中 XML 元素的证据。有 7 种默认证据类型,它们与代码组的成员资格条件元素一对一映射,外加一个映射所有代码的成员资格条件。
代码可以根据成员资格条件属于某个代码组,该成员资格条件反映了在程序集加载过程中收集或提供给策略评估器的证据。我们知道在 ASP.NET 应用程序中,代码在虚拟目录中执行;因此,我们可以创建以下代码组来匹配此条件。
代码组可以包含子代码组;因此,可以创建跨匹配组的权限。最常用的代码组类型是 Union (AND) 和 First Match(找到匹配项即停止)。还可以使用 **PolicyStatementAttribute** 来提供机制,阻止策略评估器计算策略层次结构的其余部分。选项是 **Exclusive** 或 **LevelFinal**。请参阅 .NET Framework SDK 获取更多信息。
什么是权限集?权限集是授予代码的所有权限(权利)的组合。有 6 种不可变(预构建)的权限集,我们无法修改它们,这意味着我们可以将它们添加到权限集合或从权限集合中删除。
如果您注意到“Everything”和“ASP.NET”是命名权限集,但它们是可变的,我可以自由地向其中添加或删除权限。我还可以为 XML **PermissionSet** 元素创建自己的名称,并组合权限,如果我希望此权限集拥有它们。
现在,我们回到之前关于 WebService 的场景。您仍然想修复它,还是已经放弃了这个想法?
当然,您想修复它。我当初也想。我发现,为了让 WebService 调用正常工作,我们需要允许 WebHttpRequest/WebHttpResponse 类拥有 WebPermission。如果您查看 web_minimal.config 配置文件,您会发现 ASP.NET 权限集有以下条目:
<PermissionSet class="NamedPermissionSet" version="1" Name="ASP.Net">
<IPermission class="AspNetHostingPermission" version="1" Level="Minimal" />
<IPermission class="SecurityPermission" version="1" Flags="Execution" />
</PermissionSet>
这个权限集没有给我的虚拟目录代码(ASP.NET 应用程序)太多权限。
微软总是喜欢给我们提供多种方法来实现同一件事,这对我们有利。
我们的**第一个选择**是找出哪个 ASP.NET 应用程序域策略文件包含 WebPermission。您可以通过查看策略文件来做到这一点。您会发现 **web_mediumtrust.config** 中的 ASP.NET 权限集包含 WebPermission。
<IPermission class="WebPermission" version="1">
<ConnectAccess>
<URIuri="$OriginHost$"/>
</ConnectAccess>
</IPermission>
所以您需要做的就是将您的 web.config 文件修改为以下内容:
<trust level="Medium" originUrl="https:///.*"/>
现在,当您运行应用程序时,它应该可以顺利工作。**OriginUrl** 用于授予特定 Web 服务器的权限。如果您引用了多个服务器呢?
那么只需在 web_mediumtrust.config 中进行以下条目:
<IPermission class="WebPermission" version="1">
<ConnectAccess>
<URIuri="$OriginHost$"/>
<URIuri="http://server1/.*"/>
<URIuri="http://server2/.*"/>
</ConnectAccess>
</IPermission>
我们的**第二个选择**涉及创建自定义策略。例如,如果我真的想锁定我的服务器并只允许在 **Minimal** 信任级别下运行,那么我需要创建一个基于 **Minimal** 信任级别的自定义策略,然后添加 WebService 执行的权限(权利)。
以下是创建 ASP.NET 应用程序域策略的步骤,该策略具有最小信任级别并允许调用 WebServices:
- 导航到 C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\CONFIG 或 .NET Framework 在您的机器上安装的位置。
- 在默认 ASP.NET 信任级别下方打开 Machine.config 文件,并添加 <trustLevel name="Minimal_Web" policyFile="web_minimal_Web.config" />。
- 创建 web_minimaltrust.config 的副本,并将其命名为 web_minimal_Web.config 或您希望策略文件使用的名称。
- 打开 web_minimal_Web.config,现在我们可以进行必要的修改,为 ASP.NET 权限集授予 WebPermission。
- 在 <SecurityClasses> 元素之间添加对 WebPermission 程序集的引用。
<SecurityClass Name="WebPermission" Description="System.Net.WebPermission, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
- 导航到 ASP.NET 命名权限集并添加:
<IPermissionclass="WebPermission"version="1"> <ConnectAccess> <URIuri="$OriginHost$"/> </ConnectAccess> </IPermission>
- 在 ASP.NET 应用程序中打开您的 Web.Config 文件,并添加以下内容:
<trust level="Minimal_Web" originUrl="https:///.*"/>
- 生成并在浏览器中查看。它应该非常有效地工作。
如果您遵循了所有步骤,那么您就成功创建了一个自定义策略,并以最小信任级别(运行应用程序所需的权限,不多不少)运行了 ASP.NET 应用程序。
让我快速为您总结整个过程。非托管代码加载 ASP.NET 程序集,并为其提供一套证据。有五种 ASP.NET 应用程序域策略,通过修改 Machine.Config 或 Web.Config 文件的信任属性来配置。策略评估器对四种策略(企业、机器、用户、应用程序域)执行“交集”,并将证据映射到代码组的成员资格条件,后者提供权限(权利)。如果所有策略在不同级别之间达成一致,则权限集将包含在授予集中。
声明式 vs. 命令式
主要区别在于信息存储在程序集中的位置,如下所示:
清单存储元数据信息,无需运行程序集即可读取;因此,如果您使用声明式安全来强制执行安全,那么我就可以简单地运行命令行实用程序(**Permview.exe**)来查看运行您的代码需要什么权限。相比之下,命令式更灵活,存储在 MSIL 代码中,该代码将在 JIT 编译时进行编译,并在运行时引发安全异常。
此时,我还没有涵盖在自己的代码中使用 CAS 的问题。我已尽力从一个略有不同的角度来提供代码访问安全的使用方式或潜在用途。这篇文章我仅仅是触及了皮毛。在 CAS 的配置和开发技术方面还有很多内容。在下一期中,我将讨论如何为您的 ASP.NET 应用程序配置沙箱。我还将推出一个为期一天的 CAS 实践工作坊,将于六月举行,有关更多信息,请联系我。
敬请期待。
版权所有 © 2003-2004 Maxim V. Karpov 保留所有权利。