Home > Back-end >  Check if port is open even if no service is listening on remote machine
Check if port is open even if no service is listening on remote machine

Time:12-29

Scenario: I'm into a corporate network and need to automate some network checks. One of these is to verify if specific ports are not firewall blocked but very often there is no service listening on those ports on remote machines and, per my understanding, both Test-NetConnection and portqry, return false/filtered if there is no response from the other side but it doesn't actually mean the firewall is blocking the port, right?

Idea: I first use Test-NetConnection to see if I get any reply, in that case the test is ok. If the remote end is not responding on the port:

  • I create a TCPClient object
  • Invoke a ScriptBlock to the remote computer to create a Listener on the specific port
  • try to connect from Source computer to the target so I can be sure the port is open

By now here is what I came up with:

function Test-Port([string] $server, [int] $port) {
    $out = $false;
    if(Test-NetConnection -ComputerName $server -Port $port | Select-Object -ExpandProperty TcpTestSucceeded) {
        $out = $true;
    } else {
        $TcpClient = New-Object System.Net.Sockets.TcpClient;
        try {
            $Session = New-PSSession -ComputerName $server -ErrorAction Stop;
            if($null -ne $Session) {
                $RemoteLastExitCode = Invoke-Command -Session $Session -ScriptBlock {
                    param($p)
                    try {
                        $Listener = [System.Net.Sockets.TcpListener]$p;
                        $Listener.Start();
                        while($true) {
                            $client = $Listener.AcceptTcpClient();
                            $client.Close();
                            Exit $p;
                        }
                    } catch [System.Net.Sockets.SocketException] {
                        Write-Host "Socket Error: $($_)";
                    } finally {
                        $Listener.Stop();
                    }
                    $LASTEXITCODE;
                } -ArgumentList $port
                try {
                    $TcpClient.Connect($server, $port);
                } catch [System.Net.Sockets.SocketException] {
                    Write-Host "TCP Client Error: $($_)";
                } finally {
                    if($RemoteLastExitCode -eq $port) { $out = $true; }
                    $TcpClient.Close();
                }
            }
        } catch [System.Management.Automation.Remoting.PSRemotingTransportException] {
            Write-Host "Error: Unable to open remote session to $($server). Skipping." -ForegroundColor Red;
        } finally {
            Remove-PSSession -Session $Session -ErrorAction SilentlyContinue;
        }
    }
    return $out;
}

However, it doesn't seem to work. Additionally, when this runs again after the first time, I receive the error "Only one usage of each socket address (protocol/network address/port) is normally permitted". I suppose either the Socket listener or the TCPClient doesn't get closed/Stopped properly.

I'm also not sure the Invoke-Command is execution blocking or not. I also tried to run it with -AsJob parameter but the port test fails. Just to say, I made a manual test (logged into target computer, created a listener and connected from source) to be sure the port is open as prove that this test should return True.

Thanks in advance.

CodePudding user response:

I have something the same, but for azure workbook, it could help you - it will take all public IPs from your subscription and will do a telnet to 3389 and 1433 ports, and, if opened - send an email.

workflow PortChecker
{
    #RunAs account name
$connectionName = "AzureRunAsConnection"

#connect to azure subscription
$servicePrincipalConnection = Get-AutomationConnection -Name $connectionName
$connectionResult =  Connect-AzAccount -Tenant $servicePrincipalConnection.TenantID -ApplicationId $servicePrincipalConnection.ApplicationID -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint -ServicePrincipal

$exclude = "Not Assigned"
$publicIps = Get-AzPublicIpAddress | where -Property IpAddress -CNotMatch $exclude | Select -ExpandProperty IpAddress 


function Test-Port
{
    param
    (
        [Parameter(Position=0, Mandatory = $True, HelpMessage="Provide destination source", ValueFromPipeline = $true)]
        $Address,
        [Parameter(Position=1, Mandatory = $False, HelpMessage="Provide port numbers", ValueFromPipeline = $true)]
        $Port
    )
    


    $ErrorActionPreference = "SilentlyContinue"
    $Results = @()

    ForEach ($A in $Address) {
 $Object = New-Object PSCustomObject
 $Object | Add-Member -MemberType NoteProperty -Name "Destination" -Value $A

            ForEach ($P in $Port) {
            
             try
    {
        $tcpClient = new-object Net.Sockets.TcpClient
        $tcpClient.Connect("$A", $P)
        $Object = "For IP: $Address Port $P is open"
    }
    catch
    {
         $Object = "For IP: $Address Port $P is closed"
    }
    finally
    {
       $tcpClient.Dispose() 
    }

        $Results  = $Object
    }

 If($Results){
    $Results
}}}


$allinfo = foreach -parallel ($publicIps in $publicIps) {Test-Port $publicIps -Port 1433, 3389}

$username = "---"
$password = '---' | ConvertTo-SecureString -asPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($username,$password)

#write-output $allinfo
$alertIP = $allinfo | where-object {$_ -like '*open*'}
#write-output $alertIP



if ($allinfo -like '*open*') { Send-MailMessage -SmtpServer %email-server% -useSsl -Port 587 -Credential $cred -From %From% -To %to% -Subject "New open port detected" -Body "New open port detected $alertIP, please check the Azure Portal" }
else {}
}

