Home   Cover Cover Cover Cover
 

Sicherheit

Beispiel "IsSubsetOf - implizite Genehmigungen"

Bei der Vergabe von Rechten muss man immer darauf achten, dass man nicht zuviel erlaubt. Viele Genehmigungen schließen andere mit ein, d.h. sie sind eine Obermenge der anderen Genehmigung oder - anders ausgedrückt - sie implizieren die andere Genehmigung.
Um festzustellen, ob eine Genehmigung eine andere impliziert, enthält das Interface System.Security.IPermission eine Methode IsSubsetOf.
Im Beispiel erzeugen wir drei Genehmigungen und testen dann kreuzweise, welche Genehmigung von welcher impliziert wird. Die Ausgabe zeigt die tatsächlichen Impliziert-Beziehungen an.

PermissionSubset.cs
using System;
using System.Security;
using System.Security.Permissions;

public class A {
  public static void Main () {
    CodeAccessPermission
      p1 = new FileIOPermission(FileIOPermissionAccess.Read, "c:\\Temp"),
      p2 = new FileIOPermission(FileIOPermissionAccess.AllAccess, "c:\\Temp"),
      p3 = new FileIOPermission(FileIOPermissionAccess.Read, "c:\\");

    if (p1.IsSubsetOf(p2)) Console.WriteLine("p1 is subset of p2 OR p2 implies p1.");
    if (p1.IsSubsetOf(p3)) Console.WriteLine("p1 is subset of p3 OR p3 implies p1.");
    if (p2.IsSubsetOf(p1)) Console.WriteLine("p2 is subset of p1 OR p1 implies p2.");
    if (p2.IsSubsetOf(p3)) Console.WriteLine("p2 is subset of p3 OR p3 implies p2.");
    if (p3.IsSubsetOf(p1)) Console.WriteLine("p3 is subset of p1 OR p1 implies p3.");
    if (p3.IsSubsetOf(p2)) Console.WriteLine("p3 is subset of p2 OR p2 implies p3.");
  }
}



Wir sehen, dass p1 (= nur Leserechte auf c:\Temp) sowohl eine Untermenge von p2 (= alle Zugriffsrechte auf c:\Temp) als auch ein Untermenge von p3 (= Leserechte auf c:\) darstellt.
Das sollte man bei der Vergabe von Rechten stets bedenken und immer versuchen, nur die kleinstmögliche Menge an Genehmigungen zu erteilen.

Beispiel "Demand - Überprüfung der Rechte"

Bevor man in seinem Programmcode auf eine Ressource zugreift, muss überprüft werden, ob auch alle Rufer tatsächlich die dafür notwendigen Rechte besitzen.
Im Beispiel wollen wir auf eine Datei lesend zugreifen. Mit der Methode Demand prüfen wir daher zuvor, ob wir Leserechte auf das Verzeichnis besitzen, das die Datei enthält. Nach erfolgreicher Prüfung lesen wir den Text aus der Datei und geben ihn auf der Konsole aus. Je nachdem, ob wir alles lesen konnten, geben wir true bzw. false zurück.

SecureAccess0.cs
using System;
using System.IO;
using System.Security;
using System.Security.Permissions;

public class SecureAccess {

  public static bool ReadFile (string filename) {
  
    // check for necessary permissions
    CodeAccessPermission p = null;
    p = new FileIOPermission(FileIOPermissionAccess.Read, "c:\\Temp\\");
    try {
      p.Demand();
    } catch (SecurityException) {
      Console.WriteLine("Access denied.");
      return false;
    }
    
    // read the file
    StreamReader reader = null;
    try {
      reader = new StreamReader(File.OpenRead("c:\\Temp\\" + filename));
      string line = reader.ReadLine();
      while (line != null) {
        Console.WriteLine(line);
        line = reader.ReadLine();
      }
    } catch (Exception) {
      return false;
    } finally {
      if (reader != null) reader.Close();
    }
    
    return true;
  }
}



SecureAccess0 ist nur eine Bibliotheksklasse, die wir in SecureAccessTester0 verwenden.

SecureAccessTester0.cs
using System;

class SecureAccessTester {
  public static void Main () {
    bool readOK = SecureAccess.ReadFile("F.txt");
    Console.WriteLine("Reading file F.txt: " + ((readOK) ? "Success" : "Failure"));
  }
}



Offensichtlich hatte die Applikation die erforderlichen Genehmigungen (in der Grundeinstellung des .NET-Framework hat Code von der lokalen Festplatte alle Rechte). Daher konnte die Datei gelesen werden.

Beispiel "Deny - Genehmigungen verbieten"

Es gibt auch die Möglichkeit in die Vergabe der Rechte - genauer in den Security Stack Walk (= Rechteüberprüfung zur Laufzeit) - direkt einzugreifen. Dazu bietet die .NET-Plattform drei spezielle Mengen von Genehmigungen an:
  • Assert-Menge: wenn die geforderte Genehmigung in dieser Menge enthalten ist, wird der Stack Walk sofort abgebrochen und die Genehmigung wird erteilt.
  • Deny-Menge: wenn die geforderte Genehmigung in dieser Menge enthalten ist, wird der Stack Walk sofort abgebrochen und die Genehmigung wird nicht erteilt.
  • PermitOnly-Menge: wenn die geforderte Genehmigung nicht in dieser Menge enthalten ist, wird der Stack Walk sofort abgebrochen und die Genehmigung wird nicht erteilt.
