PS Passwort / Kennwort

Diese Seite beschreibt die verschiedenen Möglichkeiten mit Kennworten in Powershell zu arbeiten. Spätestens wenn Sie mit PowerShell den lokalen PC verlassen und z.B.: auf ein Active Directory oder Exchange PowerShell oder andere Webservices zugreifen wollen, müssen Sie sich um die Authentifizierung Gedanken machen. Ein Skript läuft natürlich immer mit den Rechten des aufrufenden Prozesses aber die reichen vielleicht nicht immer. Selbst wenn das PowerShell-Script mit einem Domänen-Account läuft, kann es sein dass Sie für Zugriffe auf andere Dienste Credentials angeben müssen. Nicht immer ist ein Trust vorhanden, möglich oder erwünscht. Oder die Gegenstelle unterstützt einfach keine Anmeldung per Kerberos oder NTLM, sondern erwartet z.B. "Basic Authentication" oder die Eingabe von Daten in Anmeldeformularen.

Achtung:
Wenn Sie ein PowerShell aus unbekannter Quelle starten und es nach Anmeldedaten fragt, dann sollten Sie immer erst einmal nachschauen, ob es die Daten nur im Skript verwendet oder vielleicht auch über einen anderen Kanal nach draußen sendet. Das ist durchaus schon passiert
Siehe auch https://www.bleepingcomputer.com/news/security/psa-beware-of-windows-powershell-credential-request-prompts/

Kennworte eingeben

Wenn ein Skript interaktiv abläuft, dann kann das Kennwort per Eingabe angefordert werden. Dazu gibt es gleich drei mir  bekannte Wege:

  • Get-Credential
    Wenn später als Parameter bei Commandlets gleich mit Get-Credentials gearbeitet werden kann, sollten diese direkt so eingelesen und weiter verarbeitet werden.
$cred = Get-Credentials
  • Read-Host mit asSecureString
    Einige Funktionen erwarten aber die Angabe eines Kennworts als "Secure Password". Dann kann der String z.B.: mit Read-Host eingelesen werden.

$password = read-host "Enter Password" -asSecureString

  • Read-Host als Klartext und Angabe Konvertierung
    Alternativ kann man mit den beiden Commandlets "ConvertTo-Securestring" und "ConvertFrom-Securestring" die Werte umwandeln. Wird also bei einem Commandlet ein Parameter "Passwort" erwartet, dann hilft der geklammerte Ausdruck:

$password = (convertto-securestring -string "kennwort" -asplaintext -force)

Allerdings ist es generell eine schlechte Idee, Kennworte in einem Skript fest zu hinterlegen. Wenn Sie nicht gleich die "integrierte Authentifizierung" nutzen möchten, d.h. der gerade angemeldete Benutzer, der das Skript ausführt, dann können Sie das Kennwort auch in einer Datei (reversibel) verschlüsselt speichern und importieren. 

Kennwort speichern

Damit stellt sich natürlich wo das Kennwort hinterlegt wird, damit der Zugriff möglich ist. Verschiedene Verfahren sind für den Zugriff auf andere Dienste denkbar:

Verfahren Bewertung

Credentials des ausführenden Kontos

Das macht es am einfachsten, weil z.B: per Kerberos oder NTLM das Skript direkt eine Authentifizierung durchführen kann und das eigentliche Kennwort gar nicht erforderlich ist.

  • Kein Kennwort im Skript oder Konfiguration zu speichern
  • Kennwort kann im Taskplaner hinterlegt sein
  • Angesprochene Dienste müssen "Integrierte Anmeldung" unterstützen
    Kein Problem für Active Directory und Exchange Remote PowerShell im LAN aber knifflig bei Office 365 oder über diverse Proxy-Server
  • Keine Unterstützung für "BasicAuth"-Dienste

Interaktive Abfrage

