假设我的应用程序实现了一个允许用户从外部DLL文件动态加载和执行代码的插件/扩展系统,那么我将如何管理磁盘,注册表和网络访问(读/写/删除)方面的安全性?
更具体地说,我希望能够知道插件/扩展即将执行磁盘/注册表/网络操作,拦截该操作并根据一组预定义规则允许或拒绝它,或者只是询问用户,如果他们允许对该特定插件的某种类型的访问。
对于绝对解决方案,您将不得不使用内核驱动程序来拦截请求。但如果你不需要100%,你可以开发一个类似于API Monitor的工具。
工具挂钩API函数,(很少有这样的库,MSDetour,Mhook等),可以记录通话或中断通话。通过检查堆栈,知道哪个模块也可以调用API。
使用AppDomains和代码访问安全性可以实现某些控制。您可以创建沙盒AppDomain并将其权限授予您希望插件访问的目录。如果插件尝试在其外部访问文件,则会获得SecurityException。
例如,如果我们有以下ClassLib.dll插件:
namespace ClassLib
{
public class Class1
{
public static string DoWork(string path)
{
return System.IO.File.ReadAllText(path);
}
}
}
我们可以使用受限制的权限调用它:
using System;
using System.Collections;
using System.Reflection;
using System.Security;
using System.Security.Policy;
using System.Security.Permissions;
namespace ConsoleApplication1
{
class Program
{
//Path to plugin library
public static string libname = @"C:\PROJECTS\ConsoleApp1\bin\Debug\ClassLib.dll";
//Invokes plugin library in restricted AppDomain
//Grants access to the directory specified by allowedPath parameter
static void InvokeLibrary(string allowedPath="")
{
var path = new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath;
//create restricted permission set
Evidence evidence = new Evidence();
evidence.AddHostEvidence(new Zone(SecurityZone.Internet));
PermissionSet permissionSet = SecurityManager.GetStandardSandbox(evidence);
//grant read access to plugin library file, so it can at least load successfully
permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read, libname));
permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.PathDiscovery, libname));
//grant read access to
if (allowedPath != "")
{
permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read, allowedPath));
}
AppDomainSetup appDomainSetup = new AppDomainSetup();
appDomainSetup.ApplicationBase = System.IO.Path.GetDirectoryName(path);
//create AppDomain with specified permissions
AppDomain domain = AppDomain.CreateDomain(
"MyDomain",
evidence,
appDomainSetup,
permissionSet
);
//create remoting wrapper
Type type = typeof(Wrapper);
Wrapper wrapper = (Wrapper)domain.CreateInstanceAndUnwrap(
type.Assembly.FullName,
type.FullName);
//invoke method
InvokeResult res = wrapper.Invoke(libname,"c:\\dir\\file.txt");
if (res.Success) Console.WriteLine("Invoke result: " + res.Result);
else {
Console.WriteLine("Invoke error: " + res.Error.GetType().ToString());
if (res.Error is SecurityException)
{
Console.WriteLine("Demanded permissions:\n" + (res.Error as SecurityException).Demanded);
}
}
AppDomain.Unload(domain);
}
static void Main(string[] args)
{
//invoke plugin without explicit permissions
Console.WriteLine("First attempt...");
InvokeLibrary();
//invoke plugin with specified directory access
Console.WriteLine("Second attempt...");
InvokeLibrary("c:\\dir\\");
Console.ReadKey();
}
}
//Object to pass between app domains
public class InvokeResult : MarshalByRefObject
{
public bool Success = false;
public string Result="";
public Exception Error =null;
}
//Wrapper for remoting
public class Wrapper : MarshalByRefObject
{
public Exception lasterror;
public InvokeResult Invoke(string lib, string arg)
{
InvokeResult res = new InvokeResult();
try
{
//load plugin
Assembly ass = Assembly.LoadFile(lib);
Type t = ass.GetType("ClassLib.Class1");
MethodInfo mi = t.GetMethod("DoWork", BindingFlags.Static | BindingFlags.Public);
//invoke plugin method
res.Result = (string)mi.Invoke(null, new object[] { arg });
res.Success = true;
}
catch (TargetInvocationException ex)
{
res.Success = false;
if (ex.InnerException != null) res.Error = ex.InnerException;
else res.Error = ex;
}
return res;
}
}
}
/* Output:
First attempt...
Invoke error: System.Security.SecurityException
Demanded permissions:
<IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
version="1"
Read="c:\dir\file.txt"/>
Second attempt...
Invoke result: The content of file.txt
*/