反射的安全注意事项
反射提供了获取有关类型和成员的信息和访问成员(即,调用方法和构造函数、获取和设置属性值、添加和移除事件处理程序等)的能力。 使用反射获取有关类型和成员的信息不受限制。 所有代码都可以使用反射来执行以下任务:
枚举类型和成员并验证其元数据。
枚举并验证程序集和模块。
相反,使用反射访问成员将受到限制。 从 .NET Framework 4 版开始,仅受信任的代码可以使用反射访问安全关键成员。而且,仅受信任的代码才能使用反射访问编译代码不可直接访问的非公共成员。最后,使用反射访问安全关键成员的代码必须具有安全关键成员要求的任何权限,就像使用编译代码一样。
根据必要的权限,代码可以使用反射来执行以下各种访问:
访问不是安全关键的公共成员。
访问可由已编译代码访问的非公共成员(如果这些成员不是安全关键)。 此类非公共成员的示例包括:
调用代码的基类的受保护成员。 (在反射中,将此称为系列级访问。)
调用代码的程序集中的 internal 成员(在 Visual Basic 中为 Friend 成员)。 (在反射中,将此称为程序集级访问。)
包含调用代码的类的其他实例的私有成员。
例如,在沙盒应用程序域中运行的代码只限于此列表中描述的访问,除非应用程序域授予其他权限。
从 .NET Framework 2.0 版 Service Pack 1 开始,如果尝试访问通常不可访问的成员,则会生成针对目标对象的授予集以及带有 ReflectionPermissionFlag.MemberAccess 标志的 ReflectionPermission 的要求。 以完全信任方式运行的代码(例如,从命令行启动的应用程序中的代码)始终可以满足这些权限。 (这会受到有关访问安全关键的成员的限制,如本文后面所述。)
或者,沙盒应用程序域可以授予带有 ReflectionPermissionFlag.MemberAccess 标志的 ReflectionPermission,如本文后面的访问通常不可访问的成员节中所述。
访问安全关键成员
如果一个成员满足以下条件之一,则为安全关键成员:具有 SecurityCriticalAttribute;属于一个具有 SecurityCriticalAttribute 的类型;或者位于一个安全关键程序集中。 从 .NET Framework 4 版开始,访问安全关键成员的规则如下所示:
透明代码不能使用反射来访问安全关键成员,即使代码是完全受信任的也是如此。 将引发 MethodAccessException、FieldAccessException 或 TypeAccessException。
以部分信任方式运行的代码将被视为透明的。
无论安全关键成员是由已编辑代码直接访问,还是通过使用反射访问,这些规则都是相同的。
从命令行运行的应用程序代码以完全信任方式运行。 只要代码不被标记为透明的,它就可以使用反射来访问安全关键成员。 当相同的代码以部分信任方式运行(例如,在沙盒应用程序域中)时,程序集的信任级别将确定它是否可以访问安全关键代码:如果程序集具有强名称并在全局程序集缓存中进行安装,则它是一个受信任的程序集并可以调用安全关键成员。 如果它不是受信任的,则它将成为透明的(即使它未被标记为透明的),并且不能访问安全关键成员。
有关 .NET Framework 4 中的安全模型的更多信息,请参见 .NET Framework 4 中的安全性更改。
反射和透明度
从 .NET Framework 4 开始,公共语言运行时将根据多个因素来确定类型或成员的透明度级别,这些因素包括程序集的信任级别和应用程序域的信任级别。 反射提供 IsSecurityCritical、IsSecuritySafeCritical 和 IsSecurityTransparent 属性以使您能够发现类型的透明度级别。 下表显示了这些属性的有效组合。
安全级别 |
IsSecurityCritical |
IsSecuritySafeCritical |
IsSecurityTransparent |
---|---|---|---|
Critical |
true |
false |
false |
安全关键的 |
true |
true |
false |
透明 |
false |
false |
true |
相对于检查程序集及其类型的安全批注、检查当前信任级别以及尝试复制运行时的规则,使用这些属性要简单很多。 例如,同一个类型既可以是安全关键的(当从命令行中运行时),也可以是安全透明的(当在沙盒应用程序域中运行时)。
MethodBase、FieldInfo、TypeBuilder、MethodBuilder 和 DynamicMethod 类有相似的属性。 (对于其他反射和反射发出抽象,将对关联的方法应用安全特性。例如,对于属性,安全特性将应用于属性访问器。)
访问通常不可访问的成员
若要使用反射来调用根据公共语言运行时的可访问性规则不可访问的成员,必须向您的代码授予以下两种权限之一:
若要允许代码调用任何非公共成员,必须为您的代码授予带有 ReflectionPermissionFlag.MemberAccess 标志的 ReflectionPermission。
注意 默认情况下,安全策略拒绝向源自 Internet 的代码授予此权限。决不应向源自 Internet 的代码授予此权限。
若要允许代码调用任何非公共成员(前提是包含被调用成员的程序集的授予集与包含调用代码的程序集的授予集或其子集相同),必须为您的代码授予带有 ReflectionPermissionFlag.RestrictedMemberAccess 标志的 ReflectionPermission。
例如,假定向某个应用程序域授予 Internet 权限以及带有 ReflectionPermissionFlag.RestrictedMemberAccess 标记的 ReflectionPermission,然后运行具有 A 和 B 两个程序集的 Internet 应用程序。
因为程序集 B 的授权集不包含任何未授予 A 的权限,因此程序集 A 可以使用反射访问程序集 B 的私有成员。
程序集 A 不能使用反射来访问 .NET Framework 程序集(例如 mscorlib.dll)的私有成员,其原因是:mscorlib.dll 是完全受信任的,因此它具有尚未授予程序集 A 的权限。 当代码访问安全性在运行时审核堆栈时,将引发 MemberAccessException。
序列化
对于序列化,带有 SecurityPermissionAttribute.SerializationFormatter 标志的 SecurityPermission 提供获取并设置可序列化类型的成员的功能,无论这些可序列化类型的成员是否可以访问。 此权限可以使代码发现并更改实例的私有状态。 (除了授予正确的权限之外,还必须在元数据中将类型标记为可序列化。)
类型 MethodInfo 的参数
应避免编写采用 MethodInfo 参数的公共成员,对于受信任的代码尤其如此。 此类成员可能更容易受到恶意代码的攻击。 例如,设想在高度受信任的代码中有一个采用 MethodInfo 参数的公共成员。 假设此公共成员对所提供的参数间接地调用 Invoke 方法。 如果公共成员没有执行必要的权限检查,由于安全系统断定调用方受到高度信任,对 Invoke 方法的调用将始终会成功。 即使恶意代码无权直接调用该方法,它仍然能够通过调用公共成员来间接地调用该方法。
版本信息
从 .NET Framework 4 版开始,透明代码将不能使用反射来访问安全关键成员。
ReflectionPermissionFlag.RestrictedMemberAccess 标志是在 .NET Framework 2.0 版 Service Pack 1 中引入的。 对于使用反射访问非公共成员的代码,早期版本的 .NET Framework 需要 ReflectionPermissionFlag.MemberAccess 标志。 决不应将此权限授予部分受信任的代码。
从 .NET Framework 2.0 开始,使用反射获取有关非公共类型和成员的信息不需要任何权限。 在早期版本中,带有 ReflectionPermissionFlag.TypeInformation 标志的 ReflectionPermission 是必需的。