netzwerkdude
Goto Top

OpenVPN Connectivität testen mit PowerShell

Moin,

ich würde für ein PowerShell Tool eine funktion benötigen bei dem die aktuelle erreichbarkeit eines OpenVPN Dienstes geprüft wird. Also wirklich der "Dienst" auf UDP 500 und nicht der Server.

Das UDP Packet versenden und auf Antwort warten funtioniert, nach dem Post hier:
https://learn-powershell.net/2011/02/21/querying-udp-ports-with-powershe ...
$payload = "bla"  

#UDP Client Object erstellen, mit dem highport 11222
$udpobject = new-Object system.Net.Sockets.Udpclient(11222)

#Timeout auf 10sek setzen (in ms):
$udpobject.Client.ReceiveTimeout = 10000 

#payload basteln 
$AsciiObject = New-Object System.Text.ASCIIEncoding
$Bytes = $AsciiObject.GetBytes($payload)

#pseudo "Connection" herstellen (UDP hat keine connection!) 
$udpobject.Connect("192.168.1.50",500)  

#das UDP Packet versenden:
[void]$udpobject.Send($bytes,$bytes.length)

#IP Endpoint fürs empfangen
$remoteendpoint = New-Object system.net.ipendpoint([system.net.ipaddress]::Any,0)

#Receiveendpoint:
$receivebytes = $udpobject.Receive([ref]$remoteendpoint)

#den returnvalue umwandeln zu string:
[string]$returndata = $a.GetString($receivebytes)

#schaun was zurückkommt
Write-Host "Das kam zurück: $($returndata.ToString())"  
Write-Host "Von: $($remoteendpoint.address.ToString()) $($remoteendpoint.Port.ToString())"  

Nun ist es so das hier gemeint wird das der OpenVPN Server alle Pakete verwirft die nicht die Richtige HMAC haben wenn tls-auth an ist (was sie ist):
https://serverfault.com/questions/262474/how-to-check-that-an-openvpn-se ...

Soweit auch okay, ich könnte einen validen key für den OpenVPN Server generieren und damit meine pakete signieren, irgendwie kanns ja PowerShell:
https://gist.github.com/jokecamp/2c1a67b8f277797ecdb3

Okay, jetzt finde ich leider nicht wie das Paket genau aussehen soll damit es eine response den OpenVPN triggert face-sad

Was ich fand ist dieses Python Skript das für Nagios / Icinga OpenVPN erreichbarkeit checkt, und er baut ein Paket mit der richtigen HMAC signatur:
https://github.com/liquidat/nagios-icinga-openvpn/blob/master/bin/check_ ...
(zeile 37-61)
Leider steige ich da aus den Code komplett zu verstehen und in Powershell umzuschreiben face-sad
Hoffe jemand kann mir da aushelfen

MFG
N-Dude

Content-ID: 625166

Url: https://administrator.de/contentid/625166

Ausgedruckt am: 24.11.2024 um 00:11 Uhr

tech-flare
tech-flare 23.11.2020 um 14:16:42 Uhr
Goto Top
Zitat von @NetzwerkDude:

Moin,

ich würde für ein PowerShell Tool eine funktion benötigen bei dem die aktuelle erreichbarkeit eines OpenVPN Dienstes geprüft wird. Also wirklich der "Dienst" auf UDP 500 und nicht der Server.
Hast du den Port angepasst? OpenVPN läuft normalerweise auf 1194. Bestenfalls auf 443 um Blockaden zu umgehen
NetzwerkDude
NetzwerkDude 23.11.2020 aktualisiert um 14:17:55 Uhr
Goto Top
ah vergesen zu sagen, ist wirklich custom port 500, stimmt schon face-smile
aqui
aqui 23.11.2020 aktualisiert um 16:03:50 Uhr
Goto Top
Keine besonders gute und auch intelligente Wahl für einen "Dude", denn das ist bekanntlich weltweit fest dem IKE Protokoll (ISAKMP) zugewiesen was selber wieder ein Teil der IPsec VPN Protokoll Suite ist. Bei 80% der Anschlüsse antwortet dir dann der ISAKMP Server.
Also besser UDP 1194 behalten oder wenn, dann wie immer, einen der Ephemeral Ports von 49152 bis 65535 wie z.B. 51194 verwenden o.ä. Hat zudem den Vorteil das die allermeisten der bösen Portscanner diesen Bereich nicht nutzen.
https://en.wikipedia.org/wiki/Ephemeral_port
wie das Paket genau aussehen soll damit es eine response den OpenVPN triggert
Der pfiffige NetzwerkDude nimmt dafür bekanntlich den Wireshark oder tcpdump und sniffert das mit, dann weiss er es bis aufs Bit genau ! face-wink
Oder...lädt einen Beispieltrace und filtert dann einmal nach OpenVPN.
NetzwerkDude
NetzwerkDude 23.11.2020 um 16:43:05 Uhr
Goto Top
Meine Güte, was alle der Port stört...
Also: Auf 1194 läuft schon ein anderer OpenVPN Dienst - daher ist es SCHON SO GEWOLLT das der nun den eigentlich für isakmp reservierten port nutzt.