In allen anderen Fällen wird der Security Stack Walk einfach fortgesetzt.
Im Beispiel verändern wir den SecureAccessTester aus dem vorigen Beispiel so, dass nun alle Zugriffe auf das gesamte Laufwerk C: verboten werden.

SecureAccessTester1.cs
using System;
using System.Security;
using System.Security.Permissions;

class SecureAccessTester {
  public static void Main () {
    CodeAccessPermission p1;
    p1 = new FileIOPermission(FileIOPermissionAccess.AllAccess, "c:\\");
    p1.Deny();
    
    bool readOK = SecureAccess.ReadFile("F.txt");
    Console.WriteLine("Reading file F.txt: " + ((readOK) ? "Success" : "Failure"));
  }
}




Wenn wir diese Applikation nun laufen lassen, gelingt es uns nicht mehr die Datei c:\Temp\F.txt zu lesen, weil uns die notwendigen Rechte nicht erteilt werden. Wir sehen aber, dass der SecurityException-Handler aus SecureAccess.ReadFile ausgeführt wurde (Ausgabe: Access denied.). D.h. also, dass p.Demand() gescheitert ist und eine SecurityException ausgelöst hat.
Hier sei noch angemerkt, dass solche Veränderungen am »normalen« Security Stack Walk auch nur von Assemblies mit entsprechenden Rechten vorgenommen werden dürfen.

Beispiel "Deklarative Sicherheit"

Neben der »imperativen« Variante Sicherheitsaspekte auszudrücken (siehe Beispiel "Demand" oder Beispiel "Imperative Sicherheit"), gibt es auch noch die Möglichkeit, dies mithilfe von Attributen zu tun. Man spricht dann von »deklarativer« Sicherheit.
Im Beispiel verändern wir SecureAccess so, dass nun deklarativ Leserechte für das Verzeichnis c:\Temp für die gesamte Methode ReadFile gefordert werden (Attribut FileIOPermissionAttribute).

SecureAccess1.cs
using System;
using System.IO;
using System.Security;
using System.Security.Permissions;

public class SecureAccess {

  [FileIOPermission(SecurityAction.Demand, Read="c:\\Temp\\")]
  public static bool ReadFile (string filename) {
    
    // read the file
    StreamReader reader = null;
    try {
      reader = new StreamReader(File.OpenRead("c:\\Temp\\" + filename));
      string line = reader.ReadLine();
      while (line != null) {
        Console.WriteLine(line);
        line = reader.ReadLine();
      }
    } catch (Exception) {
      return false;
    } finally {
      if (reader != null) reader.Close();
    }
    
    return true;
  }
}

Wir testen diese Variante nun mit den beiden SecureAccessTester Varianten ...



... und sehen, dass dies fast genauso funktioniert, wie die imperative Variante in den vorigen zwei Beispielen. Einziger Unterschied zur imperativen Variante ist, dass wir nun die SecurityException nicht mehr in der SecureAccess.ReadFile-Methode abfangen und behandeln können. Wir müssten dies in der Main-Methode von SecureAccessTester tun.

Beispiel "Imperative Sicherheit"

Ein Vorteil der »imperativen« Sicherheit (siehe Beispiel "Demand") liegt auch darin, Sicherheitsaspekte erst zur Laufzeit konkretisieren zu müssen.
Im Beispiel verändern wir die SecureAccess.ReadFile-Methode aus Beispiel "Demand" nur geringfügig (Zeile 12). Dadurch erreichen wir aber, dass immer nur die absolut notwendige Genehmigung angefordert wird - nämlich Leserechte nur für die zur Laufzeit spezifizierte Datei anstelle des gesamten Verzeichnisses.

SecureAccess2.cs
using System;
using System.IO;
using System.Security;
using System.Security.Permissions;

public class SecureAccess {

  public static bool ReadFile (string filename) {
  
    // check for necessary permissions
    CodeAccessPermission p = null;
    p = new FileIOPermission(FileIOPermissionAccess.Read, "c:\\Temp\\" + filename);
    try {
      p.Demand();
    } catch (SecurityException) {
      Console.WriteLine("Access denied.");
      return false;
    }
    
    // read the file
    StreamReader reader = null;
    try {
      reader = new StreamReader(File.OpenRead("c:\\Temp\\" + filename));
      string line = reader.ReadLine();
      while (line != null) {
        Console.WriteLine(line);
        line = reader.ReadLine();
      }
    } catch (Exception) {
      return false;
    } finally {
      if (reader != null) reader.Close();
    }
    
    return true;
  }
}

Wir testen diese Variante auch mit beiden SecureAccessTester Varianten ...



... und sehen, dass dies genauso funktioniert, wie bisher. Diese dynamische Anpassung der geforderten Rechte ist mit »deklarativer Sicherheit« nicht möglich.