ADMIN

2021

11

2022-11-01T12:00:00

Collaboration

PRAXIS

044

Sicherheit

PowerShell

Signieren von PowerShell-Skripten

Richtig unterschreiben

von Thomas Krampe

Veröffentlicht in Ausgabe 11/2021 - PRAXIS

Um die PowerShell zu nutzen, muss der Admin nicht immer selbst Skripte schreiben, denn fast jede aktuelle Anwendung kommt mittlerweile mit einer Schnittstelle oder einem PowerShell-SDK daher. Und selbst wenn dies nicht der Fall ist, steht dennoch mit hoher Wahrscheinlichkeit ein passendes Skript in der Community zur Verfügung. Doch nur mit der richtigen Signatur ist der Einsatz von externem Code auch sicher.

Administratoren müssen heutzutage Skripte nicht mehr unbedingt selbst schreiben. Durch die rasante Verbreitung der PowerShell in den letzten Jahren existieren viele freie Repositories, wie die PowerShell Gallery oder natürlich GitHub. Dort finden Admins fertige Skripte, Module oder Funktionen, die sich kostenfrei verwenden oder auch für eigene Zwecke anpassen lassen. Allerdings ist der Admin hier mit dem Problem konfrontiert, dass sich nicht genau feststellen lässt, ob das heruntergeladene Skript nicht ein Sicherheitsproblem darstellt.
In erster Linie hilft hier die genaue Kontrolle eines solchen externen Skripts. Mit entsprechenden PowerShell-Kenntnissen ausgestattet, lassen sich kleinere Skripte in der Regel schnell auf ihre Funktion und eventuell schädlichen Code überprüfen. Bei längeren Skripten oder externen Modulen beziehungsweise Funktionen gestaltet sich das schon etwas schwieriger und vor allem viel zeitaufwändiger. Dennoch muss der IT-Verantwortliche externe Skripte sowohl aus bekannten und erst recht aus unbekannten Quellen überprüfen und eventuell auch zuerst in einer Sandbox, also einer geschützten Umgebung, ausgiebig testen. Etwas mehr Kontrolle darüber lässt sich in der PowerShell mit der Ausführungsrichtlinie (Execution Policy) implementieren.
Execution Policy steuert Skript-Rechte
Der wesentliche Zweck der Execution Policy besteht darin, Benutzer vor dem versehentlichen Ausführen nicht vertrauenswürdiger Skripte zu schützen. Auf einem frisch installierten Windows-Rechner gilt immer "Restricted", sodass kein Anwender PowerShell-Skripte ausführen kann – auch kein Administrator.
Administratoren müssen heutzutage Skripte nicht mehr unbedingt selbst schreiben. Durch die rasante Verbreitung der PowerShell in den letzten Jahren existieren viele freie Repositories, wie die PowerShell Gallery oder natürlich GitHub. Dort finden Admins fertige Skripte, Module oder Funktionen, die sich kostenfrei verwenden oder auch für eigene Zwecke anpassen lassen. Allerdings ist der Admin hier mit dem Problem konfrontiert, dass sich nicht genau feststellen lässt, ob das heruntergeladene Skript nicht ein Sicherheitsproblem darstellt.
In erster Linie hilft hier die genaue Kontrolle eines solchen externen Skripts. Mit entsprechenden PowerShell-Kenntnissen ausgestattet, lassen sich kleinere Skripte in der Regel schnell auf ihre Funktion und eventuell schädlichen Code überprüfen. Bei längeren Skripten oder externen Modulen beziehungsweise Funktionen gestaltet sich das schon etwas schwieriger und vor allem viel zeitaufwändiger. Dennoch muss der IT-Verantwortliche externe Skripte sowohl aus bekannten und erst recht aus unbekannten Quellen überprüfen und eventuell auch zuerst in einer Sandbox, also einer geschützten Umgebung, ausgiebig testen. Etwas mehr Kontrolle darüber lässt sich in der PowerShell mit der Ausführungsrichtlinie (Execution Policy) implementieren.
Execution Policy steuert Skript-Rechte
Der wesentliche Zweck der Execution Policy besteht darin, Benutzer vor dem versehentlichen Ausführen nicht vertrauenswürdiger Skripte zu schützen. Auf einem frisch installierten Windows-Rechner gilt immer "Restricted", sodass kein Anwender PowerShell-Skripte ausführen kann – auch kein Administrator.
In der Praxis schalten daher viele Admins diesen Schutz eher ab, als ihn richtig zu konfigurieren. Dabei sollte der Fehler auf keinen Fall mit dem Cmdlet "Set-ExecutionPolicy Unrestricted-" oder – noch schlimmer – mit "Set-ExecutionPolicy ByPass" beseitigt werden, denn so schalten Sie den Schutz durch die Execution Policy mehr oder weniger komplett ab. Für die Konfiguration der Execution Policy stehen folgende Werte zur Verfügung:
- AllSigned: Führt nur signierte Skripte aus, das gilt auch für lokal erstellte.
- RemoteSigned: Extern heruntergeladene Skripte müssen signiert sein.
- Unrestricted: Führt alle Skripte aus, bei nicht-signierten externen Skripten ist jede Ausführung zu bestätigen.
- ByPass: Keinerlei Einschränkungen, Warnungen oder Prompts.
- Undefined: Entfernt eine zugewiesene Richtlinie und setzt wieder den Wert "Restricted".
Geltungsbereich der Execution Policy
Interessant ist hier der Geltungsbereich der Execution Policy. Geben Sie zum Beispiel Set-ExecutionPolicy ByPass ein, scheitert dies, wenn die PowerShell-Sitzung nicht mit administrativen Rechten ("Ausführen als…") läuft. Der Grund dafür liegt im Scope für die Execution Policy. Geben Sie diesen nicht explizit an, dann geht das Set-ExecutionPolicy-Cmdlet von "LocalMachine" aus. Das würde aber die Einstellung für alle Benutzer auf diesem System ändern, weswegen hierfür zwingend administrative Rechte notwendig sind.
Definieren Sie allerdings die Execution Policy nur für den aktuellen Benutzer, dann überschreibt sie jene für die lokale Maschine. Daher kann jeder User eine restriktive, rechnerweite Einstellung aufheben, zum Beispiel mit
Set-ExecutionPolicy ByPass -Scope CurrentUser
Das bedeutet allerdings nicht, dass ein PowerShell-Skript alles machen darf. Viele Skripte beziehungsweise Cmdlets benötigen trotzdem noch erweiterte Rechte. Aber der Benutzer darf zumindest auf diese Weise ein Skript und alle Kommandos ausführen, die keine erweiterten Rechte benötigen – was auch nicht gewünscht sein sollte. Noch enger gefasst ist der Gültigkeitsbereich "Process", der nur die aktuelle Sitzung betrifft. Die Einstellung dafür wird nicht wie sonst üblich in der Registry, sondern in einer Umgebungsvariablen ("$env:PSExecutionPolicyPreference") gespeichert und ist nur für die aktuelle Sitzung gültig.
Um zu vermeiden, dass Benutzer diese Einstellungen ändern, lässt sich die Execution Policy auch über eine Gruppenrichtlinie zentral steuern. Die dafür zuständige Einstellung findet sich für die Computer- und Benutzerkonfiguration unter "Richtlinien / Administrative Vorlagen / Windows-Komponenten / Win-dows PowerShell" und heißt "Skriptausführung aktivieren". Die damit konfi- gurierte Execution Policy übersteuert die in der Session festgelegten Werte und verhindert, dass ein Administrator sie per Set-ExecutionPolicy-Cmdlet ändern kann.
Leider ist das kein wirkungsvoller Schutz gegen böswillige Benutzer. Die vorgegebene Execution Policy lässt sich relativ einfach mit Hilfe der PowerShell ISE umgehen. Hierfür wird lediglich das Skript in den ISE-Editor kopiert und dort ausgeführt. Um dem entgegenzuwirken, können Sie das Verwenden der PowerShell ISE beispielsweise mit AppLocker verhindern. Es existieren jedoch noch weitere Möglichkeiten, die Execution Policy zu umgehen. Abhilfe schafft in diesem Fall nur der Einsatz von Tools wie Microsoft AppLocker und des Constrained-Language-Modus.
Signieren von PowerShell-Skripten
Sinnvolle Einstellungen für die Execution Policy sind ausschließlich "AllSigned" und "RemoteSigned". Um diese Voraussetzung bei unseren Skripten zu erfüllen, sind diese digital zu signieren. Dazu kommen ein digitaler Schlüssel sowie ein dazugehöriges X.509-Zertifikat zum Einsatz, das von einer vertrauenswürdigen Zertifizierungsstelle (CA) ausgestellt sein muss.
Übliche Code-Signing-Zertifikate von öffentlichen Zertifizierungsstellen umfassen die Validierung der Identität eines Unternehmens oder einer Organisation (bekannt als Organisationsvalidierung oder OV) oder einer einzelnen Person (individuelle Validierung oder IV) und bieten Schutz für Anwendungen sowie Skripte, die von Einzelpersonen oder Organisationen stammen. Code-Signing-Zertifikate mit erweiterter Validierung (EV) sind teurer und nur für registrierte Organisationen verfügbar. Eine solche Validierung ist unter anderem für Windows-Kernel-Mode-Treiber zwingend erforderlich. Für PowerShell-Skripte, die in einem öffentlichen Repository bereitstehen, reicht aber eine OV/IV-Validierung aus.
Die meisten Entwickler veröffentlichen ihre Skripte und Module in der Regel unsigniert in den Repositories. Es gibt aber auch Entwickler, die zwei Versionen ihrer Skripte (mit und ohne Signatur) bereitstellen. Allerdings macht dies die Entscheidung für den Administrator nicht einfacher, denn nur weil ein Skript bereits mit einem öffentlichen und gültigen Schlüssel sowie Zertifikat signiert herunterladen werden kann, bedeutet dies nicht zwangsläufig auch, dass dieses Skript keinen schadhaften Code enthält.
Der beste Weg an dieser Stelle ist das Testen solcher Skripte in einer Sandbox. Ist dies erfolgreich und das Skript somit für sicher befunden, können Sie es zur Ausführung in der eigenen Infrastruktur mit dem eigenen Schlüssel und Zertifikat ausstatten und somit problemlos in einer "AllSigned"-Session starten. Bei fremden, vom Entwickler bereits signierten Skripten können Sie die Signatur auch in der PowerShell auf Gültigkeit überprüfen:
Get-AuthenticodeSignature -FilePath <C:\foo.ps1> | Select-Object -Property *
Selbstsignierte Zertifikate
Verwenden Sie PowerShell-Skripte zum Beispiel ausschließlich innerhalb der eigenen Infrastruktur, benötigen Sie nicht zwingend ein teures, öffentliches Code-Signing-Zertifikat. In kleineren Umgebungen erreichen Sie das auch mit einem selbstsignierten Zertifikat, das Sie sich relativ einfach erstellen:
New-SelfSignedCertificate -Subject "CodeSign-Authenticode" -CertStoreLocation Cert:\LocalMachine\My -Type CodeSigningCert
Der Nachteil eines solchen Zertifikats liegt auf der Hand – niemand vertraut diesem. Das ist an dieser Stelle noch kein wirkliches Problem, denn natürlich können Sie das Zertifikat auch auf den verschiedenen Zielsystemen im "Trusted Root Certification Authorities Store" für die interne Verwendung bereitstellen.
Die Verwaltung von Zertifikatsvorlagen in einer Microsoft-CA erfolgt über das Werkzeug "certsrv.msc".
Eigene Zertifikate für Skripte erstellen
Es würde hier den Rahmen sprengen, auf den Aufbau einer Active-Directory-basierten Zertifizierungsstelle einzugehen. Sobald allerdings eine CA in der Domäne existiert, können Sie diese für das Code Signing verwenden. Der erste Schritt ist die Veröffentlichung der entsprechenden Zertifikatsvorlage, damit berechtigte Benutzer Code-Signing-Zertifikate anfordern können. Die Vorlage ist bereits vorhanden und lässt sich über das MMC- Tool "certsrv.msc" auf dem Server, der die MSCS-Rolle hält, veröffentlichen.
Hierfür wählen Sie die Zertifikatsvorlagen aus und klicken im Kontextmenü auf den Punkt "Verwalten". Nun benötigen Sie die entsprechende Codesignatur-Vorlage und passen sie über den Punkt "Eigenschaften" für Ihre Zwecke an. Wichtig an dieser Stelle ist lediglich, eine Active-Directory-Gruppe zuzuweisen, die später Code-Signing-Zertifikate anfordern darf. Die zu konfigurierenden Berechtigungen für diese Gruppe sind "Lesen" und "Registrieren".
Nach dem Bestätigen dieses Dialogs kehren Sie wieder zu "certsrv.msc" zurück und führen aus dem Kontextmenü von Zertifikatsvorlagen den Befehl "Neu / Auszustellende Zertifikatsvorlage" aus. Im nachfolgenden Dialog markieren Sie die Codesignatur und bestätigen mit "OK". Somit ist die Vorlage veröffentlicht und dient jetzt von der vorher definierten AD-Gruppe zum Registrieren von Zertifikaten. Nun können Sie über die MMC das Snap-in "Zertifikate" hinzufügen. Mit Admin-Rechten ausgestattet, müssen Sie im folgenden Dialog entscheiden, ob Sie hier den Computer- oder den Benutzerstore nutzen.
Ohne erhöhte Rechte öffnet sich automatisch der Kontext "Aktueller Benutzer" ("CurrentUser"). In diesem Fall wählen Sie mit einem Rechtsklick auf "Meine Zertifikate" das Kontextmenü und führen dann "Alle Aufgaben / Neues Zertifikat anfordern" aus. Im folgenden Assistenten müssen Sie die Zertifikatregistrierungsrichtlinie auswählen und die Vorlage "Codesignatur" aktivieren. Unter "Details" klicken Sie dann auf "Eigenschaften" und haken im angezeigten Dialog unter dem Reiter "Privater Schlüssel" die Option "Privaten Schlüssel exportierbar machen" an. Nach einem Klick auf "Fertig" sollte sich nun im lokalen Store ein Code-Signing-Zertifikat Ihrer CA befinden.
Verwenden der Zertifikate zum Signieren von Skripten
An dieser Stelle haben wir jetzt entweder ein öffentliches, ein selbstsigniertes oder ein Zertifikat unserer Domänen-Zertifizierungsstelle in unserem persönlichen Store, das wir zum Signieren von PowerShell-Skripten verwenden können. Das Zertifikat bringen Sie mit folgendem Befehl auf den Bildschirm:
Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert"
Prinzipiell können Sie mit diesem Zertifikat auch EXE- oder DLL-Dateien signieren. Doch wir bleiben unserem Ziel treu und signieren ein Skript:
Set-AuthenticodeSignature <c:\foo.ps1> @(Get-ChildItem cert:\CurrentUser\My -codesign)[0]
Das Cmdlet fügt einfach die entsprechende Signatur im Base64-Format als einen eigenen Block am Ende des Skripts (in unserem Beispiel "c:\foo.ps1") ein.
Steht die Execution Policy auf "AllSigned", startet das so signierte Skript problemlos. Eventuell kommt bei einem selbstsignierten Zertifikat noch eine Abfrage, ob Sie dem Herausgeber vertrauen. Wählen Sie einmalig "Immer vertrauen", taucht die Abfrage nicht erneut auf. Selbstverständlich funktioniert ein signiertes Skript, das nach dem Signieren verändert wird, nicht mehr und Sie erhalten eine Fehlermeldung.
Das Gleiche passiert, wenn das Zertifikat, das Sie zum Signieren benutzt haben, abgelaufen und damit nicht mehr gültig ist. Sobald das der Fall ist, führt die PowerShell damit signierte Skripte nicht mehr aus. Dies ist prinzipiell eine gute Sicherheitsmaßnahme, allerdings bedeutet das auch einen sehr großen administrativen Aufwand, wenn Sie zum Beispiel Login-Skripte oder Automatisierungsskripte alle zwölf Monate neu signieren müssen. Für die meisten Administratoren wäre dies ein absoluter Show-Stopper für das Signieren von PowerShell-Skripten.
Hier können wir uns allerdings schnell helfen. Signierte PowerShell-Skripte haben standardmäßig nämlich keinen Timestamp. Das prüfen Sie ganz einfach mit einem Rechtsklick auf eine Skriptdatei unter "Eigenschaften / Digitale Signatur" nach. Der Zeitstempel würde dafür sorgen, dass die PowerShell akzeptiert, dass unser Zertifikat zum Zeitpunkt des Signierens gültig war. Damit würden alle unsere Skripte weiter funktionieren, obwohl das Zertifikat abgelaufen ist, da es zum Zeitpunkt des Signierens gültig war.
Für einen solchen Timestamp benötigen Sie einen öffentlich erreichbaren Time-Stamp-Server. Normalerweise erhalten Sie eine entsprechende URL vom Herausgeber des Zertifikats. Da wir das im Fall der eigenen CA selbst sind, müssen Sie hier einen öffentlich verfügbaren Server verwenden (oder einen eigenen betreiben). Entsprechende, kostenlose Dienste finden Sie schnell über Ihre Suchmaschine. Das Cmdlet zum Signieren bekommt in diesem Fall lediglich zwei zusätzliche Parameter:
Set-AuthenticodeSignature <c:\foo.ps1> @(Get-ChildItem cert:\CurrentUser\My -codesign)[0] -TimeStampServer <http:// ca.signfiles.com/tsa/get.aspx> -HashAlgorithm SHA256
Den Unterschied sehen Sie wieder mit einem Rechtsklick auf eine Skriptdatei unter "Eigenschaften / Digitale Signatur".
Fazit
Bei der Vielzahl der frei verfügbaren PowerShell-Skripte fällt es schwer, den Überblick zu behalten, welches Skript in der eigenen Umgebung funktioniert und welches nicht. Dabei geht es gar nicht unbedingt darum, dass solche Skripte Schadcode enthalten können. Oftmals macht eben ein solches Skript mehr als ursprünglich erwartet. Eigentlich sagt uns hier der gesunde Menschenverstand, derartige Skripte vor der Ausführung zu überprüfen und zu testen. Ergänzen sollten Sie dieses Vorgehen um eine technische Kontrolle via Execution Policy, damit Skripte aus unbekannten Quellen nicht ausgeführt werden können (auch nicht aus Versehen). Die Execution Policy sowie das Signieren von Skripten ist einfach zu implementieren und erhöht die Sicherheit von PowerShell-Skripten enorm.
 (jp)