Powershell – Die zugrunde liegende Verbindung wurde geschlossen: Unerwarteter Fehler beim Senden…

Beim Abfragen von Webservices über Powershell kommt es immer wieder mal zu folgender Fehlermeldung:

PS N:\> Invoke-Webrequest -Uri https://tls-v1-2.badssl.com:1012/
Invoke-Webrequest : Die zugrunde liegende Verbindung wurde geschlossen: Unerwarteter Fehler beim Senden..
In Zeile:1 Zeichen:1
+ Invoke-Webrequest -Uri https://tls-v1-2.badssl.com:1012/
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebExc
eption
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

So wirklich schlau wird man aus der Meldung „Die zugrunde liegende Verbindung wurde geschlossen: Unerwarteter Fehler beim Senden..“ jetzt auf den ersten Blick nicht. (In Englisch: The underlying connection was closed: An unexpected error occurred on a send..)
Der Fehler tritt bei Problemen mit der TLS Verschlüsselung auf. Speziell wenn der Client (also die Powershell in diesem Fall) mit einer nicht unterstützten TLS Version beim Server ankommt.
Bei Windows Server 2016, 2012R2 oder älteren Windows Client Betriebssystemen ist die Standard Verschlüsselung TLS 1.1/1.0 welche von vielen Betreibern von Websites mittlerweile wegen der Sicherheit deaktiviert wurde.
Bein Windows Server 2019 und Windows 10 (1909) hatte ich bei Tests mit dem Powershell Invoke-Webrequest standardmäßig TLS 1.2 vorgefunden.

Wie kann man per Powershell die TLS Verschlüsselung ändern?

Über folgende Befehle vor dem Invoke-Webrequest bzw. Invoke-RestMethod kann die TLS Version gesteuert werden.

TLS 1.2

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

TLS 1.1

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls11

TLS 1 ( 1.0 )

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls

Beispiel

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$a = Invoke-Webrequest -Uri https://tls-v1-2.badssl.com:1012/

Dies kann man über folgende URL’s auch direkt testen (Quelle: Stackexchange.com):

This subdomain and port only supports TLSv1.2

https://tls-v1-2.badssl.com:1012/

This subdomain and port only supports TLSv1.1

https://tls-v1-1.badssl.com:1011/

This subdomain and port only supports TLSv1.0

https://tls-v1-0.badssl.com:1010/

Nützliche Links

IISCrypto

Mit dem kostenfreien Tool IISCrypto lässt sich speziell der IIS aber auch die Grundlegenden Systemstandards und TLS Protokolle definieren.

Cipherlist

Unter Cipherlist bekommt man jeweils immer die aktuellen SSL Einstellungen für viele Webserver, wie apache2, nginx, oder auch andere Dienste.

Ähnliche Fehlermeldungen

Auch bei der PowerShell Gallery muss man mittlerweile TLS 1.2 einsetzen. Ansonsten bekommt ihr folgende Fehlermeldung:

Install-Module -Name SqlServer
WARNUNG: Unable to resolve package source 'https://www.powershellgallery.com/api/v2'.
PackageManagement\Install-Package : Für die angegebenen Suchkriterien und den Paketnamen "SqlServer" wurde keine
Übereinstimmung gefunden. Verwenden Sie Get-PSRepository, um alle verfügbaren, registrierten Paketquellen anzuzeigen.
In C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModule.psm1:1772 Zeichen:21
+ ... $null = PackageManagement\Install-Package @PSBoundParameters
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Microsoft.Power....InstallPackage:InstallPackage) [Install-Package], Ex
ception
+ FullyQualifiedErrorId : NoMatchFoundForCriteria,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackage

Exchange 2016 und WMF 5.0 – Management Shell Fehler

Installiert man das Windows Management Framework 5.0 auf einem Windows Server 2012 R2 um z.B. die aktuelle Powershell 5.0 zu bekommen, sollte man vorher prüfen ob nicht auch die Exchange Management Shell verwendet wird.
Sonst führt dies zu folgendem Fehler: „Cannot find path “ because it does not exist.“

VERBOSE: Connecting to TESTDC.domain.info.
New-PSSession : Cannot find path '' because it does not exist.
At line:1 char:1
+ New-PSSession -ConnectionURI "$connectionUri" -ConfigurationName Micr ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OpenError: (System.Manageme....RemoteRunspace:RemoteRunspace) [New-PSSession], RemoteExc
   eption
    + FullyQualifiedErrorId : PSSessionOpenFailed
