Veröffentlicht: 13. Mai 2003 | Aktualisiert: 22. Jun 2004
Von Chris Sells
Chris Sells erläutert Berechtigungen in .NET und wie Sie das Modellobjekt anpassen können, damit Smart Clients geschützt und gleichzeitig bekannten Assemblys oder Sites zusätzliche Berechtigungen erteilt werden, um Benutzern zusätzliche Dienste anbieten zu können.
Auf dieser Seite
Codegruppen
Zulassen von teilweise vertrauenswürdigen Aufrufern
Erteilen von Berechtigungen
Bereitstellen von Berechtigungen
Schlussfolgerung
Danksagung
Nachdem ich den Artikel mit dem imposanten Titel .NET Zero Deployment: Security and Versioning Models in the Windows Forms Engine Help You Create and Deploy Smart Clients
(in Englisch) geschrieben hatte, habe ich praktisch das gesamte Land bereist und auf Konferenzen, Benutzergruppenmeetings und Schulungskursen über dieses Thema sowie
über die Vorteile der Bereitstellung von Smart Clients referiert. Wenn ich meinen Zuhörern zeige, wie einfach ich eine ausführbare Windows Forms-Datei in ein virtuelles Verzeichnis stelle, sie mit einem URL aufrufe und sie außerhalb des Browsers ohne erforderliche Installation anzeigen lassen kann, sind sie jedes Mal erstaunt - allerdings nur für ca. drei Sekunden. Dann haben sie sich wieder erholt und fragen "Aber da fehlt doch die Eingabeaufforderung! Und das soll sicher sein?". An dieser Stelle zeige ich ihnen eine Beispielanwendung, die z.B. den Highscore in Mine Sweeper zurücksetzt (wie garstig!), und starte sie aus demselben URL, wobei der Benutzer dann vom Dialogfeld Unbehandelte Ausnahme darüber informiert wird, dass die Anwendung versucht hat, die Sicherheitsrichtlinien zu verletzen, und gestoppt wurde. Daraufhin erläutere ich die beschränkten Berechtigungen von .NET-Assemblys, die über das Intranet oder Internet gestartet werden. Das gefällt meinen Zuhörern wieder.
Wieder nur ca. drei Sekunden lang... Denn dann fragen sie "Aber wie schreibe ich eine Anwendung, die <etwas tun soll, von dem ich gerade gesagt habe, sie dürfe es nicht tun>?". Natürlich möchten Entwickler nicht auf die komfortable Bereitstellung von Smart Clients verzichten - allerdings ohne die unbequeme Aufgabe, Probleme der Sicherheitsverletzung beheben zu müssen. Das aktuelle .NET Framework ermöglicht es beispielsweise noch nicht einmal, dass eine Assembly aus dem Intranet auf Active Directory® zugreift, und gemäß Service Pack1 (SP1) kann eine Assembly aus dem Internet überhaupt nicht ausgeführt werden. Diese beiden Einschränkungen wurden jedoch aus verschiedenen Gründen errichtet.
Codegruppen
Code in .NET erhält Berechtigungen auf der Grundlage von Beweisen, womit in der Regel der Ursprungsort der Assembly gemeint ist. Wenn eine Assembly z.B. aus dem Intranet stammt, erhält sie die Berechtigungen aus dem Intranet-Berechtigungssatz. Sie können die Berechtigungen in den gegenwärtigen Berechtigungssätzen auf Ihrem Computer unter Verwendung des Microsoft .NET Framework-Konfigurationstools anzeigen, das im Menü Start unter Administrative Tools (Verwaltung) zu finden ist. Die Berechtigungssätze Ihres Computers stehen unter Runtime Security Policy | Machine | Permission Sets (Laufzeitsicherheitsrichtlinie | Computer | Berechtigungssätze) zur Verfügung (siehe Abbildung 1).
Abbildung 1. Berechtigungssätze mit Standardnamen
Beachten Sie, dass der Internet-Berechtigungssatz in Abbildung 1 ausgewählt ist und eine Anzahl von Vorgängen aufführt, zu deren Ausführung die Assemblys durch den Internet-Berechtigungssatz ermächtigt sind. Die Namen der Berechtigungssätze jedoch sind ein wenig irreführend, insbesondere der des Internet-Berechtigungssatzes. Standardmäßig erhalten Assemblys aus dem Internet laut SP1 keine Berechtigungen aus dem Internet-Berechtigungssatz, sondern aus dem Nothing-Berechtigungssatz (also gar keine, wie Sie sich anhand des Namens denken können). Der Grund hierfür ist, dass Berechtigungslisten nichts mit der Erteilung von Berechtigungssätzen zu tun haben. Die Zuordnung geschieht über Codegruppen.
Mit einer Codegruppe wird festgelegt, auf welche Art und Weise Assemblys ihre Berechtigungssätze erhalten. Sie kombiniert eine Mitgliedsschaftsbedingung mit einem Berechtigungssatz. Wenn die Mitgliedsschaftsbedingung einer geladenen Assembly entspricht, erhält die Assembly - mit Ausnahme einiger undurchsichtiger Ausnahmen - diese Berechtigungen. Die Standardcodegruppen finden Sie in Abbildung 2.
Abbildung 2. .NET-Standardcodegruppen
Gemäß SP0 beispielsweise erteilte die Internet_Zone-Codegruppe allen Assemblys aus dem Internet Berechtigungen aus dem Internet-Berechtigungssatz. Gemäß SP1 gewährt die Internet_Zone-Codegruppe allen Assemblys aus dem Internet Berechtigungen aus dem Nothing-Berechtigungssatz. Der Grund also, warum Assemblys aus dem Internet gemäß SP1 nicht ausgeführt werden können, ist, dass sie einschließlich der Security.Execute-Berechtigung überhaupt keine Berechtigungen besitzen.
Sie gewähren Ihren eigenen Assemblys Berechtigungen, indem Sie eine Codegruppe mit einer Mitgliedsschaftsbedingung erstellen, die Ihren Assemblys entspricht, z.B. von welcher Site und welchem URL sie stammen, mit welchem Schlüssel sie signiert sind usw., und sie einem Berechtigungssatz zuordnen. Zudem können Sie Ihren eigenen Berechtigungssatz erstellen oder einen der Standardberechtigungssätze verwenden. Wenn Sie z.B. über das Intranet bereitstellen und Zugriff auf die Active Directory-Dienste benötigen, können Sie einen benutzerdefinierten Berechtigungssatz erstellen, die erforderlichen Berechtigungen einschließlich der Directory Service-Berechtigungen hinzufügen, und diesen dann Ihren Clientcomputern bereitstellen (später mehr zum Bereitstellen von Berechtigungen). Damit ist das Problem aber noch nicht gelöst.
Zulassen von teilweise vertrauenswürdigen Aufrufern
Sie sollten Ihre .NET-Assemblys immer signieren, da signierte Assemblys über einige besondere Eigenschaften verfügen. So können z.B. nur die Versionen signierter Assemblys überprüft werden. Auch werden nur signierte Assemblys vom Ladeclient abgestimmt, um festzustellen, ob die Assembly, die gerade geladen wird, diejenige ist, die für den Client vorgesehen ist. Zudem sind signierte Assemblys besser gegen teilweise vertrauenswürdigen Code geschützt. Hierbei handelt es sich um Code, der nicht über den FullTrust-Berechtigungssatz verfügt. Dieser "bessere Schutz", den eine signierte Assembly erhält, besteht darin, dass sie nicht von einem teilweise vertrauenswürdigen Code aufgerufen werden kann. Die Datei System.DirectoryServices.dll beispielsweise ist der Teil der .NET Framework-Klassenbibliotheken, in dem der Zugriff auf das Active Directory festgelegt ist. Als Teil des .NET Framework wird System.DirectoryServices.dll signiert. Daher kann Ihr Code, der unter Verwendung der Standardcodegruppe über einen URL gestartet wird, nicht darauf zugreifen. Anders ausgedrückt: Sie können Ihren Assemblys zwar die Berechtigung für den zeitlich uneingeschränkten Zugriff auf Directory Services zuweisen, aber solange sie nicht über den FullTrust-Berechtigungssatz verfügen, können sie nicht auf Active Directory zugreifen.
System.DirectoryServices.dll ist nur ein Beispiel für derartige Assemblys. Viele Assemblys in .NET sind vor der Verwendung durch teilweise vertrauenswürdigen Code geschützt. Wenn Sie Ihre eigene DLL-Assembly erstellen müssten und sie signieren, könnte sie nicht von Ihren eigenen EXE-Assemblys verwendet werden, selbst wenn diese aus derselben Site heruntergeladen würden und mit demselben Schlüssel signiert wären.
Dennoch können viele Assemblys aus dem .NET Framework von Ihrem teilweise vertrauenswürdigen Code verwendet werden, und Sie können aus einer teilweise vertrauenswürdigen Assembly Zugang zu Ihren benutzerdefinierten Assemblys gewähren. Grund hierfür ist das AllowPartiallyTrustedCaller-Attribut, das Sie wie folgt in C# auf Ihre Assembly anwenden können:
[assembly: AllowPartiallyTrustedCallers]
Allerdings sollten Sie dabei sehr sorgfältig vorgehen. Schließlich müssen Sie ja sicherstellen, dass Ihre Assembly angesichts teilweise vertrauenswürdiger Aufrufer stabil genug ist. Microsoft hat dieses Attribut mit Bedacht eingesetzt. Schauen Sie sich dazu einmal Tabelle 1 an, in der die wichtigsten .NET-Assemblys aufgeführt werden, die teilweise vertrauenswürdige Aufrufer gemäß .NET SP1 zulassen.
Tabelle 1. Wichtige .NET-Assemblys und ihre APTC-Einstellung
| Assembly | Teilweise vertrauenswürdige Aufrufer zugelassen |
| Accessibility.dll | Ja |
| CustomMarshalers.dll | Nein |
| Microsoft.JScript.dll | Nein |
| Microsoft.VisualBasic.Compatibility.Data.dll | Nein |
| Microsoft.VisualBasic.Compatibility.dll | Nein |
| Microsoft.VisualBasic.dll | Ja |
| Microsoft.VisualBasic.Vsa.dll | Nein |
| Microsoft.VisualC.Dll | Nein |
| Microsoft.Vsa.dll | Nein |
| mscorlib.dll | Ja |
| System.Configuration.Install.dll | Nein |
| System.Data.dll | Ja |
| System.Design.dll | Nein |
| System.DirectoryServices.dll | Nein |
| System.dll | Ja |
| System.Drawing.Design.dll | Nein |
| System.Drawing.dll | Ja |
| System.EnterpriseServices.dll | Nein |
| System.Management.dll | Nein |
| System.Messaging.dll | Nein |
| System.Runtime.Remoting.dll | Nein |
| System.Runtime.Serialization.Formatters.Soap.dll | Nein |
| System.Security.dll | Nein |
| System.ServiceProcess.dll | Nein |
| System.Web.dll | Nein |
| System.Web.RegularExpressions.dll | Nein |
| System.Web.Services.dll | Ja |
| System.Windows.Forms.dll | Ja |
| System.XML.dll | Ja |
Wenn Sie auf Assemblys zugreifen müssen, die nicht mit dem APTC-Attribut gekennzeichnet sind, müssen Sie eine Codegruppe erstellen, die Ihrer Assembly den FullTrust-Berechtigungssatz gewährt. Keiner der anderen Berechtigungssätze mit Standardnamen, eingeschlossen der Everything-Berechtigungssatz (der jede von Microsoft definierte Berechtigung enthält), kann Berechtigungen für den Zugriff auf Assemblys ohne APTC-Attribut gewähren.
Erteilen von Berechtigungen
Es versteht sich, dass Sie, je vertrauter Sie mit den Richtlinienverwaltungstools für .NET-Berechtigungen werden, nicht zu jedem einzelnen Clientcomputer gehen möchten, um die erforderlichen Codegruppen und Berechtigungssätze zu erstellen, die diese zum Ausführen Ihres Codes benötigen - insbesondere, wenn dieser Code über das Internet bereitgestellt werden soll. Zum Glück verfügt .NET über Klassen zur Erstellung von Codegruppen und Berechtigungssätzen. Mit dem folgenden Code beispielsweise wird eine benutzerdefinierte Codegruppe erstellt, die allen Assemblys, die mit einem bekannten Strong Name signiert sind, Internetberechtigungen gewährt:
using System.Security;
using System.Security.Permissions;
using System.Security.Policy;
// Generated with 'secutil -c -s wahoo.exe'
byte[] publicKey = { 0, 36, ... };
// Find the machine policy level
PolicyLevel machinePolicyLevel = null;
System.Collections.IEnumerator ph = SecurityManager.PolicyHierarchy();
while( ph.MoveNext() ) {
PolicyLevel pl = (PolicyLevel)ph.Current;
if( pl.Label == "Machine" ) {
machinePolicyLevel = pl;
break;
}
}
if( machinePolicyLevel == null ) return;
// Create a new code group giving Wahoo! Internet permissions
PermissionSet permSet1 = new NamedPermissionSet("Internet");
StrongNamePublicKeyBlob key = new StrongNamePublicKeyBlob(publicKey);
IMembershipCondition membership1 =
new StrongNameMembershipCondition(key, null, null);
// Create the code group
PolicyStatement policy1 = new PolicyStatement(permSet1);
CodeGroup codeGroup1 = new UnionCodeGroup(membership1, policy1);
codeGroup1.Description = "Internet permissions for Sells Brothers Wahoo!";
codeGroup1.Name = "Sells Brothers Wahoo!";
// Add the code group
machinePolicyLevel.RootCodeGroup.AddChild(codeGroup1);
// Create a new code group giving all of sellsbrothers.com Execute permission
PermissionSet permSet2 = new NamedPermissionSet("Execution");
IMembershipCondition membership2 =
new SiteMembershipCondition("<A href="http://www.sellsbrothers.com">www.sellsbrothers.com</A>");
// Create the code group
PolicyStatement policy2 = new PolicyStatement(permSet2);
CodeGroup codeGroup2 = new UnionCodeGroup(membership2, policy2);
codeGroup2.Description = "Minimal execute permissions for sellsbrothers.com";
codeGroup2.Name = "sellsbrothers.com minimal execute";
// Add the code group
machinePolicyLevel.RootCodeGroup.AddChild(codeGroup2);
// Save changes
SecurityManager.SavePolicy();
Dieser Code fügt zwei Codegruppen hinzu - eine zur Gewährung von Internetberechtigungen für alle Assemblys, die mit einem bekannten Strong Name signiert sind, eine zweite zur Umgehung von Problemen in .NET SP1, wo eine gesamte Site mindestens über die Execute-Berechtigung verfügen muss, damit jede weitere Berechtigung, der ein Strong Name zugewiesen wurde, wirksam wird. Wir beginnen mit der Verwendung von SecurityManager, um den Anfang der Laufzeitrichtlinenhierarchie des Computers zu finden, wo wir neue Codegruppen hinzufügen werden. Danach nehmen wir den Internet-Berechtigungssatz NamedPermissionSet und verknüpfen ihn mit einer StrongNameMembershipCondition, um die neue Codegruppe zu erstellen. Dieser geben wir einen für die Verwaltungstools sinnvollen Namen und fügen sie dann zusammen mit allen existierenden Codegruppen der Stammcodegruppe hinzu. Wenn wir unseren Assemblys volle Vertrauenswürdigkeit hätten gewähren wollen, hätten wir die Zeichenfolge FullTrust an Stelle von Internet beim Erstellen dieses NamedPermissionSet-Objekts verwendet. Nachdem wir nun den ersten Berechtigungssatz erstellt haben, wiederholen wir den Vorgang mit Execution NamedPermissionSet und SiteMembershipCondition, benennen ihn und fügen ihn ebenfalls hinzu.
Um die Änderungen auf die Laufzeitsicherheitsrichtlinie des Computers zu übertragen, lassen wir sie von SecurityManager speichern. Fertig! Sie benötigen nur diesen Code, um Ihren Assemblys Berechtigungen aus bestehenden Berechtigungssätzen zu erteilen. Wenn Sie benutzerdefinierte Berechtigungssätze erstellen möchten, ist der Code ähnlich. In diesem Fall erstellen Sie jedoch Instanzen von leeren NamedPermissionSet-Objekten und fügen Berechtigungsobjekte ein, die aus CodeAccessPermission stammen, z.B. FileIOPermission und DirectoryServicesPermission. Neue Berechtigungssätze werden dann wie folgt über die AddNamedPermissionSet-Methode der Computerrichtlinie hinzugefügt:
// Create a named, empty permission set
NamedPermissionSet permSet =
new NamedPermissionSet("My Permission Set", PermissionState.None);
// Add a permission
IPermission perm = new DirectoryServicesPermission();
permSet.AddPermission(perm);
machinePolicyLevel.AddNamedPermissionSet(permSet);
Beachten Sie die Verwendung von PermissionState.None bei der Weiterleitung an den NamedPermissionSet-Konstruktor. Ohne dies erhalten Sie einen Berechtigungssatz wie FullTrust mit allen Berechtigungen. Stattdessen möchten Sie einen leeren Berechtigungssatz haben, der lediglich die Berechtigungen aufweist, die Sie explizit hinzugefügt haben.
Bereitstellen von Berechtigungen
Jetzt haben wir verwalteten Code, der mit FullTrust auf dem Computer ausgeführt werden soll. Andernfalls ist er nicht zum Ändern der Berechtigungsrichtlinie berechtigt (er muss auch als ein Win32-Administrator ausgeführt werden). Es reicht nicht aus, eine EXE-Datei auf einem Webserver zu platzieren und den Benutzer zu bitten, auf einen Link zu klicken, da sich der Code dann in einer teilweise vertrauenswürdigen Umgebung befindet und wir wieder da sind, wo wir angefangen haben. Am einfachsten lässt sich verwalteter Code durch Verwendung einer MSI-Datei zur Ausführung auf dem Clientcomputer mit FullTrust in Pakete aufteilen. .MSI-Dateien werden von einem Laufzeitmodul ausgeführt, das den Code auf den Computer herunterlädt, bevor er gestartet wird, und uns so die Berechtigungen erteilt, die wir für die Erteilung anderer Berechtigungen benötigen. Außerdem besitzen MSI-Dateien in Intranetumgebungen eine integrierte Unterstützung von Installationstools wie SMS und Active Directory. In einer weniger anspruchsvollen Installationsumgebung wie z.B. kleinen Unternehmen oder dem Internet können Sie einen Link angeben, der die MSI-Datei nach einer Eingabeaufforderung auf dem Computer des Benutzers ausführt. Selbst wenn jeder Ihrer Benutzer die MSI-Datei selbst ausführen muss, handelt es sich hierbei dennoch um nur eine Installation zur Aktivierung der Berechtigungen, die für jeden Smart Client benötigt werden, der z.B. von der internen Website der IT-Gruppe aus bereitgestellt wird.
Es gibt viele Tools zum Erstellen von MSI-Dateien. Das am schnellsten verfügbare ist mit erweiterten Versionen von Visual Studio® .NET ausgestattet. Der Trick ist, dass ein Setupprojekt während der Installation Ihren Code ausführen soll. Wenn Sie z.B. eine Visual Studio .NET-Projektmappe mit einem Setup-Projekt und einem Klassenbibliotheksprojekt haben, müssen Sie dazu nur zwei Aufgaben ausführen.
Als Erstes muss Ihrem Klassenbibliotheksprojekt eine Klasse hinzugefügt werden, die aus System.Configuration.Install.Installer stammt und mit dem RunInstaller(true)-Attribut gekennzeichnet ist. Eine Instanz einer solchen Klasse wird während des Setups vom MSI-Modul erstellt. Dort platzieren Sie also Ihren benutzerdefinierten Code. Am einfachsten rufen Sie eine solche Klasse auf, indem Sie im Projektmappen-Explorer mit der rechten Maustaste auf Ihr Klassenbibliotheksprojekt klicken und im Kontextmenü Add New Item | Code | Installer Class (Neues Element hinzufügen | Code | Installerklasse) wählen. Daraufhin wird wie folgt ein Speicherort für Ihren Berechtigungserteilungscode im Konstruktor erstellt:
[RunInstaller(true)]
public class Installer1 : System.Configuration.Install.Installer {
public Installer1() {
...
// TODO: Add your permission award code here
}
}
Ihre zweite Aufgabe besteht darin, diese Assembly der Liste mit benutzerdefinierten Aktionen hinzuzufügen, die Sie während der Installation einrichten. Klicken Sie dazu mit der rechten Maustaste im Projektmappen-Explorer auf Ihr Setup-Projekt, und wählen Sie im Kontextmenü View, Custom Actions (Ansicht, Benutzerdefinierte Aktionen). Daraufhin wird Ihnen eine Liste mit den benutzerdefinierten Aktionen bei jeder Installationsphase angezeigt (siehe Abbildung 3).
Abbildung 3. Einrichten von benutzerdefinierten Aktionen
Um der Installationsphase eine benutzerdefinierte Aktion hinzuzufügen, klicken Sie mit der rechten Maustaste auf die Install-Liste (Installieren) der benutzerdefinierten Aktionen und wählen Add Custom Action (Benutzerdefinierte Aktion hinzufügen). Daraufhin wird Ihnen eine Liste mit den Ordnern angezeigt, in die Sie Ihren benutzerdefinierten Aktionscode speichern können (siehe Abbildung 4).
Abbildung 4. Auswählen eines Ordners für eine benutzerdefinierte Aktion
Doppelklicken Sie auf den Application Folder (Anwendungsordner), und klicken Sie anschließend auf Add Output (Ausgabe hinzufügen), um die Ausgabe aus einem der anderen Projekte in der Projektmappe auszuwählen. Stellen Sie sicher, dass ganz oben das Klassenbibliotheksprojekt mit Ihrer Installerklasse ausgewählt ist, und klicken Sie auf Primary Output (Primäre Ausgabe) (siehe Abbildung 5).
Abbildung 5. Festlegen der primären Ausgabe Ihres Klassenbibliotheksprojekts als benutzerdefinierte Aktion
Diese Einstellungen legen fest, dass Installerklassen während der Installation Ihres MSI-Setups in Ihrer Klassenbibliotheksassembly erstellt werden. Wenn Sie jetzt die MSI-Datei erstellen und ausführen, die vom Setup-Projekt erstellt wurde, wird Ihr Code mit FullTrust-Berechtigung ausgeführt und kann Ihren Assemblys Berechtigungen erteilen.
Schlussfolgerung
Die beschränkten Berechtigungen der Standardberechtigungssätze verstärken unser Vertrauen in die Sicherheit von Code, der nicht auf unserem Computer ausgeführt wird. Aus diesem Grund sind Smart Clients auch nicht die besten Virenverbreiter der Welt. Bei einigen Szenarios ist es jedoch wünschenswert, dass bekannte Assemblys oder Sites über weitere Berechtigungen verfügen, um Benutzern zusätzliche Dienste anbieten zu können. Dies lässt sich mit neuen Codegruppen realisieren, die die Mitgliedsschaftsbedingungen von Assemblys mit Standard- oder benutzerdefinierten Berechtigungssätzen abgleichen, die weitere Berechtigungen erteilen. .NET verfügt über ein Objektmodell zum Erstellen von Codegruppen und Berechtigungssätzen, die in MSI-Dateipaketen an Ihre Benutzer weitergegeben werden können. Dank dieser Technik können Sie Smart Clients im HTTP-Stil bereitstellen und erhalten gleichzeitig die Berechtigungen, die Ihre Anwendungen benötigen, um die Anforderungen Ihrer Benutzer zu erfüllen.
Danksagung
Wie immer stand mir Keith Brown als Sicherheitsexperte zur Seite. Mit dem von ihm geschriebenen hervorragenden FindAPTC-Dienstprogramm lässt sich erkennen, welche .NET-Assemblys über APTC-Attribute verfügen und welche nicht. Er fand heraus, dass der Hostingcode von Smart Clients den Execution-Mindestberechtigungssatz für eine gesamte Site erfordert, und erklärte mir den Unterschied bei der Erstellung von NamedPermissionSet mit und ohne PermissionState.None. Keith, Du bist mein großes Vorbild.
Referenzmaterial