Cómo: Ejecutar código de confianza parcial en un recinto

El uso de un espacio aislado consiste en ejecutar código en un entorno de seguridad restringido, que limita los permisos de acceso concedidos al código. Por ejemplo, si tiene una biblioteca administrada de un origen que no es de plena confianza, no debe ejecutarla como de plena confianza. En su lugar, debe colocar el código en un espacio aislado que limite sus permisos a los que crea necesitar (por ejemplo, el permiso Execution).

También puede usar un espacio aislado para probar código que va a distribuir y que se va a ejecutar en entornos de confianza parcial.

Un AppDomain es una manera eficaz de proporcionar un espacio aislado para las aplicaciones administradas. Los dominios de aplicación que se usan para ejecutar código de confianza parcial tienen permisos que definen los recursos protegidos que están disponibles cuando se ejecuta el código en ese AppDomain. El código que se ejecuta en el AppDomain está limitado por los permisos asociados al AppDomain y solo puede obtener acceso a los recursos especificados. El AppDomain también incluye una matriz StrongName que se utiliza para identificar los ensamblados que se van a cargar como de plena confianza. Esto permite al creador de un AppDomain iniciar un nuevo dominio en espacio aislado que permite que determinados ensamblados auxiliares sean de plena confianza. Otra opción para cargar ensamblados como de plena confianza consiste en colocarlos en la memoria caché global de ensamblados; sin embargo, de este modo, los ensamblados se cargarán como de plena confianza en todos los dominios de aplicación creados en ese equipo. La lista de nombres seguros admite decisiones porAppDomain que proporcionan una determinación más restrictiva.

Puede utilizar la AppDomain.CreateDomain(String, Evidence, AppDomainSetup, PermissionSet, StrongName[]) sobrecarga de método para especificar el conjunto de permisos para las aplicaciones que se ejecutan en un recinto. Esta sobrecarga permite especificar el nivel exacto de seguridad de acceso del código que desea. Los ensamblados que se cargan en un AppDomain mediante esta sobrecarga pueden tener solo el conjunto de permisos especificado o pueden ser de plena confianza. Se concede plena confianza al ensamblado si está en la memoria caché global de ensamblados o si figura en el parámetro fullTrustAssemblies (el parámetro de matriz StrongName). Solo se deben agregar a la lista de fullTrustAssemblies los ensamblados que se sepa que son de plena confianza.

La sobrecarga tiene la siguiente firma:

AppDomain.CreateDomain( string friendlyName,
                        Evidence securityInfo,
                        AppDomainSetup info,
                        PermissionSet grantSet,
                        params StrongName[] fullTrustAssemblies);

Los parámetros para la sobrecarga del método CreateDomain(String, Evidence, AppDomainSetup, PermissionSet, StrongName[]) especifican el nombre del AppDomain, la evidencia del AppDomain, el objeto AppDomainSetup que identifica la base de la aplicación del espacio aislado, el conjunto de permisos que se va a usar y los nombres seguros de los ensamblados de plena confianza.

Por seguridad, la base de la aplicación especificada en el parámetro info no debe ser la base de la aplicación de la aplicación de hospedaje.

Para el parámetro grantSet, puede especificar un conjunto de permisos que se ha creado explícitamente o un conjunto de permisos estándar creado por el método GetStandardSandbox.

A diferencia de la mayoría de las cargas de AppDomain, no se usa la evidencia de AppDomain (proporcionada por el parámetro securityInfo) para determinar el conjunto de permisos concedidos para los ensamblados de plena confianza. En su lugar, el parámetro grantSet lo especifica de forma independiente. Sin embargo, la evidencia se puede utilizar para otros propósitos, como determinar el ámbito del almacenamiento aislado.