VERBOSE: Connecting to TESTDC.domain.info.


Das Windows Management Framework 5.0 findet man unter den installierten Updates (KB3134758).
Und wer lesen kann ist klar im Vorteil: In den System Requirements steht nichts von Powershell 5.0. (Aktueller Stand bei Exchange CU6)


Also Update entfernen und siehe da – alles geht 😉

Scripting: Das sichere Speichern von Passwörtern

Um seine privaten Passwörter zu speichern gibt es ja mittlerweile viele Tools wie z.B. PasswortDepot, Keepass, oder Lastpass die ein verschlüsseltes speichern ermöglichen.
Im Firmenumfeld gibt es aber unter Umständen einige Gründe in denen man Passwörter in Powershell oder Batch Scripten ablegen muss.
Jetzt gibt es verschiedene Wege ein Passwort zu speichern und zu nutzen:
1. Im Klartext
Leider ist es immer noch viel zu oft gängige Praxis, das Passwort aufgrund von Faulheit einfach im Klartext in einer Textdatei abzuspeichern.
2. Verschlüsselt via MD5/SHA-1
Die Verschlüsselung des Passwortes sollte auf jeden Fall im Umgang mit Kundendaten oder zum Beispiel für Logindaten erfolgen.
Wenn man ein automatischen Cronjob erzeugt der Remote Daten abgreift, bringt dies allerdings nichts, da ja immer noch das Kennwort (zum Entschlüsseln) irgendwo gespeichert bzw. eingegeben werden muss.
Weitere Infos zum Hashen von Passwörter findet ihr hier sehr schön beschrieben.
3. Verwendung des PSCredential (unter Windows)
Mit der Powershell wurde das PSCredential-Objekt eingeführt – ein Objekt in dem Username + Passwort verschlüsselt abgelegt werden.
Diese Funktion basiert auf der Windows Data Protection Api (DPAPI) wie auch alle anderen Windows-Anwendungen wie z.B. Internet Explorer usw.
Das erzeugen eines PSCredentials funktioniert zum Beispiel so:

$pw = Get-Credential

Wie man sieht besteht das Objekt aus Benutzernamen und dem Passwort welches als „System.Security.SecureString“ gespeichert wird:

PS C:\Users\Constey> $pw
UserName                                                               Password
--------                                                               --------
asd                                                System.Security.SecureString

Um das Passwort jetzt abspeichern zu können muss es in einen String konvertiert werden. Dieser String ist bereits verschlüsselt und sieht ungefähr so aus:

PS C:\Users\Constey> $pw.Password | ConvertFrom-SecureString
01000000d08c9ddf0115d1118c7a00c04fc297eb01000000cbd08902a5e36247a761f726c190898
f00000000020000000000106600000001000020000000db8dea5f0d8c77a856c7a156f2a0ebfc19
ce82a95bfc0877192fe3771178ae2f000000000e8000000002000020000000d172638c2edc9eeca
2b764e052fc94b2671d3b62fdcfb2edd98fac306a22d65670000000bbd1fb42c2f91ffc4f9abac5
4c1fc5cdf0396fee5db1f9bc1d21070c2cc07f1911c0f8af85c9eef1019bc50fd382bb120fb8826
7a51115677520452d6eb5347a615218419e3d5803170a3c71e58b5789f1de597082d5d9eb68af66
598590a1607b7975d3c70d6ae09c70ce781cb0f8e8400000001a1c3903abcc486186818c9695b88
b6c54acfb973a7e6670bd08f78653a275791b42b88d823db04348262908b37fdb03bd6d719a05f5
ea35917e69ab96a44a32

Zum Speichern in einer Datei kann man die Ausgabe per Pipe umleiten:

PS C:\Users\Constey> $pw.Password | ConvertFrom-SecureString | out-file c:\temp\test.txt

Wie liest man ein gespeichertes Passwort aus einer Datei wieder ein ?

$username = "test"
$pw = cat c:\temp\test.txt | ConvertTo-SecureString
$credential = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $pw

Hier sollte beachtet werden, dass man den Usernamen explizit nochmal von Hand angeben muss, das Kennwort aber aus der Datei importiert wird.
Die $credential Variable ist nun wieder unser PSCredential-Objekt und kann normal weiter verwendet werden.

Get-WmiObject -Query "Select * FROM Win32_OperatingSystem" -Credential $credential

Wenn man nur das Passwort als SecureString über die Konsole einlesen möchte geht dies alternativ auch so:

