即使没有服务在远程机器上侦听,也要检查端口是否打开

Check if port is open even if no service is listening on remote machine

场景:我进入公司网络并需要自动执行一些网络检查。其中之一是验证特定端口是否未被防火墙阻止,但通常没有服务侦听远程计算机上的这些端口,据我了解,Test-NetConnection 和 portqry,return false/filtered如果对方没有响应,但实际上并不意味着防火墙阻止了端口,对吗?

思路:我先用Test-NetConnection看看有没有回复,这样测试就ok了。如果远程端在端口上没有响应:

现在这是我想出的:

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;
}

不过,好像不行。此外,当这个 运行s 在第一次之后再次出现时,我收到错误消息“通常只允许每个套接字地址 (protocol/network address/port) 的一次使用”。我想 Socket 侦听器或 TCPClient 没有正确获取 closed/Stopped。

我也不确定 Invoke-Command 是否正在执行阻塞。我还尝试使用 -AsJob 参数 运行 它,但端口测试失败。 只是说,我进行了手动测试(登录到目标计算机,创建了一个侦听器并从源连接)以确保端口已打开,以证明该测试应该 return True。

提前致谢。

我有相同的东西,但对于 azure 工作簿,它可以帮助你 - 它会从你的订阅中获取所有 public IP,并将远程登录到 3389 和 1433 端口,如果打开 -发送电子邮件。

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

我终于找到了解决办法。 我的问题是:

  • 调用命令,即使是远程的,也是执行阻塞的
  • 如果无法建立连接,则未关闭 TCPListener
  • 我没有正确返回远程调用命令执行的结果

这是一个工作版本,它执行以下操作:

  • 使用 Test-NetConnection 检查 $port 是否在 $server 上打开并且正在侦听;
  • 如果没有,创建TCPClient + TCPListener
  • 在启动 TCPlistener 的远程 $server 上启动 Invoke-Command 作业,这样我们就有了一个端点来测试防火墙是否阻止了 $port;
  • 尝试将 TCPClient 连接到 $server
  • 上指定的 $port
  • 如果成功,调用命令作业 returns 端口号,否则为空;
  • 如果没有连接,TCPListener 将在 10 秒后关闭;

P.S.: 在我的示例中,我寻找一个已经存在的特定会话名称,您可以更改该部分。

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;
}