Come utilizzare PowerShell per rilevare le porte aperte in Windows

Nel panorama in continua evoluzione della sicurezza e della gestione IT, il monitoraggio dell’attività di rete e l’identificazione di potenziali vulnerabilità sono fondamentali. Le porte aperte possono fungere da punti di ingresso per accessi non autorizzati, per cui è importante che i professionisti IT le verifichino e le gestiscano regolarmente.

PowerShell, con le sue potenti capacità di scripting, offre un modo flessibile ed efficiente per monitorare queste porte. In questo post esploreremo uno script PowerShell progettato per  rilevare le porte aperte su un sistema Windows, discuteremo le sue applicazioni pratiche e forniremo informazioni sul suo funzionamento e utilizzo.

Comprendere la necessità del monitoraggio delle porte

Le porte sono un aspetto essenziale della comunicazione di rete, in quanto consentono a diversi servizi e applicazioni di interagire tra loro attraverso la rete. Tuttavia, le porte aperte, soprattutto quelle che non vengono monitorate attivamente, possono diventare un rischio per la sicurezza.

I criminali informatici spesso sfruttano queste vulnerabilità per ottenere un accesso non autorizzato ai sistemi. Per questo motivo è indispensabile che i professionisti IT, in particolare quelli dei Managed Service Provider (MSP), controllino regolarmente la rete per verificare la presenza di porte aperte, assicurandosi che siano aperte e in ascolto solo quelle necessarie.

Questo script PowerShell è stato progettato per aiutare i professionisti IT in questo compito, rilevando automaticamente le porte aperte e fornendo informazioni dettagliate su di esse. Può anche salvare i risultati in un campo personalizzato, per facilitare la tracciabilità e la documentazione.

Lo script per rilevare le porte aperte:

#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 {
    
    
    
}

 

Come funziona lo script

Lo script è stato progettato con un obiettivo chiaro: identificare e segnalare le porte TCP e UDP aperte che si trovano in uno stato di “ascolto” o “stabilito”. Di seguito viene illustrato in dettaglio il modo in cui lo script raggiunge questo obiettivo:

1. Impostazione iniziale e funzioni:

  • Lo script inizia definendo diverse funzioni di utilità, tra cui Test-IsElevated, che verifica se lo script è in esecuzione con privilegi di amministratore. Ciò è essenziale perché il controllo delle porte aperte richiede permessi elevati.
  • Un’altra funzione chiave, Set-NinjaProperty, è inclusa per gestire il salvataggio dei risultati in un campo personalizzato, se specificato. Questa funzione gestisce diversi tipi di dati e garantisce che non venga superato il limite di caratteri del campo.

2. Gestione dei parametri:

  • Lo script accetta due parametri: PortsToCheck, un elenco separato da virgole di porte da monitorare, e CustomFieldName, il nome del campo personalizzato in cui verranno archiviati i risultati.
  • Quindi elabora questi parametri, ampliando eventuali intervalli di porte e rimuovendo gli spazi bianchi per un’ esecuzione accurata.

3. Rilevamento della porta:

  • Lo script utilizza le cmdlet Get-NetTCPConnection e Get-NetUDPEndpoint per recuperare informazioni sulle connessioni TCP e UDP attive sul sistema.
  • Filtra i risultati in modo da includere solo le porte ‘in ascolto’ o ‘stabilite’ (per TCP) e tutte le porte UDP, poiché UDP è un protocollo stateless.
  • I risultati filtrati vengono quindi ordinati e formattati per l’output.

4. Salvataggio dell’output e dei campi personalizzati:

  • Lo script visualizza le porte rilevate, insieme a dettagli rilevanti come l’ID del processo, lo stato, l’indirizzo locale e il nome del processo.
  • Se viene specificato un campo personalizzato, lo script tenta di salvare i risultati utilizzando la funzione Set-NinjaProperty, gestendo eventuali errori che possono verificarsi durante questo processo.

Casi d’uso potenziali

Immagina un professionista IT che gestisce la sicurezza della rete di un’azienda di medie dimensioni. I controlli regolari delle porte fanno parte della loro routine per garantire che solo i servizi necessari siano in funzione e accessibili. Implementando questo script PowerShell, il professionista IT può automatizzare il processo di identificazione delle porte aperte, riducendo il rischio di lasciare esposte le porte vulnerabili.

Ad esempio, dopo aver eseguito lo script, il professionista IT nota che una porta sospetta è aperta e collegata a un servizio non essenziale. Possono quindi adottare misure per chiudere questa porta, riducendo così la potenziale superficie di attacco della loro rete.

Confronto con altri metodi