$pwOnly = read-host -assecurestring

[stextbox id=“info“]Seit Powershell 3.0 gibt es auch die Möglichkeit vollständige Objekte als XML-Datei zu exportieren. Dies kann unter Umständen einfacher sein, da dass Passwort automatisch konvertiert wird und das Objekt auch den Usernamen enthält.[/stextbox]

$pw = Get-Credential
$pw | Export-Clixml C:\temp\test.xml

Der Import funktioniert ebenso simple:

$pwNeu = Import-Clixml C:\temp\test.xml
PS C:\Users\Constey> $pwNeu
UserName                                                               Password
--------                                                               --------
asdasd                                             System.Security.SecureString

Die XML Datei ist wie folgt aufgebaut:

<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
  <Obj RefId="0">
    <TN RefId="0">
      <T>System.Management.Automation.PSCredential</T>
      <T>System.Object</T>
    </TN>
    <ToString>System.Management.Automation.PSCredential</ToString>
    <Props>
      <S N="UserName">asdasd</S>
      <SS N="Password">01000000d08c9ddf0115d1118c7a00c04fc297eb01000000cbd08902a5e36247a761f726c190898f00000000020000000000106600000001000020000000aa344345ba7c169386795f88c4631bde7834256b4d5f65023cf3a8520683b789000000000e8000000002000020000000b219a67d828f8fe0391687af3ca7ccfc96dfd21546b5ad45915745aed130bcd280000000bf438824b21f066088a69a8bf6c0b2286f50c2ff68b1c881b55d98d6b300d3407be4eb2de0521c21b03bdeec2cf60729ff97ead3217f592ab01954641d0c445c9a0ca77e73d83d949425613dcb51c97c917610a61239cb2aa4017141b2139a0ae7d80d738de22c0545019d38af1811dc8d89476d273f578b776cf0e7addc847c40000000bda7b710327a37477bc6db816a7ca7a3553f44fd71aff73bfa6bbaf1b16b9cc4a400fbc8c536dc08d23ed126547c3b5a049105ef8bb8ca487933755c8565aa8b</SS>
    </Props>
  </Obj>
</Objs>

[stextbox id=“info“]Wichtig: Das PSCredential bzw. der SecureString den man erzeugt wird auf Basis von der PC-Hardware aber auch von Werten des aktuellen Benutzers erzeugt. Das heißt, dass dieser String nicht unter einem anderem User funktioniert.
Das hat zudem den Vorteil, dass man ohne Zugangsdaten des Users nichts mit dem gespeichertem Passwort anfangen kann.
Eine Nutzung zum Beispiel über den Task Scheduler wäre dennoch möglich, denn man kann dort sein Script unter einem anderem Benutzer ausführen lassen. Dafür müssen bei der Erstellung des Tasks die Zugangsdaten des Benutzers einmalig angegeben werden. Diese werden dann wiederum verschlüsselt von Windows’s Credential Manager gespeichert.[/stextbox]
Abschließend gilt zu sagen, dass Sicherheit natürlich auch viel mit Bequemlichkeit zu tun hat – dennoch sollte man im Firmenumfeld seine Kennwörter nicht im Klartext in irgendwelchen Dateien abspeichern, jedenfalls sofern es nicht anders möglich ist.
Falls man dennoch mal mit unsicheren Programmen wie Net-Use, FTP etc. arbeiten muss, die keine -Credential Objekte unterstützen gibt es noch das Tool „Get-PSCredential“ von Thomas Franke, welche auch den Umgang mit solchen Programmen zumindest so Sicher wie möglich macht.

Powershell 3.0 (WMF 3.0) "Das Update ist nicht für Ihren Computer geeignet"

Moin,
beim Installieren des Windows Management Framework 3.0 kann es zu Problemen führen, wenn dot.Net 4.0 (Full) nicht installiert ist.
Beim Anwenden des Windows Update Paket (msu):

Windows6.0-KB2506146-x64.msu (2008 - 64bit)
Windows6.0-KB2506146-x86.msu (2008 - 32bit)
Windows6.1-KB2506143-x64.msu (2008 R2 - 64bit)
Windows6.1-KB2506143-x86.msu (2008 R2 - 32bit)

kann es zum Fehler:

Das Update ist nicht für Ihren Computer geeignet
The update is not applicable

Die Pakete sind Sprachen unabhängig.
Abhilfe schafft die Requirements zu lesen und dotNetFx40_Full_setup.exe zu installieren.
Gruß
Constey