在沙箱 AppDomain 中执行反射方法时出现 SecurityException

SecurityException when executing reflected method in sandbox AppDomain

我正在使用单独的 AppDomain 作为沙箱,并尝试执行通过反射构建的方法。

调用方法时,

SecurityException

...被抛出,即使沙箱 AppDomain 在其 PermissionSet 上设置了 ReflectionPermission(PermissionState.Unrestricted)

PermissionSet 设置为 PermissionState.Unrestricted 时调用确实有效,但这违背了沙箱的目的。

这是一个演示问题的示例:

using System;
using System.Security;
using System.Security.Permissions;

namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person();
            var program = new Program();

            var customDomain = program.CreateDomain();
            var result = program.Execute(customDomain, (x) =>
            {
                var type = x.GetType();
                var propertyInfo = type.GetProperty("Name");
                var method = propertyInfo.GetMethod;
                var res = method.Invoke(x, null) as string;
                return res;
            }, person);
            Console.WriteLine(result);
            Console.ReadLine();
        }

        public object Execute(AppDomain domain, Func<object, object> toExecute, params object[] parameters)
        {
            var proxy = new Proxy(toExecute, parameters);
            var result = proxy.Invoke(domain);
            return result;
        }

        private AppDomain CreateDomain()
        {
            var appDomainSetup = new AppDomainSetup()
            {
                ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
                ApplicationName = "UntrustedAppDomain"
            };

            // Set up permissions
            var permissionSet = new PermissionSet(PermissionState.None);
            permissionSet.AddPermission(new SecurityPermission(PermissionState.Unrestricted));
            permissionSet.AddPermission(new ReflectionPermission(PermissionState.Unrestricted));

            // Create the app domain.
            return AppDomain.CreateDomain("UntrustedAppDomain", null, appDomainSetup, permissionSet);
        }

        private sealed class Proxy : MarshalByRefObject
        {
            private Delegate method;
            private object[] args;
            private object result;

            public Proxy(Delegate method, params object[] parameters)
            {
                this.method = method;
                this.args = parameters;
            }

            public object Invoke(AppDomain customDomain)
            {
                customDomain.DoCallBack(Execute);
                return this.result;
            }

            private void Execute()
            {
                this.result = this.method.DynamicInvoke(this.args);
            }
        }
    }

    public class Person
    {
        public Person()
        {
            this.Name = "Test Person";
        }

        public string Name { get; set; }
    }
}

这是对我上面评论的补充


SecurityException when executing reflected method in sandbox AppDomain

您正试图从辅助域的上下文中对在 主 AppDomain 中创建的对象执行方法。换句话说,沙箱中的代码试图调用不允许的主域中的代码。通过 AppDomains 使用沙箱时,必须通过 主应用程序域 CreateInstanceAndUnwrap 在沙箱域 中创建 Proxy Proxy 98=].

改变这个:

public object Execute(AppDomain domain, Func<object, object> toExecute, params object[] parameters)
    {
        var proxy = new Proxy(toExecute, parameters);
        var result = proxy.Invoke(domain);
        return result;
    }

...至:

    public object Execute(AppDomain domain, Func<object, object> toExecute, params object[] parameters)
    {
        var t = typeof(Proxy); // add me
        var args = new object[] {toExecute, parameters};
        var proxy = domain.CreateInstanceAndUnwrap(t.Assembly.FullName, t.FullName, false,
            BindingFlags.Default, 
            null,
            args,
            null,
            null) as Proxy; // add me

        //var proxy = new Proxy(toExecute, parameters);
        var result = proxy.Invoke(domain);
        return result;
    }

...并使 Person 成为 [Serializable] 或派生自 MarshalByRefObject 取决于您是否要将 副本转移到 将可间接修改的对象分别传递给沙箱。

Thank you for your reply. The above sample code can work if the Proxy is created in the sandbox domain, and the Person class is Serializable, but then the person object used in the secondary domain is indeed not the same instance as the one in the primary domain - it gets serialized in the CreateInstanceAndUnwrap method

这是正确的,是设计使然。请注意,post 中显示的 Person 未标记为 Serializable,因此会导致错误。我假设你修好了。如果你想传递 "same" 对象,让 class 派生自 MarshalByRefObject.

完整代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;

