前提条件:读者应当熟悉公共语言运行库和 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,并且代码不会运行。您可以确保代码将被授予该权限,并且不必担心由于权限太少而导致的错误。
该示例还告诉系统:不需要其他权限。除此之外,代码将被授予策略选择要授予它的任何权限。虽然额外的权限不会导致损害,但如果某处有安全问题,则拥有较少的权限可以很好地堵住漏洞。带有代码不需要的权限会导致安全问题。
将代码所接收的权限限制为最少特权的另一个方式是列出要拒绝的特定权限。如果您要求所有权限都是可选的,并从该请求中排除特定权限,那么权限通常会被拒绝。
确保状态数据的安全
处理敏感数据或作出任何安全决定的应用程序需要使该数据处于自己的控制下,并且不能让其他的潜在恶意代码直接访问该数据。使数据安全地保留在内存中的最佳方式是将其定义为私有或内部(限制在同一程序集的范围内)变量。但是,此数据也服从于您应当知道的访问权:
• 在反射时,引用了对象的高度受信任代码可以获得并设置私有成员。
• 使用序列化时,如果高度受信任的代码可以通过对象的序列化形式访问相应数据,那么它就可以有效地获得和设置私有成员。
• 在调试时,可以读取该数据。
确保自己的任何方法或属性都没有无意地公开这些值。
在某些情况下,数据可以使用“protected”加以保护,这时,只能访问该类及其派生类。但是,由于存在其他公开的可能性,您还应当采取下面的预防措施:
• 通过将类的派生限制在同一个程序集内,或使用声明性安全来要求某些标识或权限以便从您的类中派生,从而控制允许哪些代码从您的类中派生(请参阅本文的确保方法访问的安全部分)。
• 确保所有派生类都实现了相似的保护或者被密封。
装箱的值类型
如果您认为已经分发了无法修改原始定义的类型的副本,有时还可以修改装箱的值类型。返回装箱的值类型时,您所返回的是对值类型的引用,而不是对值类型副本的引用,因而允许调用您代码的代码修改您的变量值。
以下 C# 代码示例显示了如何使用引用来修改装箱的值类型。
using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
using System.Collections;
class bug {
// Suppose you have an API element that exposes a
// field through a property with only a get aclearcase/" target="_blank" >ccessor.
public object m_Property;
public Object Property {
get { return m_Property;}
set {m_Property = value;} // (if applicable)
}
// You can modify the value of this by doing
// the byref method with this signature.
public static void m1( ref int j ) {
j = Int32.MaxValue;
}
public static void m2( ref ArrayList j )
{
j = new ArrayList();
}
public static void Main(String[] args)
{
Console.WriteLine( "////// doing this with value type" );
{
bug b = new bug();
b.m_Property = 4;
Object[] objArr = new Object[]{b.Property};
Console.WriteLine( b.m_Property );
typeof(bug).GetMethod( "m1" ).Invoke( null, objArr );
// Note that the property changed.
Console.WriteLine( b.m_Property );
Console.WriteLine( objArr[0] );
}
Console.WriteLine( "////// doing this with a normal type" );
{
bug b = new bug();
ArrayList al = new ArrayList();
al.Add("elem");
b.m_Property = al;
Object[] objArr = new Object[]{b.Property};
Console.WriteLine( ((ArrayList)(b.m_Property)).Count );
typeof(bug).GetMethod( "m2" ).Invoke( null, objArr );
// Note that the property does not change.
Console.WriteLine( ((ArrayList)(b.m_Property)).Count );
Console.WriteLine( ((ArrayList)(objArr[0])).Count );
}
}
}
确保方法访问的安全
某些方法可能不适合由不受信任的任意代码调用它们。此类方法会导致几个风险:方法可能会提供某些受限制信息;可能会相信传递给它的任何信息;可能不会对参数进行错误检查;或者,如果参数错误,可能会出现故障或执行某些有害操作。您应当注意这些情况,并采取适当的操作来确保方法的安全。
在某些情况下,您可能需要限制不打算公开使用、但仍必须是公共的方法。例如,您可能有一个需要在自己的 DLL 之间进行调用的接口,因此它必须是公共的,但您不想公开它,以防止用户使用它或防止恶意代码利用它作为入口点进入到您的组件中。对不打算公共使用(但仍必须是公共)的方法进行限制的另一个常见理由是,避免用文档记录和支持非常内部的接口。
托管代码为限制方法访问提供了几个方式:
• 将可访问性的作用域限制到类、程序集或派生类(如果它们是可信任的)。这是限制方法访问的最简单方式。请注意,通常派生类的可信赖度比它们派生自的类更低,但在某些情况下,它们可以共享超类标识。特别是,不要从关键字 protected 推断信任情况,因为在安全上下文中,该关键字不是必须使用的。
• 将方法访问限制到指定标识(实质上,是您选择的任何特殊证据)的调用方。
• 将方法访问限制到拥有您所选权限的调用方。
同样,声明性安全也允许您控制类的继承。您可以使用 InheritanceDemand 执行以下操作:
• 要求派生类拥有指定的标识或权限。
• 要求重写特定方法的派生类拥有指定的标识或权限。
示例:保护对类或方法的访问
以下示例显示了如何确保公共方法的安全,以限制访问。
• sn -k 命令用于创建新的私钥/公钥对。私钥部分需要使用强名称签署代码,并由代码发行者安全地保留。(如果泄露,任何人都可以在他们的代码上假冒您的签名,从而使保护措施失效。)
通过强名称标识来确保方法的安全
sn -k keypair.dat
csc/r:App1.dll /a.keyfile:keypair.dat App1.cs
sn -p keypair.dat public.dat
sn -tp public.dat >publichex.txt
[StrongNameIdentityPermissionAttribute
(SecurityAction.LinkDemand,
PublicKey="_",hex_",Name="App1",
Version="0.0.0.0")]
public class Class1
• csc 命令用于编译和签署 App1,以授于它访问受保护方法的权限。
• 后面的两个 sn 命令用于从密钥对中提取公钥部分,并将它格式化为十六进制。
• 示例的下半部分是摘录的受保护方法的源代码。自定义属性可定义强名称,指定密钥对中的公钥,并插入从 sn 得到的十六进制格式的数据作为 PublicKey 属性。
• 在运行时,由于 App1 拥有必需的强名称签名,所以被允许使用 Class1。
该示例使用 LinkDemand 来保护 API 元素;有关使用 LinkDemand 的限制的重要信息,请参阅本文后面的部分。
防止不受信任的代码使用类和方法
使用以下声明可以防止部分信任的代码使用类和方法(包括属性和事件)。通过将这些声明应用于类,可以对该类的所有方法、属性和事件应用保护;但请注意,字段访问不会受到声明性安全的影响。请注意,链接请求只保护直接调用方,并且仍可能会受到引诱攻击(本文的基于证据的安全性和代码访问安全性部分对此进行了描述)。
具有强名称的程序集将声明性安全应用于所有可公开访问的方法、属性和事件,因此只有完全受信任的调用方才能使用它们,除非程序集通过应用 AllowPartiallyTrustedCallers 属性显式地决定参与使用。因此,通过显式地标记类来排除不受信任的调用方,只对未签名的程序集或具有该属性的程序集、以及原本就不打算用于不受信任调用方的类型的子集才是必需的。有关全部详细信息,请参阅 Microsoft .NET 框架的第 1 版安全更改文档。
• 对于公共的非密封类:
[System.Security.Permissions.PermissionSetAttribute(System.Security.
Permissions.SecurityAction.InheritanceDemand, Name="FullTrust")]
[System.Security.Permissions.PermissionSetAttribute
(System.Security.Permissions.SecurityAction.LinkDemand, Name="FullTrust")]
public class CanDeriveFromMe
• 对于公共的密封类:
[System.Security.Permissions.PermissionSetAttribute
(System.Security.Permissions.SecurityAction.LinkDemand, Name="FullTrust")]
public sealed class CannotDeriveFromMe
• 对于公共的抽象类:
[System.Security.Permissions.PermissionSetAttribute
(System.Security.Permissions.SecurityAction.InheritanceDemand, Name="FullTrust")]
[System.Security.Permissions.PermissionSetAttribute
(System.Security.Permissions.SecurityAction.LinkDemand, Name="FullTrust")]
public abstract class CannotCreateInstanceOfMe_CanCastToMe
• 对于公共的虚拟函数:
class Base {
[System.Security.Permissions.PermissionSetAttribute
(System.Security.Permissions.SecurityAction.InheritanceDemand,
Name="FullTrust")]
[System.Security.Permissions.PermissionSetAttribute(
System.Security.Permissions.SecurityAction.LinkDemand,
Name="FullTrust")]
public override void CanOverrideOrCallMe() { ... }
• 对于公共的抽象函数:
class Base {
[System.Security.Permissions.PermissionSetAttribute
(System.Security.Permissions.SecurityAction.InheritanceDemand, Name="FullTrust")]
[System.Security.Permissions.PermissionSetAttribute
(System.Security.Permissions.SecurityAction.LinkDemand,
Name="FullTrust")]
public override void CanOverrideMe() { ... }
• 对于基函数不需要完全信任的公共重写函数:
class Derived {
[System.Security.Permissions.PermissionSetAttribute
(System.Security.Permissions.SecurityAction.Demand, Name="FullTrust")]
public override void CanOverrideOrCallMe() { ... }
• 对于基函数需要完全信任的公共重写函数:
class Derived {
[System.Security.Permissions.PermissionSetAttribute
(System.Security.Permissions.SecurityAction.LinkDemand,
Name="FullTrust")]
public override void CanOverrideOrCallMe() { ... }
• 对于公共接口:
[System.Security.Permissions.PermissionSetAttribute
(System.Security.Permissions.SecurityAction.InheritanceDemand,
Name="FullTrust")]
[System.Security.Permissions.PermissionSetAttribute
(System.Security.Permissions.SecurityAction.LinkDemand,
Name="FullTrust")]
public interface CanCastToMe
Demand 与 LinkDemand
声明性安全提供了两种类似的、但检查方式大不相同的安全检查。花些时间了解这两种形式是值得的,因为错误的选择会导致脆弱的安全性或性能损失。本部分并不打算完整地说明这些功能;有关完整的详细信息,请参阅产品文档。
声明性安全可提供以下安全检查:
• Demand 指定代码访问安全性的堆栈审核:堆栈上的所有调用方都必须拥有权限或标识才能通过。Demand 会发生在每个调用上,这是因为堆栈可能包含不同的调用方。如果您重复调用某个方法,则每次都会进行该安全检查。Demand 对引诱攻击具有强大的抵御能力;它可捕获试图通过它的未经授权的代码。
• LinkDemand 发生在实时 (JIT) 编译时(在前面的示例中,当引用 Class1 的 App1 代码将要执行时),并且它只检查直接调用方。该安全检查不检查调用方的调用方。一旦通过该检查,无论它被调用多少次,都不会有其他安全开销。但是,它无法防御引诱攻击。如果使用 LinkDemand,则您的接口是安全的,但通过测试并且可以引用您的代码的任何代码都可以潜在地破坏安全性,因为它们允许使用授权代码调用恶意代码。因此,除非可以完全避免所有可能的弱点,否则不要使用 LinkDemand。
使用 LinkDemand 时所需的额外预防措施必须是“手工制订的”(安全系统可以帮助实施)。任何错误都会导致安全漏洞。使用您代码的所有经授权代码都必须通过执行以下操作,来负责实现其他安全性:
• 将呼叫代码的访问权限制到类或程序集。
• 为该代码设置相同的安全检查,并强制它的调用方这样做。例如,如果您编写的代码调用了某个方法,而该方法通过对 SecurityPermission.UnmanagedCode 权限使用 LinkDemand 获得保护,那么您的方法也应当对该权限使用 LinkDemand(或 Demand,这是更强的手段)。例外情况是,假如代码中有其他安全保护机制(例如,Demand),则您的代码将以一种总是安全的或您决定是安全的受限方式使用 LinkDemand 所保护的方法。在调用方负责削弱对基础代码的安全保护情况下,会出现此例外情况。
• 确保它的调用方无法进行欺骗,以代表它去调用受保护的代码(也就是说,调用方不能强迫经授权的代码将特定参数传递给受保护的代码,或从中获得结果)。
接口和 LinkDemands
如果具有 LinkDemand 的虚拟方法、属性或事件重写了某个基类方法,则此基类方法也必须具有相同的 LinkDemand,以便重写方法也是安全的。恶意代码可能会强制转换回基础类型,并调用基类方法。还要注意,可以将 LinkDemands 隐式地添加到不具有 AllowPartiallyTrustedCallersAttribute 程序集级属性的程序集中。
好的做法是,当接口方法也有 LinkDemands 时,使用 LinkDemands 对方法实现进行保护。
关于对接口使用 LinkDemands,请注意以下事项:
• AllowPartiallyTrustedCallers 属性会影响接口。
• 可以将 LinkDemands 放在接口上,以有选择地挑出某些接口,使其不能由部分信任的代码使用(例如,在使用 AllowPartiallyTrustedCallers 属性时)。
• 如果在不包含 AllowPartiallyTrustedCallers 属性的程序集中定义接口,则可以在部分信任的类上实现该接口。
• 如果将 LinkDemand 放在一个实现接口方法的类的公共方法上,在随后强制转换到该接口并调用该方法时,将不会执行 LinkDemand。在这种情况下,因为是对接口进行链接,所以只考虑接口上的 LinkDemand。
应当审阅以下各项是否有安全问题:
• 接口方法上的显式链接请求。确保这些链接请求提供了预期的保护。确定恶意代码是否可以使用强制转换来避开前面描述的链接请求。
• 具有链接请求的虚拟方法。
• 它们实现的类型和接口应当一致地使用 LinkDemands。
虚拟内部重写
在确认代码对其他程序集不可用时,需要了解类型系统可访问性的细微差别。声明 virtual 和 internal 的方法可以重写超类的 vtable 条目,并且只能在同一个程序集的内部使用,因为它是内部的。但是,重写的可访问性是由 virtual 关键字决定的,并且只要代码能够访问类本身,就可以从另一个程序集对该可访问性进行重写。如果重写的可能性比较小,请使用声明性安全解决它,或者删除 virtual 关键字(如果它不是必需的)。
包装程序代码
包装程序代码(特别是在包装程序比使用它的代码具有更高可信度时)可以显露一组独特的安全漏洞。如果没有将调用方的受限制权限包括在适当的安全检查中,则代表调用方所执行的任何操作都是可能被利用的潜在漏洞。
不要通过包装程序来启用调用方本身无法执行的某些操作。在执行某些涉及受限制安全检查的操作时(与完整的堆栈审核请求相反),会有特殊的危险性。涉及到单一级别的检查时,在实际调用方与可疑 API 元素之间插入包装程序代码,可以很容易地使安全检查在不应当成功时成功通过,从而降低了安全性。
委托
无论何时,如果您的代码从可能调用它、但信任度较低的代码那里取得委托权,请确保您不会让信任度较低的代码提升它的权限。如果您取得委托权并随后使用它,那么,如果委托中或委托下面的代码试图执行受保护的操作,则创建委托的代码将不会在调用堆栈中,并且不会测试它的权限。如果您的代码和委托代码具有比调用方更高的特权,这将使调用方能够在不成为调用堆栈一部分的情况下改变调用路径。
要解决该问题,可以限制调用方(例如,要求它具有某个权限)或对执行委托的权限加以限制(例如,通过使用 Deny 或 PermitOnly 堆栈重写)。
LinkDemands 和包装程序
在安全基础结构中,已经加强了对链接请求的特殊保护措施,但它仍然是代码中可能的漏洞来源。
如果完全受信任的代码调用某个由 LinkDemand 保护的属性、事件或方法,那么,如果对调用方的 LinkDemand 权限检查获得通过,则调用将成功。此外,如果完全受信任的代码所公开的类使用了某个属性的名称,并使用反射来调用该属性的 get 访问器,那么,即使用户代码无权访问该属性,对 get 访问器的调用也会成功。这是因为 LinkDemand 将只检查直接调用方,而直接调用方是完全受信任的代码。其实,完全受信任的代码在代表用户代码执行经授权的调用时,不需要确保用户代码有权进行该调用。如果您要包装反射功能,请参阅 Microsoft .NET 框架的第 1 版安全更改一文,以获得详细信息。
为了防止因疏忽造成的安全漏洞(例如,上面描述的情况),运行时将对任何使用调用的操作(实例创建、方法调用、属性设置或获得)所进行的完整堆栈审核请求检查扩展到由链接请求保护的方法、构造函数、属性或事件。该保护会导致一些性能成本(单级 LinkDemand 速度更快),并且会更改安全检查的语义 — 即使单级检查已通过,完整堆栈审核请求也可能会失败。
程序集加载包装程序
用来加载托管代码的几个方法(包括 Assembly.Load(byte[])),可使用调用方的证据加载程序集。具体来说,如果要包装这些方法中的任意一个,安全系统可以使用您代码被授予的权限(而不是调用方对包装程序的权限)来加载程序集。显然,您并不想允许信任度较低的代码指使您代表它加载其所获权限比调用方对包装程序的权限更高的代码。
拥有完全信任或信任度比潜在调用方(包括 Internet 权限级调用方)高得多的任何代码很容易通过这种方式降低安全性。如果您代码包含的公共方法可以取得字节数组并将其传递给 Assembly.Load(byte[]),从而代表调用方创建程序集,则可能会破坏安全性。
该问题会发生在以下 API 元素上:
• System.AppDomain.DefineDynamicAssembly
• System.Reflection.Assembly.LoadFrom
• System.Reflection.Assembly.Load
异常处理
在运行任何 finally 语句之前,将运行堆栈上面的一个筛选表达式。在运行 finally 语句之后,将运行与该筛选器关联的 catch 代码块。请考虑以下伪代码:
void Main() {
try {
Sub();
} except (Filter()) {
Console.WriteLine("catch");
}
}
bool Filter () {
Console.WriteLine("filter");
return true;
}
void Sub() {
try {
Console.WriteLine("throw");
throw new Exception();
} finally {
Console.WriteLine("finally");
}
}
该代码将打印以下内容:
Throw
Filter
Finally
Catch
筛选器将在 finally 语句之前运行,这样,如果可以执行其他代码,则造成状态更改的任何操作都可以带来安全问题。例如:
try {
Alter_Security_State();
// This means changing anything (state variables,
// switching unmanaged context, impersonation, and so on)
// that could be exploitable if malicious code ran before state is restored.
Do_some_work();
}
finally {
Restore_Security_State();
// This simply restores the state change above.
}
该伪代码允许筛选器备份堆栈以运行任意代码。具有类似效果的其他操作示例是对另一个标识的临时模拟,这样会设置一个避开某个安全检查的内部标志,并更改与线程关联的区域性等等。
建议的解决方案是引入异常处理程序,将代码的线程状态更改与调用方的筛选器块隔离开。但重要的是,要正确引入异常处理程序,否则将无法解决该问题。下面的 Microsoft Visual Basic(R) 示例将切换 UI 区域性,但可以类似地公开任何线程状态更改。
YourObject.YourMethod()
{
CultureInfo saveCulture = Thread.CurrentThread.CurrentUICulture;
try {
Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE");
// Do something that throws an exception.
}
finally {
Thread.CurrentThread.CurrentUICulture = saveCulture;
}
}
Public Class UserCode
Public Shared Sub Main()
Try
Dim obj As YourObject = new YourObject
obj.YourMethod()
Catch e As Exception When FilterFunc
Console.WriteLine("An error occurred: '{0}'", e)
Console.WriteLine("Current Culture: {0}",
Thread.CurrentThread.CurrentUICulture)
End Try
End Sub
Public Function FilterFunc As Boolean
Console.WriteLine("Current Culture: {0}", Thread.CurrentThread.CurrentUICulture)
Return True
End Sub
End Class
在此例中,正确的修复措施是在 try/catch 块中包装现有的 try/finally 块。只将 catch-throw 子句引入现有的 try/finally 块中将无法解决问题:
YourObject.YourMethod()
{
CultureInfo saveCulture = Thread.CurrentThread.CurrentUICulture;
try {
Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE");
// Do something that throws an exception.
}
catch { throw; }
finally {
Thread.CurrentThread.CurrentUICulture = saveCulture;
}
}
这不会解决问题,因为在 FilterFunc 获得控制权之前尚未运行 finally 语句。
以下代码通过确保在根据调用方的异常筛选块提供异常之前执行 finally 子句,来解决该问题。
YourObject.YourMethod()
{
CultureInfo saveCulture = Thread.CurrentThread.CurrentUICulture;
try {
try {
Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE");
// Do something that throws an exception.
}
finally {
Thread.CurrentThread.CurrentUICulture = saveCulture;
}
}
catch { throw; }
}
共2页: 1 [2] 下一页 |