Para ejecutar una aplicación en un recinto

  1. Cree el conjunto de permisos que se va a conceder a la aplicación que no es de confianza. El permiso mínimo que se puede conceder es el permiso Execution. También puede conceder permisos adicionales que, en su opinión, son seguros para el código que no es de confianza; por ejemplo, IsolatedStorageFilePermission. El código siguiente crea un nuevo conjunto de permisos con solo el permiso Execution.

    PermissionSet permSet = new PermissionSet(PermissionState.None);
    permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
    

    Otra opción consiste en utilizar un conjunto de permisos con nombre existente, como Internet.

    Evidence ev = new Evidence();
    ev.AddHostEvidence(new Zone(SecurityZone.Internet));
    PermissionSet internetPS = SecurityManager.GetStandardSandbox(ev);
    

    El método GetStandardSandbox devuelve un conjunto de permisos Internet o un conjunto de permisos LocalIntranet según la zona en la evidencia. GetStandardSandbox también construye permisos de identidad para algunos de los objetos de evidencia que se pasan como referencias.

  2. Firme el ensamblado que contiene la clase de hospedaje (en este ejemplo, Sandboxer) que llama al código que no es de confianza. Agregue el objeto StrongName utilizado para firmar el ensamblado a la matriz StrongName del parámetro fullTrustAssemblies de la llamada a CreateDomain. La clase de hospedaje debe ejecutarse como de plena confianza para permitir la ejecución del código de confianza parcial u ofrecer servicios a la aplicación de confianza parcial. A continuación se ve cómo se lee el objeto StrongName de un ensamblado:

    StrongName fullTrustAssembly = typeof(Sandboxer).Assembly.Evidence.GetHostEvidence<StrongName>();
    

    No es necesario agregar a la lista de plena confianza los ensamblados de .NET Framework, como mscorlib y System.dll, porque se cargan como de plena confianza desde la memoria caché global de ensamblados.

  3. Inicialice el parámetro AppDomainSetup del método CreateDomain. Con este parámetro, puede controlar gran parte de la configuración del nuevo AppDomain. La propiedad ApplicationBase es un valor importante y debe ser diferente de la propiedad ApplicationBase del AppDomain de la aplicación de hospedaje. Si los valores de ApplicationBase son los mismos, la aplicación de confianza parcial puede lograr que la aplicación de hospedaje cargue (como de plena confianza) una excepción que define y, por tanto, hace uso de la misma. Este es otro motivo por el cual no se recomiendan las excepciones Catch. Al establecer en valores distintos la base de la aplicación host y la base de la aplicación en espacio aislado, se mitiga el riesgo de ataques.

    AppDomainSetup adSetup = new AppDomainSetup();
    adSetup.ApplicationBase = Path.GetFullPath(pathToUntrusted);
    
  4. Llame a la sobrecarga del método CreateDomain(String, Evidence, AppDomainSetup, PermissionSet, StrongName[]) para crear el dominio de aplicación con los parámetros especificados.

    La signatura de este método es:

    public static AppDomain CreateDomain(string friendlyName, 
        Evidence securityInfo, AppDomainSetup info, PermissionSet grantSet, 
        params StrongName[] fullTrustAssemblies)
    

    Información adicional:

    • Esta es la única sobrecarga del método CreateDomain que toma PermissionSet como parámetro y, por lo tanto, la única sobrecarga que permite cargar una aplicación con una configuración de confianza parcial.

    • El parámetro evidence no se utiliza para calcular un conjunto de permisos; se utiliza para la identificación por parte de otras características de .NET Framework.

    • Es obligatorio establecer la propiedad ApplicationBase del parámetro info de esta sobrecarga.

    • El parámetro fullTrustAssemblies tiene la palabra clave params, lo que significa que no es necesario crear una matriz StrongName. Se permite pasar 0, 1 o más nombres seguros como parámetros.

    • El código para crear el dominio de aplicación es:

    AppDomain newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);
    
  5. Cargue el código en el AppDomain de espacio aislado que creó. Esto se puede hacer de dos maneras:

    Se da preferencia al segundo método porque permite pasar más fácilmente los parámetros a la nueva instancia de AppDomain. El método CreateInstanceFrom proporciona dos características importantes:

    • Se puede utilizar una base de código que apunta a una ubicación que no contiene el ensamblado.

    • Se puede realizar la creación con un método Assert de plena confianza (PermissionState.Unrestricted), lo que permite crear una instancia de una clase crítica. (Esto sucede cuando el ensamblado no tiene marcas de transparencia y se carga como de plena confianza.) Por consiguiente, hay que tener cuidado de crear solamente código de confianza con esta función y se recomienda crear únicamente instancias de clases de plena confianza en el nuevo dominio de aplicación.

    ObjectHandle handle = Activator.CreateInstanceFrom(
    newDomain, typeof(Sandboxer).Assembly.ManifestModule.FullyQualifiedName,
           typeof(Sandboxer).FullName );
    

    Observar que para crear una instancia de una clase en un nuevo dominio, la clase debe extender la clase MarshalByRefObject

    class Sandboxer:MarshalByRefObject
    
  6. Desencapsule la nueva instancia del dominio en una referencia de este dominio. Esta referencia se utiliza para ejecutar el código que no es de confianza.

    Sandboxer newDomainInstance = (Sandboxer) handle.Unwrap();
    
  7. LLame al método ExecuteUntrustedCode en la instancia de la clase Sandboxer recién creada.

    newDomainInstance.ExecuteUntrustedCode(untrustedAssembly, untrustedClass, entryPoint, parameters);
    

    Esta llamada se ejecuta en el dominio de aplicación en espacio aislado, el cual tiene permisos restringidos.

    public void ExecuteUntrustedCode(string assemblyName, string typeName, string entryPoint, Object[] parameters)
        {
            //Load the MethodInfo for a method in the new assembly. This might be a method you know, or 
            //you can use Assembly.EntryPoint to get to the entry point in an executable.
            MethodInfo target = Assembly.Load(assemblyName).GetType(typeName).GetMethod(entryPoint);
            try
            {
                // Invoke the method.
                target.Invoke(null, parameters);
            }
            catch (Exception ex)
            {
            //When information is obtained from a SecurityException extra information is provided if it is 
            //accessed in full-trust.
                (new PermissionSet(PermissionState.Unrestricted)).Assert();
                Console.WriteLine("SecurityException caught:\n{0}", ex.ToString());
    CodeAccessPermission.RevertAssert();
                Console.ReadLine();
            }
        }
    

    Se utiliza System.Reflection para obtener el identificador de un método en el ensamblado de confianza parcial. El identificador puede utilizarse para ejecutar el código de manera segura con permisos mínimos.

    En el código anterior, observe el método Assert para el permiso de plena confianza antes de imprimir SecurityException.

    new PermissionSet(PermissionState.Unrestricted)).Assert()
    

    La aserción de plena confianza se usa para obtener información extendida de SecurityException. Sin Assert, el método ToString de SecurityException detectará que hay código de confianza parcial en la pila y restringirá la información devuelta. Esto podría ocasionar problemas de seguridad si el código de confianza parcial pudiera leer esa información, pero el riesgo se mitiga porque no se concede el permiso UIPermission. La aserción de plena confianza debe usarse con moderación y solamente cuando se está seguro de que no se permite la elevación de código de confianza parcial a código de plena confianza. Como regla general, no llame a código que no sea de confianza en la misma función ni después de invocar una aserción de plena confianza. Se recomienda revertir siempre la aserción después de haber terminado de usarla.