CodePudding user response:

I finally got a solution. My problems were:

  • Invoke-command, even if remote, is execution-blocking
  • TCPListener was not closed if the connection couldn't be established
  • I was not properly returning a result from the Remote invoke-command execution

Here is a working version which does the following:

  • uses Test-NetConnection to check if $port is open on $server and LISTENING;
  • if not, create TCPClient TCPListener
  • Launch an Invoke-Command job on the remote $server starting the TCPlistener so we have an endpoint where to test if the firewall is blocking the $port or not;
  • tries to connect the TCPClient to the specified $port on the $server
  • if it succeeded, the invoke-command job returns the port number, otherwise is null;
  • the TCPListener gets closed after 10 seconds if not connection comes;

P.S.: in my example, I look for a specific session name already existing, you can change that part.

function Test-Port([string] $server, [int] $port) {
    $out = $false;
    if(Test-NetConnection -ComputerName $server -Port $port | Select-Object -ExpandProperty TcpTestSucceeded) {
        $out = $true;
    } else {
        $removeSession = $false;
        $TcpClient = New-Object System.Net.Sockets.TcpClient;
        try {
            $Session = Get-PSSession | Where-Object -FilterScript { $_.State -eq "Opened" -and ($_.Name -eq "$($env:COMPUTERNAME)$($server)" -or $_.Name -eq "$($server)$($env:COMPUTERNAME)") };
            if($null -eq $Session) { 
                $Session = New-PSSession -ComputerName $server -ErrorAction Stop; 
                $removeSession = $true;
            }

            Invoke-Command -Session $Session -AsJob -JobName PortTest -ScriptBlock {
                param($p)
                $Script:return = $null;
                try {
                    $IPAddress = Get-NetIPConfiguration | Where-Object -FilterScript { $null -ne $_.IPv4DefaultGateway } | 
                        Select-Object -ExpandProperty IPv4Address | Select-Object -ExpandProperty IPAddress;
                    $Listener = [System.Net.Sockets.TcpListener]::new($IPAddress, $p);
                    $Listener.Start();

                    $counter = 0;
                    while($true) {
                        if(!$Listener.Pending()) {
                            if($counter -eq 20) {
                                $Listener.Stop();
                                break;
                            }
                            $counter  ;
                            Start-Sleep -Milliseconds 500;
                            continue;
                        }
                        $client = $Listener.AcceptTcpClient();
                        $Script:return = $p;
                        $client.Close();
                        break;
                    }
                } catch [System.Net.Sockets.SocketException] {
                    Write-Host "Socket Error: $($_)";
                } finally {
                    $Listener.Stop();
                }
                return $Script:return;
            } -ArgumentList $port
        
            $TcpClient.ReceiveTimeout = 10000;
            $TcpClient.SendTimeout = 10000;
            $TcpClient.Connect($server, $port);
            $RemoteReturn = Wait-Job -Name PortTest | Receive-Job;
        } catch [System.Management.Automation.Remoting.PSRemotingTransportException] {
            Write-Host "Error: Unable to open remote session to $($server). Skipping." -ForegroundColor Red;
        } catch [System.Net.Sockets.SocketException] {  
            Write-Host "TCP Client Error: $($_)";
        } finally {
            if($RemoteReturn -eq $port) { $out = $true; }
            $TcpClient.Close();
            if($removeSession) { Remove-PSSession -Session $Session -ErrorAction SilentlyContinue; }
        }
    }
    return $out;
}
  • Related