Keith Brown在4月份的MSDN杂志上发表了一篇讨论.NET下安全性的文章,Beware of Fully Trusted Code,其中详细讨论了让 Managed Code 允许在 Fully Trusted 模式下的危害。真是不看不知道,呵呵,现在我是深深感到了 Fully Trusted Code 的可怕。:D
对一个类型的私有成员变量来说,其私有性的保护实际上只是编译期的
以下为引用:
class DiskQuota {
private:
long MinBytes;
long MaxBytes;
};
以上类型的私有成员变量实际上可以简单的通过unsafe代码中的指针操作访问
以下为引用:
void EvilCode(DiskQuota* pdq) {
// use pointer arithmetic to index
// into the object wherever we like!
((long*)pdq)[1] = MAX_LONG;
}
好在CLR能够通过类型系统完整性检测,一定程度上解决这类有意或缓冲区溢出的问题,除非在程序中显式的要求CLR关闭此类检测。如
以下为引用:
// evilcode.cs
using System.Security.Permissions;[assembly: SecurityPermission(
SecurityAction.RequestMinimum,
Flags=SecurityPermissionFlag.SkipVerification)]// your evil code goes here
麻烦的是一旦使用/unsafe参数编译C#程序,或者使用Managed C++编写程序,这个权限就被隐式地设置为最低,以保障代码地正确执行。虽然新版本的C#编译器将提供参数选项打开此权限,但最根本的解决方法还是得通过.NET安全策略来完成。可惜在缺省状态下,本地硬盘上的Managed程序都是在 Fully Trusted 模式下运行的。
可以通过控制面板的管理工具中的"Microsft .NET Framework 1.1配置"工具,在 我的电脑运行库安全策略计算机代码组All_Code 上通过 编辑代码组属性 看到当前的安全策略设置。安全策略一般分为企业、计算机和用户三级,每级又可根据一定的代码组条件分为多个代码组,设置不同的权限集。
我们接下来看看 Fully Trusted 权限的“威力”所在吧 :P
首先是所谓的私有方法,众所周知它们是可以通过Reflection被直接调用的,呵呵,如下:
以下为引用:
using System;
using System.Reflection;class EvilCodeWithFullTrust
{
static void CallPrivateMethod(object o, string methodName) {
Type t = o.GetType();
MethodInfo mi = t.GetMethod(methodName,
BindingFlags.NonPublic |
BindingFlags.Instance);
mi.Invoke(o, null);
}
static void Main() {
CallPrivateMethod(new NuclearReactor(), "Meltdown");
}
}
比较好的解决方法是通过检测调用者是否被信任,来保障私有函数不被滥用,如使用StrongNameIdentityPermission属性限制调用者所在assembly必须有相同的public key,可以参见我以前的一篇 BLog 文章《关于信任粒度的讨论》
以下为引用:
public class NuclearReactor {
[StrongNameIdentityPermission(
SecurityAction.LinkDemand,
PublicKey="002400000...")]
private void Meltdown() {
// calling assembly must have specified public key!
}
}
这样可以禁止直接通过Reflection访问私有成员函数或变量。但如果调用者代码具有 Fully Trusted 权限的话,它可以直接使用命令行工具 caspol -s off 或者通过代码,完全关闭 StrongNameIdentityPermission 进行的检测,呵呵
以下为引用:
using System.Security;
class EvilCodeWithFullTrust {
static void Main() {
SecurityManager.SecurityEnabled = false;
// now call Meltdown via reflection!
}
}
改变SecurityManager.SecurityEnabled的值需要SecurityPermissionFlag.ControlPolicy权限,对应于配置中安全性下的允许策略控制权限, Fully Trusted 情况下是打开的 :(
既然系统自动的调用者验证可以被跳过,是否能通过程序手工进行验证呢?以下代码在调用不可完全信任的插件之前,通过降低权限来限定插件的能力,可以说这是一种非常好的安全编程习惯。
以下为引用:
using System.Security;
using System.Security.Permissions;class WellMeaningCode {
public void CallPlugIn(EvilCode plugin) {
// put a CAS modifier on the stack that denies all file system access
new FileIOPermission(
PermissionState.Unrestricted).Deny();
plugin.DoWork();
CodeAccessPermission.RevertDeny();
}
}
但是在 Fully Trusted 权限下还是无能为力,直接对PermissionSet.Assert函数的调用使得权限检查再一次被跳过。
以下为引用:
class EvilCodeWithFullTrust {
void DoWork() {
new PermissionSet(
PermissionState.Unrestricted).Assert();
// happily access the file system
// regardless of the caller's deny!
}
}
前面提到配置工具中对安全策略的配置有企业、机器和用户三层,实际上还有AppDomain这一层,可以通过AppDomain.SetAppDomainPolicy载入单独的安全策略限定其后需要执行的代码,ASP.NET就是通过这种方法限定执行代码的权限。但是具有 Fully Trusted 权限的代码可以打破 AppDomain 边界的限制,并可以通过调用其他 Unmanaged Code 实现对其他 AppDomain 侵入。好在可以通过修改 machine.config 文件让 ASP.NET 运行在较低的权限集中,如
以下为引用:
<configuration>
<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" />
</securityPolicy>
<!-- level="[Full|High|Medium|Low|Minimal]" -->
<trust level='Medium'/>
</system.web>
</configuration>
但因为很多程序员没有耐心完成最小权限集的调整工作,导致大部分情况下为了省事将缺省权限设置为 Fully Trusted,导致上述各种精心设计的安全检测都形同虚设。再好的技术如果没有相应的管理来保障,只是摆设而已。
解决这个问题,一方面需要程序员对其代码的权限做限定和调整,另一方面需要开发厂商提供静态和动态的权限分析工具辅助,最后还得配合传统的NT权限设置来把关,毕竟CLR还是运行在NT的用户的权限集下。
有兴趣的朋友可以进一步阅读这篇文章:Writing managed code for semi-trusted environment
此外 Brown 还是 Programming Windows Security 一书的作者,他在书中详细介绍了 Windows 环境下的安全子系统的运行机制和使用方法,并一定程度上涉及到了网络认证、COM+和Internet环境下的安全性问题,非常详尽值得一读。此书已经由电力出版社翻译出版 《Windows安全性编程》。