In der sich ständig weiterentwickelnden Welt der IT-Sicherheit und -Verwaltung ist das Monitoring der Netzwerkaktivitäten und die Identifizierung potenzieller Schwachstellen essenziell. Unter anderem muss man offene und Established-Ports erkennen, da offene Ports als Einstiegspunkte für unbefugten Zugriff dienen können, weshalb es für IT-Experten wichtig ist, diese regelmäßig zu überprüfen und zu verwalten.
PowerShell bietet mit seinen leistungsstarken Skripting-Funktionen eine flexible und effiziente Möglichkeit zum Monitoring dieser Ports. In diesem Beitrag werden wir ein PowerShell-Skript untersuchen, das entwickelt wurde, um offene und Established-Ports auf einem Windows-System zu erkennen. Wir diskutieren vielmehr über seine praktischen Anwendungen und geben Einblicke in seine Funktionsweise und Verwendung.
Verständnis für die Notwendigkeit des Port-Monitorings
Ports sind ein wesentlicher Aspekt der Netzwerkkommunikation, der es verschiedenen Services und Anwendungen ermöglicht, über ein Netzwerk miteinander zu kommunizieren. Offene Ports, insbesondere solche, die nicht aktiv überwacht werden, können jedoch ein Sicherheitsrisiko darstellen.
Cyber-Kriminelle nutzen diese Schwachstellen häufig aus, um sich unbefugten Zugang zu Systemen zu verschaffen. Aus diesem Grund müssen IT-Experten, insbesondere bei Managed Service Providern (MSPs), ihr Netzwerk regelmäßig auf offene Ports überprüfen und sicherstellen, dass nur die notwendigen Ports offen sind und abhören (Listening-Ports).
Dieses PowerShell-Skript soll IT-Experten bei dieser Aufgabe unterstützen, indem es automatisch offene und Established-Ports erkennt und detaillierte Informationen dazu bereitstellt. Außerdem können die Ergebnisse in einem benutzerdefinierten Feld gespeichert werden, was eine bessere Nachverfolgung und Dokumentation ermöglicht.
Das Skript:
#Requires -Version 5.1 <# .SYNOPSIS Alert on specified ports that are Listening or Established and optionally save the results to a custom field. .DESCRIPTION Will alert on open ports, regardless if a firewall is blocking them or not. Checks for open ports that are in a 'Listen' or 'Established' state. UDP is a stateless protocol and will not have a state. Outputs the open ports, process ID, state, protocol, local address, and process name. When a Custom Field is provided this will save the results to that custom field. .EXAMPLE (No Parameters) ## EXAMPLE OUTPUT WITHOUT PARAMS ## [Alert] Found open port: 80, PID: 99, State: Listen, Local Address: 0.0.0.0, Process: nginx [Alert] Found open port: 500, PID: 99, State: Listen, Local Address: 0.0.0.0, Process: nginx PARAMETER: -Port "100,200,300-350, 400" A comma separated list of ports to check. Can include ranges (e.g. 100,200,300-350, 400) .EXAMPLE -Port "80,200,300-350, 400" ## EXAMPLE OUTPUT WITH Port ## [Alert] Found open port: 80, PID: 99, State: Listen, Local Address: 0.0.0.0, Process: nginx PARAMETER: -CustomField "ReplaceMeWithAnyMultilineCustomField" Name of the custom field to save the results to. .EXAMPLE -Port "80,200,300-350, 400" -CustomField "ReplaceMeWithAnyMultilineCustomField" ## EXAMPLE OUTPUT WITH CustomField ## [Alert] Found open port: 80, PID: 99, State: Listen, Local Address: 0.0.0.0, Process: nginx [Info] Saving results to custom field: ReplaceMeWithAnyMultilineCustomField [Info] Results saved to custom field: ReplaceMeWithAnyMultilineCustomField .OUTPUTS None .NOTES Supported Operating Systems: Windows 10/Windows Server 2016 or later with PowerShell 5.1 Release Notes: Initial Release By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://ninjastage2.wpengine.com/terms-of-use. Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA). #> #> [CmdletBinding()] param ( [Parameter()] [String]$PortsToCheck, [String]$CustomFieldName ) begin { function Test-IsElevated { $id = [System.Security.Principal.WindowsIdentity]::GetCurrent() $p = New-Object System.Security.Principal.WindowsPrincipal($id) $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) } function Set-NinjaProperty { [CmdletBinding()] Param( [Parameter(Mandatory = $True)] [String]$Name, [Parameter()] [String]$Type, [Parameter(Mandatory = $True, ValueFromPipeline = $True)] $Value, [Parameter()] [String]$DocumentName ) $Characters = $Value | Measure-Object -Character | Select-Object -ExpandProperty Characters if ($Characters -ge 10000) { throw [System.ArgumentOutOfRangeException]::New("Character limit exceeded, value is greater than 10,000 characters.") } # If we're requested to set the field value for a Ninja document we'll specify it here. $DocumentationParams = @{} if ($DocumentName) { $DocumentationParams["DocumentName"] = $DocumentName } # This is a list of valid fields that can be set. If no type is given, it will be assumed that the input doesn't need to be changed. $ValidFields = "Attachment", "Checkbox", "Date", "Date or Date Time", "Decimal", "Dropdown", "Email", "Integer", "IP Address", "MultiLine", "MultiSelect", "Phone", "Secure", "Text", "Time", "URL", "WYSIWYG" if ($Type -and $ValidFields -notcontains $Type) { Write-Warning "$Type is an invalid type! Please check here for valid types. https://ninjarmm.zendesk.com/hc/en-us/articles/16973443979789-Command-Line-Interface-CLI-Supported-Fields-and-Functionality" } # The field below requires additional information to be set $NeedsOptions = "Dropdown" if ($DocumentName) { if ($NeedsOptions -contains $Type) { # We'll redirect the error output to the success stream to make it easier to error out if nothing was found or something else went wrong. $NinjaPropertyOptions = Ninja-Property-Docs-Options -AttributeName $Name @DocumentationParams 2>&1 } } else { if ($NeedsOptions -contains $Type) { $NinjaPropertyOptions = Ninja-Property-Options -Name $Name 2>&1 } } # If an error is received it will have an exception property, the function will exit with that error information. if ($NinjaPropertyOptions.Exception) { throw $NinjaPropertyOptions } # The below type's require values not typically given in order to be set. The below code will convert whatever we're given into a format ninjarmm-cli supports. switch ($Type) { "Checkbox" { # While it's highly likely we were given a value like "True" or a boolean datatype it's better to be safe than sorry. $NinjaValue = [System.Convert]::ToBoolean($Value) } "Date or Date Time" { # Ninjarmm-cli expects the Date-Time to be in Unix Epoch time so we'll convert it here. $Date = (Get-Date $Value).ToUniversalTime() $TimeSpan = New-TimeSpan (Get-Date "1970-01-01 00:00:00") $Date $NinjaValue = $TimeSpan.TotalSeconds } "Dropdown" { # Ninjarmm-cli is expecting the guid of the option we're trying to select. So we'll match up the value we were given with a guid. $Options = $NinjaPropertyOptions -replace '=', ',' | ConvertFrom-Csv -Header "GUID", "Name" $Selection = $Options | Where-Object { $_.Name -eq $Value } | Select-Object -ExpandProperty GUID if (-not $Selection) { throw [System.ArgumentOutOfRangeException]::New("Value is not present in dropdown") } $NinjaValue = $Selection } default { # All the other types shouldn't require additional work on the input. $NinjaValue = $Value } } # We'll need to set the field differently depending on if its a field in a Ninja Document or not. if ($DocumentName) { $CustomField = Ninja-Property-Docs-Set -AttributeName $Name -AttributeValue $NinjaValue @DocumentationParams 2>&1 } else { $CustomField = $NinjaValue | Ninja-Property-Set-Piped -Name $Name 2>&1 } if ($CustomField.Exception) { throw $CustomField } } } process { if (-not (Test-IsElevated)) { Write-Error -Message "Access Denied. Please run with Administrator privileges." exit 1 } if ($env:portsToCheck -and $env:portsToCheck -ne 'null') { $PortsToCheck = $env:portsToCheck } if ($env:customFieldName -and $env:customFieldName -ne 'null') { $CustomFieldName = $env:customFieldName } # Remove any whitespace $PortsToCheck = $PortsToCheck -replace '\s', '' # Parse the ports to check $Ports = if ($PortsToCheck) { # Split the ports by comma and handle ranges $PortsToCheck -split ',' | ForEach-Object { # Trim the whitespace $Ports = "$_".Trim() # If the port is a range, expand it if ($Ports -match '-') { # Split the range and expand it $Range = $Ports -split '-' | ForEach-Object { "$_".Trim() } | Where-Object { $_ } if ($Range.Count -ne 2) { Write-Host "[Error] Invalid range formatting, must be two number with a dash in between them (eg 1-10): $PortsToCheck" exit 1 } try { $Range[0]..$Range[1] } catch { Write-Host "[Error] Failed to parse range, must be two number with a dash in between them (eg 1-10): $PortsToCheck" exit 1 } } else { $Ports } } } else { $null } if ($($Ports | Where-Object { [int]$_ -gt 65535 })) { Write-Host "[Error] Can not search for ports above 65535. Must be with in the range of 1 to 65535." exit 1 } # Get the open ports $FoundPorts = $( Get-NetTCPConnection | Select-Object @( 'LocalAddress' 'LocalPort' 'State' @{Name = "Protocol"; Expression = { "TCP" } } 'OwningProcess' @{Name = "Process"; Expression = { (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName } } ) Get-NetUDPEndpoint | Select-Object @( 'LocalAddress' 'LocalPort' @{Name = "State"; Expression = { "None" } } @{Name = "Protocol"; Expression = { "UDP" } } 'OwningProcess' @{Name = "Process"; Expression = { (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName } } ) ) | Where-Object { $( <# When Ports are specified select just those ports. #> if ($Ports) { $_.LocalPort -in $Ports }else { $true } ) -and ( <# Filter out anything that isn't listening or established. #> $( $_.Protocol -eq "TCP" -and $( $_.State -eq "Listen" -or $_.State -eq "Established" ) ) -or <# UDP is stateless, return all UDP connections. #> $_.Protocol -eq "UDP" ) } | Sort-Object LocalPort | Select-Object * -Unique if (-not $FoundPorts -or $FoundPorts.Count -eq 0) { Write-Host "[Info] No ports were found listening or established with the specified: $PortsToCheck" } # Output the found ports $FoundPorts | ForEach-Object { Write-Host "[Alert] Found open port: $($_.LocalPort), PID: $($_.OwningProcess), Protocol: $($_.Protocol), State: $($_.State), Local IP: $($_.LocalAddress), Process: $($_.Process)" } # Save the results to a custom field if one was provided if ($CustomFieldName -and $CustomFieldName -ne 'null') { try { Write-Host "[Info] Saving results to custom field: $CustomFieldName" Set-NinjaProperty -Name $CustomFieldName -Value $( $FoundPorts | ForEach-Object { "Open port: $($_.LocalPort), PID: $($_.OwningProcess), Protocol: $($_.Protocol), State: $($_.State), Local Address: $($_.LocalAddress), Process: $($_.Process)" } | Out-String ) Write-Host "[Info] Results saved to custom field: $CustomFieldName" } catch { Write-Host $_.Exception.Message Write-Host "[Warn] Failed to save results to custom field: $CustomFieldName" exit 1 } } } end { }
Wie das Skript funktioniert
Das Skript hat ein klares Ziel: offene TCP- und UDP-Ports zu identifizieren und zu melden, die sich entweder im Zustand ‚Listening‘ oder ‚Established‘ befinden. Im Folgenden wird detailliert beschrieben, wie das Skript dies erreicht:
1. Ersteinrichtung und Funktionen:
- Das Skript beginnt mit der Definition mehrerer Dienstprogramme, darunter Test-IsElevated, das überprüft, ob das Skript mit Administratorrechten ausgeführt wird. Dies ist unerlässlich, da für die Überprüfung auf offene Ports erhöhte Berechtigungen erforderlich sind.
- Eine weitere Schlüsselfunktion, Set-NinjaProperty, ist enthalten, um die Speicherung von Ergebnissen in einem benutzerdefinierten Feld zu bewältigen, falls angegeben. Diese Funktion verwaltet verschiedene Datentypen und garantiert, dass die Zeichengrenze für das Feld nicht überschritten wird.
2. Behandlung der Parameter:
- Das Skript akzeptiert zwei Parameter: PortsToCheck, eine durch Kommata getrennte Liste der zu überwachenden Ports, und CustomFieldName, der Name des benutzerdefinierten Feldes, in dem die Ergebnisse gespeichert werden sollen.
- Anschließend werden diese Parameter verarbeitet, wobei alle Port-Bereiche erweitert und Leerzeichen entfernt werden, um eine genaue Verarbeitung zu gewährleisten.
3. Port-Erkennung:
- Das Skript verwendet die cmdlets Get-NetTCPConnection und Get-NetUDPEndpoint, um Informationen über aktive TCP- und UDP-Verbindungen auf dem System abzurufen.
- Die Ergebnisse werden so gefiltert, dass für TCP nur die Listening- und Established-Ports berücksichtigt werden, sowie alle UDP-Ports, da UDP ein zustandsloses Protokoll ist.
- Die gefilterten Ergebnisse werden dann sortiert und für die Ausgabe formatiert.
4. Ausgabe und Speichern von benutzerdefinierten Feldern:
- Das Skript gibt die erkannten Ports zusammen mit den relevanten Details wie Prozess-ID, Status, lokaler Adresse und Prozessnamen aus.
- Wenn ein benutzerdefiniertes Feld angegeben ist, versucht das Skript, die Ergebnisse mit der Funktion Set-NinjaProperty zu speichern, und behandelt alle Fehler, die während dieses Vorgangs auftreten können.
Potenzielle Anwendungsfälle
Stellen Sie sich vor, ein IT-Experte ist für die Sicherheit des Netzwerks eines mittelständischen Unternehmens zuständig. Regelmäßige Port-Audits sind Teil seiner Routine, um zu gewährleisten, dass nur notwendige Dienste laufen und zugänglich sind. Durch die Implementierung dieses PowerShell-Skripts kann der IT-Experte den Prozess der Identifizierung offener Ports automatisieren und so das Risiko verringern, dass anfällige Ports ungeschützt bleiben.
Nach der Ausführung des Skripts stellt der IT-Experte beispielsweise fest, dass ein unerwarteter Port offen und mit einem nicht unbedingt erforderlichen Dienst verbunden ist. Sie können dann Maßnahmen ergreifen, um diesen Port zu schließen und so die potenzielle Angriffsfläche ihres Netzwerks zu verringern.
Vergleiche mit anderen Methoden
Traditionell verwenden IT-Experten Tools wie ’netstat‘ oder Netzwerk-Scanner von Drittanbietern, um offene Ports zu identifizieren. Diese Tools sind zwar effektiv, erfordern aber häufig manuelle Eingriffe und lassen sich nicht ohne weiteres in automatisierte Systeme integrieren. Dieses PowerShell-Skript bietet einen stärker integrierten Ansatz, der die Automatisierung innerhalb bestehender Arbeitsabläufe ermöglicht und durch Parameter wie das Speichern benutzerdefinierter Felder Flexibilität bietet.
Häufig gestellte Fragen
F: Muss ich dieses Skript mit Administratorrechten ausführen?
Ja, das Skript benötigt Administratorrechte, um offene Ports und die zugehörigen Prozesse genau zu erkennen.
F: Kann das Skript auch nach UDP-Ports suchen?
Ja, das Skript überprüft sowohl TCP- als auch UDP-Ports, wobei TCP-Ports nach ihrem Status gefiltert werden und UDP-Ports unabhängig von ihrem Status aufgelistet werden.
F: Was passiert, wenn ich einen ungültigen Port-Bereich angebe?
Das Skript enthält eine Fehlerbehandlungslösung, um sicherzustellen, dass nur gültige Port-Bereiche verarbeitet werden. Wenn ein ungültiger Bereich angegeben wird, gibt das Skript eine Fehlermeldung aus und beendet sich.
Auswirkungen auf die IT-Sicherheit
Die von diesem Skript generierten Ergebnisse können erhebliche Auswirkungen auf die IT-Sicherheit haben. Das regelmäßige Monitoring offener Ports kann dazu beitragen, nicht autorisierte Dienste in einem Netzwerk zu erkennen, die möglicherweise auf eine Sicherheitsverletzung hindeuten. Durch die Identifizierung und Schließung unnötiger Ports können IT-Experten Risiken minimieren und die allgemeine Sicherheitslage ihres Unternehmens verbessern.
Best Practices für die Verwendung dieses Skripts
- Lassen Sie es regelmäßig laufen: Planen Sie die regelmäßige Ausführung des Skripts, um eine kontinuierliche Überwachung der offenen Ports Ihres Netzwerks zu gewährleisten.
- Verwenden Sie benutzerdefinierte Felder mit Bedacht: Wenn Sie Ergebnisse in einem benutzerdefinierten Feld speichern, stellen Sie sicher, dass das Feld angemessen benannt und verwaltet wird, um das Überschreiben wichtiger Daten zu vermeiden.
- Kombinieren Sie es mit anderen Sicherheitsmaßnahmen: Verwenden Sie dieses Skript als Teil einer umfassenderen Sicherheitsstrategie und kombinieren Sie es mit anderen Tools und Praktiken, um einen kompletten Schutz zu gewährleisten.
Abschließende Überlegungen
Dieses PowerShell-Skript ist ein leistungsstarkes Tool für IT-Experten, die den Prozess der Erkennung und Überwachung offener Ports automatisieren möchten. Wenn Sie dieses Skript in Ihre regulären Sicherheitspraktiken integrieren, können Sie die Verteidigungsmechanismen Ihres Netzwerks verbessern und schnell auf potenzielle Schwachstellen reagieren. NinjaOne bietet eine Reihe von Tools, die solche Skripte ergänzen und eine solide Plattform für IT-Management und Sicherheits-Monitoring bieten.