Ejemplo

En el siguiente ejemplo, se implementa el procedimiento descrito en la sección anterior. En el ejemplo, el proyecto denominado Sandboxer de una solución de Visual Studio contiene también el proyecto denominado UntrustedCode, que implementa la clase UntrustedClass. En este escenario se supone que se ha descargado un ensamblado de biblioteca que contiene un método del que se espera que devuelva true o false para indicar si el número proporcionado es un número de Fibonacci. Sin embargo, el método intenta leer un archivo del equipo. En el ejemplo siguiente, se muestra el código que no es de confianza.

using System;
using System.IO;
namespace UntrustedCode
{
    public class UntrustedClass
    {
        // Pretend to be a method checking if a number is a Fibonacci
        // but which actually attempts to read a file.
        public static bool IsFibonacci(int number)
        {
           File.ReadAllText("C:\\Temp\\file.txt");
           return false;
        }
    }
}

En el ejemplo siguiente, se muestra el código de aplicación de Sandboxer que ejecuta el código que no es de confianza.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security;
using System.Security.Policy;
using System.Security.Permissions;
using System.Reflection;
using System.Runtime.Remoting;

//The Sandboxer class needs to derive from MarshalByRefObject so that we can create it in another 
// AppDomain and refer to it from the default AppDomain.
class Sandboxer : MarshalByRefObject
{
    const string pathToUntrusted = @"..\..\..\UntrustedCode\bin\Debug";
    const string untrustedAssembly = "UntrustedCode";
    const string untrustedClass = "UntrustedCode.UntrustedClass";
    const string entryPoint = "IsFibonacci";
    private static Object[] parameters = { 45 };
    static void Main()
    {
        //Setting the AppDomainSetup. It is very important to set the ApplicationBase to a folder 
        //other than the one in which the sandboxer resides.
        AppDomainSetup adSetup = new AppDomainSetup();
        adSetup.ApplicationBase = Path.GetFullPath(pathToUntrusted);

        //Setting the permissions for the AppDomain. We give the permission to execute and to 
        //read/discover the location where the untrusted code is loaded.
        PermissionSet permSet = new PermissionSet(PermissionState.None);
        permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));

        //We want the sandboxer assembly's strong name, so that we can add it to the full trust list.
        StrongName fullTrustAssembly = typeof(Sandboxer).Assembly.Evidence.GetHostEvidence<StrongName>();

        //Now we have everything we need to create the AppDomain, so let's create it.
        AppDomain newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);

        //Use CreateInstanceFrom to load an instance of the Sandboxer class into the
        //new AppDomain. 
        ObjectHandle handle = Activator.CreateInstanceFrom(
            newDomain, typeof(Sandboxer).Assembly.ManifestModule.FullyQualifiedName,
            typeof(Sandboxer).FullName
            );
        //Unwrap the new domain instance into a reference in this domain and use it to execute the 
        //untrusted code.
        Sandboxer newDomainInstance = (Sandboxer) handle.Unwrap();
        newDomainInstance.ExecuteUntrustedCode(untrustedAssembly, untrustedClass, entryPoint, parameters);
    }
    public void ExecuteUntrustedCode(string assemblyName, string typeName, string entryPoint, Object[] parameters)
    {
        //Load the MethodInfo for a method in the new Assembly. This might be a method you know, or 
        //you can use Assembly.EntryPoint to get to the main function in an executable.
        MethodInfo target = Assembly.Load(assemblyName).GetType(typeName).GetMethod(entryPoint);
        try
        {
            //Now invoke the method.
            bool retVal = (bool)target.Invoke(null, parameters);
        }
        catch (Exception ex)
        {
            // When we print informations from a SecurityException extra information can be printed if we are 
            //calling it with a full-trust stack.
            (new PermissionSet(PermissionState.Unrestricted)).Assert();
            Console.WriteLine("SecurityException caught:\n{0}", ex.ToString());
            CodeAccessPermission.RevertAssert();
            Console.ReadLine();
        }
    }
}

Vea también

Conceptos

Instrucciones de codificación segura