Es ist auch unötig einen highport zu nehmen da der OpenVPN DANK des tls-auth features auf keine Anfragen reagiert! also: falsche / keine hmac am packet > drop.

Ob man das initialpacket replayen kann, teste ich morgen. Schöner wärs dennoch das packet mit dem key aus einem file zu signieren - so könnte man das tool universeller einsetzen ohne eine passgenaue payload hardzucoden.

MFG
N-Dude
NetzwerkDude
NetzwerkDude 24.11.2020 aktualisiert um 09:40:49 Uhr
Goto Top
Also: Das erste Paket lässt sich replayen, und es kommt eine antwort vom Server, soweit die "hack" lösung - ich lasse den thread noch offen, da es wie oben geschrieben viel flexibler wäre das paket aus einem keyfile zu generieren / signieren
colinardo
Lösung colinardo 25.11.2020, aktualisiert am 26.11.2020 um 12:24:22 Uhr
Goto Top
Servus @NetzwerkDude,
hab dir mal das Erzeugen und Senden des UDP-Packets in eine kleine Function gepresst (hier nur die reine UDP-Variante). Hostname, Port, Timeout, TA-Secretfile und TLS-AUTH Verfahren kannst du der Function Send-OpenVPNClientResetMessage als Parameter übergeben Die Funktion gibt bei Erfolg ein Custom-Object inkl. Server Response zurück ansonsten nichts bzw. passende Fehlermeldung. Die zweite Funktion Validate-OpenVPNServerResponse validiert die von der ersten Funktion zurückgelieferte Server-Response indem man sie einfach in die Pipeline hinter die erste Funktion schaltet.
Ein Beispielaufruf findest du in der letzten Zeile des Codes.
function Send-OpenVPNClientResetMessage {
    param(
        [Parameter(mandatory=$true)][string]$hostname,
        [Parameter(mandatory=$false)][int]$port = 1194,
        [Parameter(mandatory=$true)][string]$authfile,
        [Parameter(mandatory=$false)][int]$timeout = 5000,
        [Parameter(mandatory=$false)][ValidateSet('HMACSHA1','HMACSHA256','HMACSHA384','HMACSHA512')][string]$hashalgorithm = 'HMACSHA1'  
    )

    # load secret key from file
    $ta_bytes = [regex]::matches(([regex]::match((gc $authfile -raw),'(?ism)(?<=-$).*?(?=^-)').Value.trim() -replace '[^\da-f]'),'.{2}').Value | %{[byte][convert]::ToUInt32($_,16)}  
    
    #$serverkey = $ta_bytes[64..127]
    
    # create HMAC with secret client key
    $hmac = [System.Security.Cryptography.HMAC]::Create($hashalgorithm)
    # set clientkey for HMAC
    $hmac.Key = $ta_bytes[192..(192+($hmac.HashSize / 8)-1)]
    
    # create random 8 byte session id
    [byte[]]$sid = 1..255 | get-random -Count 8 | %{[byte]$_}
    # generate unix time in 4 bytes
    [byte[]]$time = [System.BitConverter]::GetBytes([uint32][double]::Parse((Get-Date (get-date).ToUniversalTime() -UFormat %s)))

    # convert time to big endian
    [array]::Reverse($time)
    
    # create HMAC header
    [byte[]]$hmac_payload = 0,0,0,1 + $time + 56 + $sid + 0,0,0,0,0

    # create final packet
    [byte[]]$payload = (,56) + $sid + $hmac.ComputeHash($hmac_payload) + 0,0,0,1 + $time + 0,0,0,0,0

    $result = $null, $udpobject = $null
    try{
        # create udp client
        $udpobject = new-Object system.Net.Sockets.Udpclient
        # set client timeout
        $udpobject.Client.ReceiveTimeout = $timeout
        # create local udp endpoint
        $remoteendpoint = New-Object system.net.ipendpoint([system.net.ipaddress]::Any,0)
        # send udp payload
        [void]$udpobject.Send($payload,$payload.Length,$hostname,$port)
        # receive response bytes
        $buffer = $udpobject.Receive([ref]$remoteendpoint)

        return [pscustomobject]@{
            Sid = $sid
            KeyData = $ta_bytes[64..(64+($hmac.HashSize/8)-1)]
            ResponseBuffer = $buffer
            HashAlgorithm = $hashalgorithm
        }
    }catch{
        write-host $_.Exception.Message -F Red
    }finally{
        if ($udpobject) {$udpobject.Close();$udpobject.Dispose()}
    }
}

