środa, 19 maja 2010

Bezpieczne wykorzystanie obcych assembly (C# .NET 2.0 – 3.5) [PL]

Promuj
.NET Framework pozwala na łatwe wczytywanie, uruchamianie i wykorzystywanie obcych assembly. Należy jednak pamiętać, że nie wolno mieć pełnego zaufania do cudzego kodu. Ma to jeszcze większe znaczenie gdy dopuszczamy sytuację, w której pozwalamy na wykonywanie kodu, którego nigdy nie testowaliśmy, z którym nigdy nie mieliśmy do czynienia. Oczywiście w takim przypadku otrzymujemy bardzo dobre wsparcie ze strony .NET Framework, a mianowicie obsługę tzw. Domen aplikacji.
Domena aplikacji (ang. Application Domain), czyli AppDomain, jest logiczną jednostką wykonawczą. Jest to niejako „piaskownica”, w której wykonuje się kod .NET. Domena aplikacji zapewnia kontener, w którym kod może się bezpiecznie wykonywać, ze świadomością, że programy pracujące poza jego domeną aplikacji nie mogą negatywnie na niego wpłynąć. [1]
Tym razem, w nawiązaniu do przykładu z prostą obsługą wtyczek (plugin'ów), chciałbym pokazać jak można wykorzystać mechanizm domen w .NET. Dodam więc pewne zabezpieczenia do wcześniejszego kodu. Tutaj należy jeszcze dodać [1], że w przypadku komunikacji między domenowej (a z taką będziemy mieć do czynienia), należy wykorzystać komunikację międzyprocesową, np. z wykorzystaniem Remoting. To właśnie .NET Remoting zostanie wykorzystany do wykorzystania (zdalnego) obiektu w innej domenie.
W związku z wykorzystaniem .NET Remoting'u należy do wcześniejszego kodu wprowadzić pewne zmiany:
  • klasa wykorzystywana poprzez wtyczkę (w przykładzie Class1) musi być serializowalna (oznaczamy ją więc atrybutem [Serializable]);
  • należy zrezygnować z obsługi wtyczki poprzez interfejs, a zamiast tego wykorzystać klasę abstracyjną, która będzie dziedziczyła po MarshalByRefObject)
Ponadto przykład został rozbudowany o następujące elementy:
  • obsługę wyjątków i wyświetlanie informacji o nich do użytkownika;
  • do wtyczki zostanie wprowadzony „zły” kod, który zwraca jako łańcuch tekstowy zawartość pliku win.ini;
  • w aplikacji obsługującej wtyczki zostanie stworzona domena MyNewSecureDomain, do której ładowane będą pliki DLL z wtyczkami;
  • do utworzonej domeny nowe assembly będą ładowane z wykorzystaniem zabezpieczeń odpowiadających kodu pobranego z internetu (SecurityZone.Internet).
Ostatecznie mamy następujący kod (polecam przeczytanie komentarzy, które uzupełniają powyższy opis).
Class1.cs:
using System;
using System.IO;
using ChangeSupportFramework;
namespace PluginLibrary1
{
    [Serializable] //this remote object must be serializable
    public class Class1 : DoSthAbstract
    {
        #region IDoSth Members
        public override string DoSth()
        {
            string winini = "";
            using (StreamReader reader = new StreamReader(@"C:\Windows\win.ini"))
            {
                winini = reader.ReadToEnd();
            }
            return " Plugin Class, Win.ini: " + winini;
        }
        #endregion
    }
}
Program.cs:
using System;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting;
using System.Security;
using System.Security.Policy;
namespace ChangeSupportFramework
{
    public abstract class DoSthAbstract : MarshalByRefObject //this is important that this class is MarshalByRefObject
    {
        public abstract string DoSth();
    }
    class Program
    {
        static void Main()
        {
            //selecting current directory
            DirectoryInfo di = new DirectoryInfo(".");
            //checking all DLL files in the current directory that start from "Plugin"
            foreach (FileInfo fi in di.GetFiles("Plugin*.dll"))
            {
                try
                {
                    //creating evidence for the new domain
                    //we are trusting plugin as any internet application, our security setting
                    //are base on configured setting in the operating system: SecurityZone.Internet
                    object[] hostEvidence = { new Zone(SecurityZone.Internet) };
                    Evidence internetEvidence = new Evidence(hostEvidence, null);
                    //creation of new domain: MyNewSecureDomain
                    AppDomain myDomain = AppDomain.CreateDomain("MyNewSecureDomain");
                    //creation of Assembly Name based on assembly file from "Plugin*.dll" set
                    AssemblyName asname = AssemblyName.GetAssemblyName(fi.FullName);
                    //loading of the Plugin to particular Domain with Internet Zone restriction
                    Assembly pluginAssembly = myDomain.Load(asname, internetEvidence);
                    //checking all public types from loaded assembly
                    foreach (Type pluginType in pluginAssembly.GetExportedTypes())
                    {
                        //now we are checking whether particular type implements 
                        //IDoSth interface
                        if (pluginType.IsSubclassOf(typeof(DoSthAbstract)))// != null)
                        {
                            //creating the instance of selected type
                            //IMPORTANT: we have to passed evidence here
                            ObjectHandle OHpluginType = myDomain.CreateInstance(asname.FullName, pluginType.FullName);
                            //here is an important place, it is possible to use CreateInstance as is it shown above
                            //this is because we have already leaded the assembly to secure domain created before
                            //however it is possible not to load the assebly ealier and pass the secure evidence now:
                            //ObjectHandle OHpluginType = myDomain.CreateInstance(asname.FullName, pluginType.FullName),
                            //   false, 0, null, null, null, null, internetEvidence);
                            //to use remote object we have to Unwrap it
                            //see: http://msdn.microsoft.com/en-us/library/system.runtime.remoting.objecthandle.aspx
                            DoSthAbstract TypeLoadedFromPlugin = (DoSthAbstract)OHpluginType.Unwrap();
                            //do sht with the plugin
                            Console.WriteLine(TypeLoadedFromPlugin.DoSth());
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Cannot use plugin: {0}, the reason is: {1}", fi.Name, ex.Message);
                }
            }
            Console.ReadKey();
        }
    }
}
Po uruchomieniu możemy podziwiać ekran z wyjątkiem:
Zamiast (w przypadku niewykorzystania zabezpieczeń):

Co jeszcze?

Powyższy mechanizm można wykorzystać nie tylko do ładowania i wykorzystania wtyczek, ale też do uruchomiania innych aplikacji z poziomu naszej aplikacji, w tym celu należy wykorzystać metodę ExecuteAssembly klasy AppDomain, na przykład jak to pokazano poniżej:
object[] hostEvidence = { new Zone(SecurityZone.Internet) };
Evidence internetEvidence = new Evidence(hostEvidence, null);
AppDomain myDomain = AppDomain.CreateDomain("MyDomain");
myDomain.ExecuteAssembly("PluginLibrary1AppUser.exe", internetEvidence);

Na koniec kubeł zimnej wody

Dla tych, którym rozwiązanie się spodobało, dla ochłody informacja:
Powyższy kod nie zadziała dla .NET 4.0, a otrzymamy tylko:
This method implicitly uses CAS policy, which has been obsoleted by the .NET Framework. In order to enable CAS policy for compatibility reasons, please use the NetFx40_LegacySecurityPolicy configuration switch. Please see http://go.microsoft.com/fwlink/?LinkID=155570 for more information.
Można to obejść konfigurując app.config, jak pokazano niżej, ale chyba nie o to chodzi...
<configuration>
   <runtime>
      <NetFx40_LegacySecurityPolicy enabled="true"/>
   </runtime>
</configuration>
Postaram się kiedyś wrócić do tematu i opisać jak to powinno być wykonane w .NET Framework 4.0.

Literatura

[1] "Microsoft Visual C# 2005 Księga eksperta", Autor: Kevin HoffMan, Wydawnictwo: Helion, 2007
[4] „MCTS Self-Paced Training Kit (Exam 70-536): Microsoft .NET Framework Application Development Foundation 2nd edition”, Autor: Tony Northrup , Wydawnictwo: Microsoft Press, 2009
Promuj

1 komentarz:

Posty powiązane / Related posts