Das ist auch "sicher", wenn man dem Anwender oder Admin als sicher ansehen kann. Aber erlaubt keine Automatisierung.

  • Sehr einfach umzusetzen
  • Universelle verwendbar
  • Keine Automatisierung

Klartext-Credential im Skript
Klartext-Credential in Konfig-Datei
Klartext-Credential als Aufrufparameter

Zugangsdaten im Skript sind einfach aber risikobehaften. Selbst wenn das Skript er NTFS-Rechte gegen fremde Leser gesichert ist, passiert es einfach zu oft, dass solche Skripte .z.B. über GitHub oder andere Versionsverwaltungssysteme dann doch "öffentlich" werden.

  • Sehr einfach umzusetzen
  • Universelle verwendbar
  • Sehr unsicher

Crypted-Credential im Skript
Crypted-Credential in Konfig-Datei
Crypted-Credential als Aufrufparameter

Kennwort verschlüsselt in einer Datei abzulegen, kann helfen, wenn die Verschlüsselung sicher ist. Windows erlaubt dies in Grenzen, z.B. dass Zugangsdaten nur für den gleichen Benutzer wieder decodiert werden können. Aber auch hier gibt es wieder Lücken

  • Relativ einfach umzusetzen
  • Crypt-String muss erst erzeugt und dann abgelegt werden
  • nicht einfach reversibel (aber möglich)
  • Verschlüsselung durch "Key" steuerbar
  • Universell verwendbar

Token/Zertifikat/Smartcard

Ein ganz externer Container, der nicht anders ausgelesen oder kopiert werden kann, ist noch sicherer aber weniger bequem

  • Unterstützung durch den Dienst erforderlich
  • ggfls. PIN-Eingabe/Entsperren erforderlich
  • Smartcardleser oder Karte können physikalische entfernt werden
  • Nur eingeschränkt "virtuell" nutzbar
  • Beschränkung auf den Dienst

Besonders interessant ist hier also die "verschlüsselte" Speicherung. Schauen wir uns aber erst mal die von mir am häufigsten per PowerShell angesprochenen Dienste an.

Generell gilt: je sicherer, desto aufwändiger oder unbequemer. Aber ein Klartextkennwort in einen Skript ist auch keine Lösung. Versuchen Sie immer per integrierter Anmeldung (NTLM/Kerberos) zu nutzen oder zumindest einen verschlüsselten Container.

Credentials erstellen

Die verschiedenen Dienste unterstützen verschiedene Anmeldeverfahren. Gerade PowerShell-Commandlets nutzen gerne einen "-Credential"-Parameter der mit entsprechenden Daten gefüllt sein muss, so z.B. bei New-PSSession. Der einfachste Weg ist natürlich die Eingabe der Anmeldedaten mit einem Formular.

# Abfrage von Benutzer und Kennwort
$cred = get-credential

#Abfrage Kennwort fuer einen vorbelegten Benutzer
$cred = get-credential  "DomainUser"

Der Anwender kann dann das Kennwort eingeben:

Das Ergebnis ist dann ein "sicherer" Credentials-Container mit den Userdaten und dem "SecureString".

PS C:\> $cred | fl
UserName : Domain\User
Password : System.Security.SecureString

Natürlich kann ich den "SecureString" auch wieder zurück verwandeln.

$cred.Password | ConvertFrom-SecureString -AsPlainText

Ohne den Parameter "-AsPlaintext" kommt dann eine String-Version heraus.

