Gewusst wie: Ausführen von teilweise vertrauenswürdigem Code in einer Sandbox
Bei der Verwendung eines Sandkastens wird Code in einer beschränkten Sicherheitsumgebung ausgeführt, wodurch die dem Code gewährten Zugriffsberechtigungen eingeschränkt werden. Beispielsweise sollten Sie eine verwaltete Bibliothek, die aus einer nicht voll vertrauenswürdigen Quelle stammt, nicht als voll vertrauenswürdig ausführen. Stattdessen sollten Sie den Code in einem Sandkasten platzieren, durch den die Berechtigungen auf den erwartungsgemäß benötigten Umfang eingeschränkt werden, z. B. die Execution-Berechtigung.
Darüber hinaus können Sie mit einem Sandkasten zu verteilenden Code testen, der in teilweise vertrauenswürdigen Umgebungen ausgeführt wird.
Eine AppDomain ist eine wirksame Möglichkeit, um einen Sandkasten für verwaltete Anwendungen bereitzustellen. Anwendungsdomänen, die zum Ausführen von teilweise vertrauenswürdigem Code verwendet werden, verfügen über Berechtigungen, die die geschützten Ressourcen definieren, die beim Ausführen in dieser AppDomain verfügbar sind. In der AppDomain ausgeführter Code ist auf die Berechtigungen beschränkt, die der AppDomain zugeordnet sind, und kann nur auf die angegebenen Ressourcen zugreifen. Die AppDomain enthält auch ein StrongName-Array, mit dem die als voll vertrauenswürdig zu ladenden Assemblys identifiziert werden. Dadurch kann der Ersteller einer AppDomain eine neue Sandkastendomäne starten, in der bestimmte Hilfsassemblys als voll vertrauenswürdig festgelegt werden können. Assemblys können auch im globalen Assemblycache gespeichert werden, um sie als voll vertrauenswürdige Assemblys zu laden. Dadurch werden Assemblys jedoch in allen auf diesem Computer erstellten Anwendungsdomänen als vertrauenswürdig geladen. Die Liste der starken Namen unterstützt eine Entscheidung pro AppDomain, die eine restriktivere Festlegung ermöglicht.
Sie können mit der AppDomain.CreateDomain(String, Evidence, AppDomainSetup, PermissionSet, StrongName[])-Methodenüberladung den Berechtigungssatz für Anwendungen festlegen, die in einer Sandbox ausgeführt werden. Durch diese Überladung können Sie die genaue Ebene der gewünschten Codezugriffssicherheit angeben. Assemblys, die mithilfe dieser Überladung in eine AppDomain geladen werden, können entweder über den festgelegten Berechtigungssatz verfügen oder voll vertrauenswürdig sein. Der Assembly wird volle Vertrauenswürdigkeit gewährt, wenn sie sich im globalen Assemblycache befindet oder in der Liste im fullTrustAssemblies-Arrayparameter (StrongName) enthalten ist. Der fullTrustAssemblies-Liste sollten nur Assemblys hinzugefügt werden, die als voll vertrauenswürdig bekannt sind.
Die Überladung hat folgende Signatur:
AppDomain.CreateDomain( string friendlyName,
Evidence securityInfo,
AppDomainSetup info,
PermissionSet grantSet,
params StrongName[] fullTrustAssemblies);
Die Parameter für die CreateDomain(String, Evidence, AppDomainSetup, PermissionSet, StrongName[])-Methodenüberladung geben den Namen der AppDomain, den Beweis für die AppDomain, das AppDomainSetup-Objekt, das die Anwendungsbasis für den Sandkasten angibt, den zu verwendenden Berechtigungssatz und die starken Namen für voll vertrauenswürdige Assemblys an.
Aus Sicherheitsgründen sollte im info-Parameter nicht die Anwendungsbasis für die Hostanwendung als Anwendungsbasis festgelegt werden.
Für den grantSet-Parameter können Sie einen Berechtigungssatz, den Sie explizit erstellt haben, oder aber einen von der GetStandardSandbox-Methode erstellten Standardberechtigungssatz angeben.
Im Gegensatz zu den meisten AppDomain-Ladungen wird der (vom securityInfo-Parameter bereitgestellte) Beweis für die AppDomain nicht zum Bestimmen des Berechtigungssatzes für die teilweise vertrauenswürdigen Assemblys verwendet. Stattdessen wird er unabhängig vom grantSet-Parameter angegeben. Der Beweis kann jedoch für andere Zwecke verwendet werden, z. B. zum Bestimmen des isolierten Speicherbereichs.
So führen Sie eine Anwendung in einer Sandbox aus
Erstellen Sie den Berechtigungssatz, der der nicht vertrauenswürdigen Anwendung gewährt werden soll. Die Mindestberechtigung, die Sie gewähren können, ist die Execution-Berechtigung. Sie können auch zusätzliche Berechtigungen gewähren, die Sie für nicht vertrauenswürdigen Code als sicher erachten, z. B. IsolatedStorageFilePermission. Mit dem folgenden Code wird ein neuer Berechtigungssatz erstellt, der nur die Execution-Berechtigung enthält.
PermissionSet permSet = new PermissionSet(PermissionState.None); permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
Alternativ können Sie einen vorhandenen benannten Berechtigungssatz verwenden, z. B. Internet.
Evidence ev = new Evidence(); ev.AddHostEvidence(new Zone(SecurityZone.Internet)); PermissionSet internetPS = SecurityManager.GetStandardSandbox(ev);
Die GetStandardSandbox-Methode gibt abhängig von der Zone im Beweis einen Internet-Berechtigungssatz oder einen LocalIntranet-Berechtigungssatz zurück. GetStandardSandbox erstellt auch Identitätsberechtigungen für einige der als Verweise übergebenen Beweisobjekte.
Signieren Sie die Assembly mit der Hostingklasse (in diesem Beispiel Sandboxer), die den nicht vertrauenswürdigen Code aufruft. Fügen Sie den zum Signieren der Assembly verwendeten StrongName dem StrongName-Array des fullTrustAssemblies-Parameters für den CreateDomain-Aufruf hinzu. Die Hostingklasse muss als voll vertrauenswürdige Klasse ausgeführt werden, damit teilweise vertrauenswürdiger Code ausgeführt werden kann oder Dienste für die teilweise vertrauenswürdige Anwendung bereitgestellt werden können. Der StrongName einer Assembly wird folgendermaßen gelesen:
StrongName fullTrustAssembly = typeof(Sandboxer).Assembly.Evidence.GetHostEvidence<StrongName>();
.NET Framework-Assemblys wie mscorlib und System.dll müssen der Liste der voll vertrauenswürdigen Assemblys nicht hinzugefügt werden, da sie als voll vertrauenswürdige Assemblys aus dem globalen Assemblycache geladen werden.
Initialisieren Sie den AppDomainSetup-Parameter der CreateDomain-Methode. Mit diesem Parameter können viele Einstellungen der neuen AppDomain gesteuert werden. Die ApplicationBase-Eigenschaft ist eine wichtige Einstellung und sollte sich von der ApplicationBase-Eigenschaft für die AppDomain der Hostinganwendung unterscheiden. Wenn die ApplicationBase-Einstellungen identisch sind, kann die teilweise vertrauenswürdige Anwendung die Hostanwendung ausnutzen, indem sie diese anweist, eine von ihr definierte Ausnahme (mit voller Vertrauenswürdigkeit) zu laden. Dies ist ein weiterer Grund, weshalb die Verwendung eines Catch-Blocks (Ausnahme) nicht empfohlen wird. Das Risiko von Angriffen kann reduziert werden, indem die Anwendungsbasis des Hosts und der Sandkastenanwendung auf unterschiedliche Werte festgelegt werden.
AppDomainSetup adSetup = new AppDomainSetup(); adSetup.ApplicationBase = Path.GetFullPath(pathToUntrusted);
Rufen Sie die CreateDomain(String, Evidence, AppDomainSetup, PermissionSet, StrongName[])-Methodenüberladung auf, um die Anwendungsdomäne mit den angegebenen Parametern zu erstellen.
Die Signatur für diese Methode lautet wie folgt:
public static AppDomain CreateDomain(string friendlyName, Evidence securityInfo, AppDomainSetup info, PermissionSet grantSet, params StrongName[] fullTrustAssemblies)
Zusätzliche Informationen:
Dies ist die einzige Überladung der CreateDomain-Methode, die einen PermissionSet als Parameter akzeptiert und folglich die einzige Überladung, mit der Sie eine teilweise vertrauenswürdige Anwendung laden können.
Mit dem evidence-Parameter wird kein Berechtigungssatz berechnet. Er wird von anderen Funktionen des .NET Framework zur Identifikation verwendet.
Die ApplicationBase-Eigenschaft des info-Parameters muss für diese Überladung festgelegt werden.
Der fullTrustAssemblies-Parameter enthält das params-Schlüsselwort, d. h., es muss kein StrongName-Array erstellt werden. Die Übergabe von keinem (null), einem oder mehr starken Namen als Parameter ist zulässig.
Im Folgenden sehen Sie den Code zum Erstellen der Anwendungsdomäne:
AppDomain newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);
Laden Sie den Code in die erstellte Sandkasten-AppDomain. Dies kann auf zwei Arten ausgeführt werden:
Rufen Sie die ExecuteAssembly-Methode für die Assembly auf.
Erstellen Sie mit der CreateInstanceFrom-Methode eine Instanz einer von AppDomain abgeleiteten Klasse im neuen MarshalByRefObject.
Die zweite Methode ist vorzuziehen, da sie die Übergabe von Parametern an die neue AppDomain-Instanz vereinfacht. Die CreateInstanceFrom-Methode stellt zwei wichtige Funktionen bereit:
Sie können eine CodeBase verwenden, die auf einen Speicherort zeigt, der die Assembly nicht enthält.
Für volle Vertrauenswürdigkeit (PermissionState.Unrestricted) kann zum Erstellen eine Assert-Methode verwendet werden, die das Erstellen einer Instanz einer kritischen Klasse ermöglicht. (Dies ist immer dann der Fall, wenn die Assembly nicht über Transparenzmarkierungen verfügt und als voll vertrauenswürdige Assembly geladen wird.) Mit dieser Funktion darf daher nur Code erstellt werden, den Sie als vertrauenswürdig erachten. Außerdem wird empfohlen, in der neuen Anwendungsdomäne nur Instanzen voll vertrauenswürdiger Klassen zu erstellen.
ObjectHandle handle = Activator.CreateInstanceFrom( newDomain, typeof(Sandboxer).Assembly.ManifestModule.FullyQualifiedName, typeof(Sandboxer).FullName );
Wenn Sie in einer neuen Domäne eine Instanz einer Klasse erstellen möchten, muss die Klasse die MarshalByRefObject-Klasse erweitern.
class Sandboxer:MarshalByRefObject
Entpacken Sie die neue Domäneninstanz in einem Verweis in dieser Domäne. Dieser Verweis wird verwendet, um den nicht vertrauenswürdigen Code auszuführen.
Sandboxer newDomainInstance = (Sandboxer) handle.Unwrap();
Rufen Sie die ExecuteUntrustedCode-Methode in der soeben erstellten Instanz der Sandboxer-Klasse auf.
newDomainInstance.ExecuteUntrustedCode(untrustedAssembly, untrustedClass, entryPoint, parameters);
Dieser Aufruf wird in der Sandkastenanwendungsdomäne mit eingeschränkten Berechtigungen ausgeführt.
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(); } }
System.Reflection wird verwendet, um in der teilweise vertrauenswürdigen Assembly ein Handle von einer Methode abzurufen. Das Handle kann verwendet werden, um Code sicher mit minimalen Berechtigungen auszuführen.
Beachten Sie im vorherigen Code die Assert für die Berechtigung Voll vertrauenswürdig, bevor Sie SecurityException drucken.
new PermissionSet(PermissionState.Unrestricted)).Assert()
Der Assert für volle Vertrauenswürdigkeit wird verwendet, um erweiterte Informationen aus der SecurityException abzurufen. Ohne die Assert-Methode stellt die ToString-Methode der SecurityException fest, dass der Stapel teilweise vertrauenswürdigen Code enthält, und die zurückgegebenen Informationen werden eingeschränkt. Dies kann Sicherheitsprobleme verursachen, wenn der teilweise vertrauenswürdige Code diese Informationen lesen könnte. Das Risiko wird jedoch reduziert, indem UIPermission nicht gewährt wird. Die volle Vertrauenswürdigkeit sollte sparsam und nur dann verwendet werden, wenn Sie sicher sind, dass dies nicht dazu führen kann, dass teilweise vertrauenswürdiger Code zu voller Vertrauenswürdigkeit erhöht wird. Im Allgemeinen sollte nicht vertrauenswürdiger Code nicht in der gleichen Funktion und nach dem Aufrufen eines Asserts für volle Vertrauenswürdigkeit aufgerufen werden. Es empfiehlt sich, den Assert nach der Verwendung immer zurückzusetzen.
Beispiel
Im folgenden Beispiel wird das im vorhergehenden Abschnitt erläuterte Verfahren implementiert. Im Beispiel enthält ein Projekt mit dem Namen Sandboxer in einer Visual Studio-Projektmappe auch ein Projekt namens UntrustedCode, das die UntrustedClass-Klasse implementiert. In diesem Szenario wird vorausgesetzt, dass Sie eine Bibliotheksassembly heruntergeladen haben, die eine Methode enthält, die durch Rückgabe von true oder false angeben soll, ob es sich bei der angegebenen Zahl um eine Fibonacci-Zahl handelt. Stattdessen versucht die Methode, eine Datei vom Computer zu lesen. Das folgende Beispiel zeigt den nicht vertrauenswürdigen Code.
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;
}
}
}
Das folgende Beispiel zeigt den Sandboxer-Anwendungscode, durch den der nicht vertrauenswürdige Code ausgeführt wird.
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();
}
}
}