【问题标题】:SecurityException when executing reflected method in sandbox AppDomain在沙盒 AppDomain 中执行反射方法时出现 SecurityException
【发布时间】:2016-10-27 12:47:09
【问题描述】:

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

当方法被调用时,一个

安全异常

...被抛出,即使沙盒 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; }
    }
}

【问题讨论】:

  • 您正试图从辅助域的上下文中对 primary AppDomain 中创建的对象执行方法。换句话说,沙箱中的代码试图调用不允许的主域中的代码。通过AppDomains 使用沙盒时,必须在沙盒域中通过主应用程序域CreateInstanceAndUnwrap 创建Proxy
  • @MickyD 感谢您的回复。如果在沙箱域中创建Proxy,并且Person类是Serializable,则上面的示例代码可以工作,但是在二级域中使用的人对象确实与主域 - 它在 CreateInstanceAndUnwrap 方法中被序列化。
  • Person 必须首先设为Serializable。如果您不想传递副本,请使用MarshalByRefObject。请参阅下面的答案

标签: c# reflection appdomain


【解决方案1】:

这是对我上面评论的补充


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

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

改变这个:

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 派生,具体取决于您是将副本传递给 还是将可间接修改的对象传递给分别是沙盒。

感谢您的回复。如果在沙箱域中创建代理,并且 Person 类是可序列化的,则上面的示例代码可以工作,但是在辅助域中使用的人员对象确实与主域中的实例不同 - 它被序列化了在 CreateInstanceAndUnwrap 方法中

这是正确的,是设计使然。请注意,您的帖子中显示的Person 未标记为Serializable,因此会导致错误。我假设你解决了这个问题。如果你想传递“相同”的对象,让类派生自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 类来创建一个辅助AppDomain
  2. Manager 使用CreateInstanceAndUnwrap 在辅助AppDomain 中创建MarshalByRefObject Proxy
  3. Manager 打电话给ProxyLoadPlugins()。这意味着任何插件创建的对象现在都自动存在于辅助AppDomain
  4. 确保从主 AppDomain 调用的 Proxy 方法中的任何方法都包含在 try-catch 中。 不要让任何异常冒泡到主 AppDomain,因为来自可疑插件的狡猾代码很可能会抛出自定义异常,在完成时可以使用 完全权限执行它希望执行的任何代码 由于调用堆栈现在位于主 AppDomain 上。这是 .NET 编程中为数不多的情况之一,您必须 catch 一切,而不是 throw
  5. 如果主 AppDomain 需要与辅助 appdomain 中的对象通信,请通过调用您的 Proxy 并让它代表您调用该方法。再次确保此代理方法完全包装在 try-catch all 中

结论

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

更多信息

  • 在 2006 年左右的 AppDomains 上有一篇精彩的 MSDN 杂志文章“加载项 - 你信任它吗”(或类似的文章),我的答案正是以此为基础的。遗憾的是,Microsoft 已将 HTML 表单脱机,并且只有难以搜索的 .chm 文件。

【讨论】:

    猜你喜欢
    • 2012-01-04
    • 1970-01-01
    • 1970-01-01
    • 2016-12-14
    • 1970-01-01
    • 2014-10-22
    • 2019-04-03
    • 2011-07-07
    • 1970-01-01
    相关资源
    最近更新 更多