namespace Sandboxes1
{
    class Program
    {
        static void Main(string[] args)
        {


            var person = new Person();
            Console.WriteLine("[{0}] Person's current name: {1}", AppDomain.CurrentDomain.FriendlyName, person.Name);

            var program = new Program();

            var customDomain = program.CreateDomain();
            var result = program.Execute(customDomain, (x) =>
            {
                Console.WriteLine("[{0}] Inside delegate", AppDomain.CurrentDomain.FriendlyName);
                var type = x.GetType();
                var propertyInfo = type.GetProperty("Name");
                var method = propertyInfo.GetMethod;
                var res = method.Invoke(x, null) as string;

                dynamic d = x;
                d.Name = "Fozzy Bear";
                Console.WriteLine("[{0}] delegate changed person's name to- {1}", AppDomain.CurrentDomain.FriendlyName, d.Name);

                return res;
            }, person);
            Console.WriteLine("[{0}] Result: {1}", AppDomain.CurrentDomain.FriendlyName, result);
            Console.WriteLine("[{0}] Person's current name: {1}", AppDomain.CurrentDomain.FriendlyName, person.Name);
            Console.ReadLine();
        }

        public object Execute(AppDomain domain, Func<object, object> toExecute, params object[] parameters)
        {
            var t = typeof(Proxy); // add me
            var args = new object[] {toExecute, parameters};
            var proxy = domain.CreateInstanceAndUnwrap(t.Assembly.FullName, t.FullName, false,
                BindingFlags.Default, 
                null,
                args,
                null,
                null) as Proxy; // add me

            //var proxy = new Proxy(toExecute, parameters);
            var result = proxy.Invoke(domain);
            return result;
        }

        private AppDomain CreateDomain()
        {
            var appDomainSetup = new AppDomainSetup()
            {
                ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
                ApplicationName = "UntrustedAppDomain"
            };

            // Set up permissions
            var permissionSet = new PermissionSet(PermissionState.None);
            permissionSet.AddPermission(new SecurityPermission(PermissionState.Unrestricted));
            permissionSet.AddPermission(new ReflectionPermission(PermissionState.Unrestricted));

            // Create the app domain.
            return AppDomain.CreateDomain("UntrustedAppDomain", null, appDomainSetup, permissionSet);
        }

        private sealed class Proxy : MarshalByRefObject
        {
            private Delegate method;
            private object[] args;
            private object result;

            public Proxy(Delegate method, params object[] parameters)
            {
                Console.WriteLine("[{0}] Proxy()", AppDomain.CurrentDomain.FriendlyName);
                this.method = method;
                this.args = parameters;
            }

            public object Invoke(AppDomain customDomain)
            {
                Console.WriteLine("[{0}] Invoke()", AppDomain.CurrentDomain.FriendlyName);

                customDomain.DoCallBack(Execute);
                return this.result;
            }

            private void Execute()
            {
                Console.WriteLine("[{0}] Execute()", AppDomain.CurrentDomain.FriendlyName);

                this.result = this.method.DynamicInvoke(this.args);
            }
        }
    }

    [Serializable]
    public class Person
    {
        private string _name;

        public Person()
        {
            Name = "Test Person";
        }

        public string Name
        {
            get
            {
                Console.WriteLine("[{0}] Person.getName()", AppDomain.CurrentDomain.FriendlyName);
                return _name;
            }
            set { _name = value; }
        }
    }
}

...产生以下输出:

安全风险

是否要让沙箱中的代码修改来自另一个应用程序域的对象取决于您的设计。在我看来,这是一个 安全风险 并且可以说 否定了沙箱 的目的。因此,如果您必须传递域间对象,请将它们作为 Serializable 传递,其中传递对象的副本。

更好的安全性?

我在代码中倾向于使用的方法是:

  1. 定义一个 Manager class 创建辅助 AppDomain
  2. Manager 使用 CreateInstanceAndUnwrap 在次 AppDomain
  3. 中创建 MarshalByRefObject Proxy class
  4. Manager 呼入 ProxyLoadPlugins()。这意味着任何插件创建的对象现在自动存在于辅助 AppDomain
  5. 确保从主 AppDomain 调用的 Proxy 方法中的任何方法都包含在 try-catch 中。 不要让任何异常冒泡到主 AppDomain 因为来自可疑插件的狡猾代码很可能会抛出自定义异常,在完成时可以执行它希望使用 完全权限 由于调用堆栈现在位于主 AppDomain 上。这是 .NET 编程中为数不多的必须 catch 一切而不是 throw
  6. 的场合之一
  7. 如果主要 AppDomain 需要与次要应用程序域中的对象对话,请通过调用您的 Proxy 并让它代表您调用该方法来实现。再次确保此代理方法完全包含在 try-catch all

结论

我不知道 AppDomain.DoCallBack,现在我看到了,我不喜欢它。委托不小心使用对象的风险太大,特别是在委托代码定义在一种方法中的情况下,该方法将是一个域执行的某些代码的混合体;其他部分由另一个域。

更多信息

  • 大约在 2006 年的 AppDomains 上有一篇精彩的 MSDN 杂志文章 "Add-ins - do you trust it"(或类似文章),我的回答基于该文章。遗憾的是,Microsoft 已将 HTML 表格脱机,只有 .chm 文件很难搜索。