# Bitte Umbrüche voher entfernen"
PS C:\> $cred.Password | ConvertFrom-SecureString `
"01000000d08c9ddf0115d1118c7a00c04fc297eb01000000d1cc4f381f4f7a4ba6b822a05720ed3
e0000000002000000000003660000c00000001000000040f15edb2a0f81f7d5b133c57a76b4da00
00000004800000a000000010000000d65b95f61f26da4051a6890ebb20e048100000001b7006495
84e8025661d3051cc5e0d25140000004e44180463eaacb947357a87d3821876a34ff8fe"

Dieser String kann aber natürlich einfach in einer Datei gespeichert und später wieder zurückkonvertiert werden. Sie können das Kennwort auch wieder extrahieren, zumindest wenn Sie  auf dem gleichen Computer sind, auf dem die Credentials eingegeben wurden.

$cred.GetNetworkCredential().Username
$cred.GetNetworkCredential().Domain
$cred.GetNetworkCredential().Password

Werden zwei Variablen mit den gleichen Daten gefüllt, dann sind die verschlüsselten Kennworte nicht identisch!

Der "Key"

Natürlich muss man sich als Administrator nun fragen, wie das dann funktioniert, wenn die gleichen Anmeldedaten unterschiedliche "SecureString"-Werte ergeben und wie sicher das alles ist, wo "intern" daraus doch wieder ein Kennwort werden kann. Ich habe einfach das gleiche Kennwort mehrfach konvertiert und ausgeben lassen.

convertto-securestring "P@ssW0rD!" -asplaintext -force |convertfrom-securestring

Interessanterweise liefern die Ausgaben immer unterschiedliche Werte:

Sie können auch jeden der Werte wieder mit "Convertto-SecureString umwandeln und die Länge passt. PowerShell bedient sich dabei der Windows Data Protection API (DPAPI https://msdn.microsoft.com/en-us/library/ms995355.aspx), d.h. diese sicheren String können nur auf dem gleichen PC mit dem gleichen Benutzerkonto wieder zurückverwandelt werden. Der erforderliche Schlüssel wird aus diesen beiden Informationen (Benutzer und Computer) generiert. Sie können aber auch einen anderen Schlüssel mit angeben. Dann wird die Information "portabel". Sie müssen nun allerdings auf den verwendeten Schlüssel aufpassen.

Wenn Sie die verschlüsselte Information "portabel" gestalten wollen, dann können Sie mit dem Parameter "-key" einen eigenen 128bit-Schlüssel verwenden. Allerdings muss der dann ja auch wieder irgendwie im Code abgelegt werden. Das könnte aber z.B. eine Information sein, die nur auf diesem Computer vorliegt, z.B. die MachineGUID, die MAC-Adresse einer Netzwerkkarte, die GUID des Active Directory-Forest. So können Sie einen zweiten Faktor mit einbauen, von dem im Code nur zu sehen ist, welcher Wert abgerufen wird aber nicht der eigentliche Wert selbst.

Password in Datei

Mit dem Trick und der Sicherheit, dass diese Zahlenfolge nur auf dem gleichen PC mit dem gleichen Benutzer wieder verwendet werden kann, lässt sich sehr einfach auch ein Kennwortsafe auf Dateibasis aufbauen.

# Kennwort interaktiv erfragen und als Datei abspeichern
read-host "Enter Password" -asSecureString `
   | ConvertFrom-SecureString `
   | Out-File "C:\temp\Passwordstore.txt"

Der umgekehrte Weg ist etwas aufwändiger, da das Kennwort so alleine nicht unverschlüsselt vorliegt. Aber es kann ein Credential Container gefüllt werden.

# Kennwort aus Datei auslesen und als SecureString ablegen
$pass = (get-content "C:\temp\Passwordstore.txt" | convertto-securestring)

# Credentials Container mit Kennwort laden. Der Username ist nicht relevant
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist "Username",$pass

# Kennwort im Klartext anzeigen
$cred.GetNetworkCredential().password

Normalerweise muss ich das Kennwort aber nicht mehr als Klartext anzeigen, wenn ich es schon in $cred stehen habe. Diese Variable kann ich mit dem richtigen Benutzernamen an vielen Stellen schon direkt für die Authentifizierung einsetzen.

Zufallskennworte

Manchmal nutze ich PowerShell, um Benutzer anzulegen oder in Dateien entsprechende Anmeldeinformationen zu hinterlegen, die dann in einem anderen System importiert werden. Das brauche ich z.B. für das Management von Konten in der Google-Cloud. Da stellt sich dann auch die Frage, wie ich möglichst "zufällige" Kennworte generiere.

Ich habe mir dazu einen Code gebaut, der aus einer Liste von gültigen Zeichen mit der "Get-Random"-Funktion eine gewisse Menge einfach herausgreift. Damit kann ich natürlich nicht besondere Anforderungen an Komplexität erfüllen. Aber wir haben ja schon gelernt, dass Komplexität weniger wichtig ist als Länge. Als Start-Kennworte reichen mir in den meisten Fällen diese String:

function generate-password {

   $passwordlength = 8   # length of dynamic generates password
   $passwortchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", # characters for password generation

   write-host "Generate Random password"
   -join ($passwortchars.tochararray() | get-random -count $passwordlength | foreach {$_}) 
}

So erhalten ich immer einen String, der 8 Stellen lang ist und zufällige Zeichen enthält. Zumindest in dem Maße, in dem die Get-Random-Funktion zufällig ist.

Zertifikat als Authentifizierung

Wenn man ganz auf Kennworte in der klassischen Form verzichten will, dann könnten Zertifikate eine Option darstellen. Es ist natürlich dazu erforderlich, dass die Gegenstelle auch eine Anmeldung mittels Zertifikat unterstützt. Auch auf ihrer Seite sollten Sie dann natürlich sicherstellen, dass das Zertifikat nicht exportierbar oder sonst wie übertragbar ist. Ein auf dem lokalen PC gespeichertes Zertifikat als PFX-Datei mit einem Kennwort im PowerShell bringt also keinen Mehrwert. Selbst ein im lokalen Store hinterlegtes Zertifikat ist nicht 100% gegen Kopieren geschützt, selbst wenn es als "nicht exportierbar" gekennzeichnet ist. Es gibt schon entsprechende Tools, um auch von diesen Zertifikaten den privaten Schlüssel zu extrahieren und auch ein Snapshot des Computers auf einer virtuellen Plattform oder ein Backup/Restore erlaubt es einem Angreifer das Zertifikat zu duplizieren.

Sicher wäre ein Zertifikat nur in Verbindung mit einer entsprechenden Hardware, z.B. einer Smartcard.

In PowerShell nutzen wir dazu dann wieder die "PSCredential Class", die aber per Default nur die Felder "Benutzername" und "Kennwort" kennt. Wenn Sie aber mal "Get-Credential" eingeben, dann sehen sie in der Dropdown-List durchaus vorhandene Zertifikate und auch ggfls. einen Smartcard-Leser.

Der Trick besteht hier darin den Benutzernamen entsprechend zu füllen, damit Get-Credential das Zertifikat nutzt. Allerdings ist das nicht einfach möglich. Sie müssen dann schon etwas in die Tiefen von C# gehen, um den passenden Namen zu ermitteln. Die Arbeit haben aber schon andere gemacht.

Windows Credential Manager

Wenn Sie ihr Windows anschauen, dann finden Sie dort auch einen "Anmeldeinformationsverwaltung.

"

Wann immer sie in Windows Anmelddaten speichern, z.B.: beim Verbinden eines Laufwerks zu einem Server, bei dem eine Anmeldung per NTLM/Kerberos nicht automatisch erfolgt, können Sie die Anmeldedaten in diesem "Tresor" hinterlegen. Die Daten werden in ihrem Benutzerprofil verschlüsselt gespeichert und wenn ein Admin z.B. ihr Kennwort zurücksetzt, sind diese Daten nicht mehr erreichbar.

Auch hier können Sie per PowerShell eigene Anmeldedaten hinterlegen. Allerdings ist dies nicht einfach per Einzeiler möglich. Es gibt aber für PowerShell gleich mehrere Funktionen und Module. Am bekanntesten ist hier wohl der CredentialManager aus der Powershell Gallery

CredentialManager 2.0
https://www.powershellgallery.com/packages/CredentialManager/2.0

Sie können ihn einfach installieren und importieren:

PS C:\> Install-Module -Name CredentialManager
PS C:\> Import-Module CredentialManager
PS C:\> Get-Command -Module CredentialManager

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Cmdlet          Get-StoredCredential                               2.0        CredentialManager
Cmdlet          Get-StrongPassword                                 2.0        CredentialManager
Cmdlet          New-StoredCredential                               2.0        CredentialManager
Cmdlet          Remove-StoredCredential                            2.0        CredentialManager

PS C:\> Get-StoredCredential

UserName                                                 Password
--------                                                 --------
PersonalAccessToken                  System.Security.SecureString
FrankCarius                          System.Security.SecureString
Personal Access Token                System.Security.SecureString
LenovoSsoSdk                         System.Security.SecureString
GitHub.auth                          System.Security.SecureString
microsoft.login                      System.Security.SecureStrings

Mit Get-StoredCredential bekomme ich eine Liste der gespeicherten Elemente samt dem "Secure Password", welches ich auch einfach wieder zurückkonvertieren kann

(Get-StoredCredential)[22].GetNetworkCredential().Password
SuperGeheim4me

Dahingehend der Hinweis, dass auch ein Virus oder eine Malware, die der Anwender unbedarft aufruft, die gleichen Informationen bekommen. Leider gibt es von Microsoft hier keine Rückfrage, wenn jemand auf einen Eintrag im Anmeldemanager zugreift.

Das kann gut sein, wenn Sie zwecks Automatisierung die Zugangsdaten benötigen aber aus Sicherheitsgründen dann wieder unerwünscht.

Microsoft SecretManagement

Im Jahr 2022 bin ich auf ein Video von John Savvill aufmerksam geworden, in dem er im April 2021 einen weiteren Ansatz erläutert:

New PowerShell Secrets Management Module - Easily use any secret provider
https://www.youtube.com/watch?app=desktop&v=7b0KGVI4VLY

Es gibt von Microsoft zwei Module, mit denen ich Credentials sicher speichern und wieder abrufen kann:

# Das ist das eigentliche Kernmodul
Install-Module Microsoft.PowerShell.SecretManagement

# Diese Module ist ein Provider für eine Speicherung in lokalen Dateien
Install-Module Microsoft.PowerShell.SecretStore

Das erste Modul liefert folgende Commandlets mit:

Get-Secret                Finds and returns a secret by name from registered vaults.
Get-SecretInfo            Finds and returns metadata information about secrets in registered vaults.
Get-SecretVault           Finds and returns registered vault information.
Register-SecretVault      Registers a SecretManagement extension vault module for the current user.
Remove-Secret             Removes a secret from a specified registered extension vault.
Set-Secret                Adds a secret to a SecretManagement registered vault.
Set-SecretInfo            Adds or replaces additional secret metadata to a secret currently stored in a vault.
Set-SecretVaultDefault    Sets the provided vault name as the default vault for the current user.
Test-SecretVault          Runs an extension vault self test.
Unregister-SecretVault    Un-registers an extension vault from SecretManagement for the current user.

In meinem PowerShell-Skript kann ich dann einfach mit "Get-Secred -name <name>" die gesicherten Daten abrufen, die ich vorher mit "Set-Secret" in einem der verbundenen Speichern (Vault) hinterlegt habe:

Der Speicher kann eine lokale verschlüsselte Datei oder z.B. auch ein Azure KeyVault oder auch der lokale CredentialManager (Microsoft.PowerShell.CredManStore) sein. Mittlerweile gibt es sogar Module zu anderen "Vaults" wie z.B.

Vielleicht baue ich mir auch mal ein Modul für meinen "PowerShell Keyvault

Weitere Links