Per identificare le porte aperte, i professionisti IT utilizzano solitamente strumenti come netstat o strumenti per la scansione della rete di terze parti. Pur essendo efficaci, questi strumenti richiedono spesso un intervento manuale e possono non integrarsi facilmente con i sistemi automatizzati. Questo script PowerShell offre un approccio più integrato, consentendo l’automazione all’interno dei flussi di lavoro esistenti e fornendo flessibilità attraverso parametri come il salvataggio di campi personalizzati.

Domande frequenti

D: Ho bisogno di privilegi amministrativi per eseguire questo script?

Sì, lo script richiede i privilegi di amministratore per rilevare con precisione le porte aperte e i processi associati.

D: Lo script può controllare anche le porte UDP?

Sì, lo script controlla sia le porte TCP che UDP, con le porte TCP filtrate in base al loro stato e le porte UDP elencate indipendentemente dallo stato.

D: Cosa succede se si specifica un intervallo di porte non valido?

Lo script include la gestione degli errori per garantire che vengano elaborati solo gli intervalli di porte validi. Se viene specificato un intervallo non valido, lo script fornisce un messaggio di errore e termina.

Implicazioni per la sicurezza informatica

I risultati generati da questo script possono avere implicazioni significative per la sicurezza IT. Il monitoraggio regolare delle porte aperte può aiutare a rilevare servizi non autorizzati in esecuzione su una rete, potenzialmente indicando una violazione della sicurezza. Identificando e chiudendo le porte non necessarie, i professionisti IT possono ridurre i rischi e migliorare la sicurezza generale della propria organizzazione.

Best practice per l’utilizzo dello script

  • Esegui lo script regolarmente: Pianifica l’esecuzione regolare dello script per garantire un monitoraggio continuo delle porte aperte della rete.
  • Usa i campi personalizzati con prudenza: Quando salvi i risultati in un campo personalizzato, assicurati che il campo sia denominato e gestito in modo appropriato per evitare di sovrascrivere dati importanti.
  • Utilizza in combinazione con altre misure di sicurezza: Usa questo script come parte di una strategia di sicurezza più ampia, combinandolo con altri strumenti e pratiche per garantire una protezione completa.

Considerazioni finali

Questo script PowerShell è un potente strumento per i professionisti IT che desiderano automatizzare il processo di rilevamento e monitoraggio delle porte aperte. Integrando questo script nelle normali pratiche di sicurezza, potrai migliorare i meccanismi di difesa della tua rete e rispondere rapidamente alle potenziali vulnerabilità. NinjaOne offre una serie di strumenti che integrano tali script, fornendo una solida piattaforma per la gestione IT e il monitoraggio della sicurezza.

Passi successivi

La creazione di un team IT efficiente ed efficace richiede una soluzione centralizzata che funga da principale strumento per la fornitura di servizi. NinjaOne consente ai team IT di monitorare, gestire, proteggere e supportare tutti i dispositivi, ovunque essi si trovino, senza la necessità di una complessa infrastruttura locale.

Per saperne di più su NinjaOne Endpoint Management, fai un tour dal vivo, o inizia la tua prova gratuita della piattaforma NinjaOne.

Categorie:

Ti potrebbe interessare anche

Guarda una demo×
×

Guarda NinjaOne in azione!

Inviando questo modulo, accetto La politica sulla privacy di NinjaOne.

Termini e condizioni NinjaOne

Cliccando sul pulsante “Accetto” qui sotto, dichiari di accettare i seguenti termini legali e le nostre condizioni d’uso:

  • Diritti di proprietà: NinjaOne possiede e continuerà a possedere tutti i diritti, i titoli e gli interessi relativi allo script (compreso il copyright). NinjaOne ti concede una licenza limitata per l’utilizzo dello script in conformità con i presenti termini legali.
  • Limitazione d’uso: Puoi utilizzare lo script solo per legittimi scopi personali o aziendali interni e non puoi condividere lo script con altri soggetti.
  • Divieto di ripubblicazione: In nessun caso ti è consentito ripubblicare lo script in una libreria di script appartenente o sotto il controllo di un altro fornitore di software.
  • Esclusione di garanzia: Lo script viene fornito “così com’è” e “come disponibile”, senza garanzie di alcun tipo. NinjaOne non promette né garantisce che lo script sia privo di difetti o che soddisfi le tue esigenze o aspettative specifiche.
  • Assunzione del rischio: L’uso che farai dello script è da intendersi a tuo rischio. Riconosci che l’utilizzo dello script comporta alcuni rischi intrinseci, che comprendi e sei pronto ad assumerti.
  • Rinuncia e liberatoria: Non riterrai NinjaOne responsabile di eventuali conseguenze negative o indesiderate derivanti dall’uso dello script e rinuncerai a qualsiasi diritto legale o di equità e a qualsiasi rivalsa nei confronti di NinjaOne in relazione all’uso dello script.
  • EULA: Se sei un cliente NinjaOne, l’uso dello script è soggetto al Contratto di licenza con l’utente finale (EULA) applicabile.