# Validate Server response function
function Validate-OpenVPNServerResponse {
    [cmdletbinding()]
    param(
        [Parameter(mandatory=$true, ValueFromPipelineByPropertyName=$true)][ValidateNotNullOrEmpty()][byte[]]$ResponseBuffer,
        [Parameter(mandatory=$true, ValueFromPipelineByPropertyName=$true)][ValidateNotNullOrEmpty()][Alias('Sid')][byte[]]$QuerySid,  
        [Parameter(mandatory=$true, ValueFromPipelineByPropertyName=$true)][ValidateNotNullOrEmpty()][byte[]]$KeyData,
        [Parameter(mandatory=$true, ValueFromPipelineByPropertyName=$true)][ValidateNotNullOrEmpty()][string]$HashAlgorithm
    )

    process{
        $digest_size = $KeyData.Length

        # identify packet
        $ident = $ResponseBuffer.Length - $ResponseBuffer[9] * 4
        if ($ident -eq 14){
            $plen = 0
        }elseif($ident -eq 22){
            $plen = 1
        }elseif(($ResponseBuffer.Length - $ResponseBuffer[(17 + $digest_size)] * 4) -eq (30+$digest_size)){
            $plen = 2
        }else{
            write-error -Message "Packet could not be identified!"  
            return $null
        }

        # parse packet
        $ptype = $ResponseBuffer # packet type
        $sid = $ResponseBuffer[1..8] # sessionid
    
        if ($plen -ge 2){
            $phmac = $ResponseBuffer[9..(9+$digest_size-1)]
            $_pid = $ResponseBuffer[($digest_size+12)]
            $time = $ResponseBuffer[($digest_size+13)..($digest_size+16)]
        }

        $mpidlen = $ResponseBuffer[($digest_size+17)] # message packet id array length
        $mpidarray = 0..$midplen | %{$ResponseBuffer[($digest_size+18+$_)..($digest_size+21+$_)]}

        $cpos = $digest_size+18+($midplen * 4)

        if ($plen -ge 1){
            $rsid = $ResponseBuffer[$cpos..($cpos + 7)] # remote session id
        }
        $mpid = $ResponseBuffer[($cpos + 12)] # message packet id

       # validate packet
        if ($ptype -ne 64) {
            write-error -Message "Wrong packet type."  
            return $null
        }
        if ($mpid -ne 0){
            write-error -Message "Wrong message packet id."  
            return $null
        }
        if($plen -ge 1 -and (!(compare $rsid $QuerySid))){
            write-error -Message "Wrong session id."  
            return $null
        }
        if ($plen -ge 2 -and $KeyData){
            if ($_pid -ne 1){
                write-error -Message "Wrong pid."  
                return $null
            }
            # generate hmac
            # create HMAC with secret server key
            $hmac = [System.Security.Cryptography.HMAC]::Create($HashAlgorithm)
            $hmac.Key = $KeyData

            [byte[]]$payload = (0,0,0,$_pid) + $time + $ptype + $sid + $mpidlen + 0,0,0,0 + $rsid + $mpid

            if (!(Compare $phmac $hmac.ComputeHash($payload))){
                Write-Error -Message "Invalid HMAC"  
                return $null
            }

            write-host "Response validated - checked HMAC!" -F Green  
            return $true
         }
         write-host "Response validated!" -F Green  
         return $true
    }
}

Send-OpenVPNClientResetMessage -hostname "openvpn.server.tld" -port 1194 -authfile 'D:\openvpn\ta.key' -hashalgorithm HMACSHA1 -timeout 4000 | Validate-OpenVPNServerResponse  

Viel Spaß damit
Grüße Uwe
aqui
aqui 25.11.2020 um 17:41:56 Uhr
Goto Top
Hammer ! 👍
Lochkartenstanzer
Lochkartenstanzer 25.11.2020 aktualisiert um 17:44:32 Uhr
Goto Top
Zitat von @aqui:

Hammer ! 👍

Jau, unser Uwe, ich staune auch immer wieder, wie er das Zeug grad mal so im Vorbeigehen aus dem Ärmel schüttelt. 👍

lks
colinardo
colinardo 25.11.2020 aktualisiert um 17:50:54 Uhr
Goto Top
Bei nützlichen Skripts für die Nachwelt bin ich immer mit dabei sofern das runde mit den zwei Zeigern es zulässt und keiner mit "Kloppe" vorbei kommt face-smile.
NetzwerkDude
NetzwerkDude 26.11.2020 um 11:50:57 Uhr
Goto Top
Perfekt, besten dank! - aber wo ist der gelöst button eigentlich hin? hm...
colinardo
Lösung colinardo 26.11.2020 aktualisiert um 12:22:09 Uhr
Goto Top
So, habe der Vollständigkeit halber die anschließende Validierung der Server-Response oben im Code noch nachgeholt.

Grüße Uwe