NMAP without NMAP - Port Testing and Scanning with PowerShell
Ever needed to do a portscan and didn't have nmap installed? I've had this more than once on an internal pentest or more often just on run-rate "is that port open? / is there a host firewall in the way?" testing.
Often we see folks doing something cludgy with telnet or ftp, but Powershell Test-NetConnection (tnc for short) does a fine job of spot testing for an open port or to see if an IP is in use.
For a quick ping test, run tnc with just the target host as an argument:
PS L:\> tnc 8.8.8.8 ComputerName : 8.8.8.8 RemoteAddress : 8.8.8.8 InterfaceAlias : Ethernet 2 SourceAddress : 192.168.122.201 PingSucceeded : True PingReplyDetails (RTT) : 15 ms |
For a quick traceroute:
PS L:\> tnc 8.8.8.8 -traceroute ComputerName : 8.8.8.8 RemoteAddress : 8.8.8.8 InterfaceAlias : Ethernet 2 SourceAddress : 192.168.122.201 PingSucceeded : True PingReplyDetails (RTT) : 13 ms TraceRoute : 192.168.122.1 99.254.226.1 66.185.90.177 24.156.147.129 209.148.235.222 72.14.216.54 108.170.228.0 172.253.69.113 8.8.8.8 |
Limiting the hopcount of traceroute:
PS L:\> tnc 8.8.8.8 -traceroute -hops 3 WARNING: Trace route to destination 8.8.8.8 did not complete. Trace terminated :: 66.185.90.177 ComputerName : 8.8.8.8 RemoteAddress : 8.8.8.8 InterfaceAlias : Ethernet 2 SourceAddress : 192.168.122.201 PingSucceeded : True PingReplyDetails (RTT) : 16 ms TraceRoute : 192.168.122.1 99.254.226.1 66.185.90.177 |
Testing an open port:
PS L:\> tnc 8.8.8.8 -port 443 ComputerName : 8.8.8.8 RemoteAddress : 8.8.8.8 RemotePort : 443 InterfaceAlias : Ethernet 2 SourceAddress : 192.168.122.201 TcpTestSucceeded : True |
Or, for a (not so) quick port scan:
$ip = "192.168.122.241" $result = @() for ($port = 9099; $port -le 9101 ; $port ++) { $result += (tnc $ip -port $port) | select remoteport, tcptestsucceeded } PS L:\> $result RemotePort TcpTestSucceeded ---------- ---------------- 9099 False 9100 True 9101 False |
Or if you just want the open ports:
$ip = "192.168.122.241" $result = @() for ($port = 9099; $port -le 9101 ; $port ++) { $a = (tnc $ip -port $port) | select remoteport, tcptestsucceeded if ($a.tcptestsucceeded ) { $result += $a } } |
As you may have noticed, tnc is a decent tool for testing a single port, but for a portscan it makes for the slowest tool ever. It is however a a great tool for troubleshooting an individual service because the syntax is so easy to remember in a pinch. Using Net.Sockets directly makes for a much faster scan, with slightly more complicated syntax:
new-object system.net.sockets.tcpclient -argumentlist "192.168.122.241","9100"
(yes, system.net.sockets.udpclient is also a thing, everything below works for udp too)
So our port scanner morphs to:
$targetservers = "192.168.122.241", "192.168.122.1" $ports = "22","23","80","443","9100" $result = @() foreach ($target in $targetservers) { foreach ($port in $ports) { $a = new-object system.net.sockets.tcpclient -argumentlist $target,$port if ($a.connected) { $r = new-object -type psobject $r | add-member -membertype noteproperty -name host -value $target $r | add-member -membertype noteproperty -name port -value $port $r | add-member -membertype noteproperty -name state -value "Open" $result += $r } } } |
Hmm, this still takes forever to come back from closed ports. How about this adding in a 100ms timeout on each connection - you can play with that number as needed:
$targetservers = "192.168.122.241", "192.168.122.1" $ports = "22","23","80","443","9100" $result = @() foreach ($target in $targetservers) { foreach ($port in $ports) { $obj = new-Object system.Net.Sockets.TcpClient $connect = $obj.BeginConnect($target,$port,$null,$null) $Wait = $connect.AsyncWaitHandle.WaitOne(100,$false) If (-Not $Wait) { write-host $target 'port' $port 'Closed - Timeout' } else { $value = "Open" write-host $target 'port' $port $value $r = new-object -type psobject $r | add-member -membertype noteproperty -name host -value $target $r | add-member -membertype noteproperty -name port -value $port $r | add-member -membertype noteproperty -name state -value $value $result += $r } } } 192.168.122.241 port 22 Closed - Timeout 192.168.122.241 port 23 Closed - Timeout 192.168.122.241 port 80 Open 192.168.122.241 port 443 Open 192.168.122.241 port 9100 Open 192.168.122.1 port 22 Open 192.168.122.1 port 23 Closed - Timeout 192.168.122.1 port 80 Open 192.168.122.1 port 443 Open 192.168.122.1 port 9100 Closed - Timeout
PS L:\> $result host port state ---- ---- ----- 192.168.122.241 80 Open 192.168.122.241 443 Open 192.168.122.241 9100 Open 192.168.122.1 22 Open 192.168.122.1 80 Open 192.168.122.1 443 Open |
You can of course play with this final script as needed - For instance if you wanted to do a whole range of ports you might change the"foreach ($port in $ports)" loop back to a for loop:
$lport = 0
$hport = 65535
for ($port = $lport; $port -le $hport ; $port ++)
Or if you wanted to scan a /24 subnet, you might change the "foreach ($target in $targetservers) " loop to:
$net = "192.168.122."
for ($h = 1; $h -le 254 ; $h++)
$target = $net+$h
This set of approaches is NOT meant to replace NMAP - for instance I can't tell you for instance how often I use NMAP scripts in a week! However, if you are testing a site-to-site VPN, testing a router or firewall ACL, or checking a host firewall, almost always the host you are testing from is a customer's server, and you won't have NMAP available. Considering that for so many people, the go-to test is to install a telnet (bad idea), then test for an open port using "telnet <target> <port>", with success being a blank screen, these approaches give you a LOT quicker and a LOT more reliably.
If you've got a any improvements to these scripts. in particular maybe a better way to indicate why a port might be not open (for instance - did it time out, send an ICMP port unreachable ro send a RST?) - by all means post to our comment section. The scripts and snips I posted are only as good as they needed to be the last time I needed them, by all means lets make these better!
References:
Test-Netconnection:
https://learn.microsoft.com/en-us/previous-versions/windows/powershell-scripting/dn372891(v=wps.630)
system.Net.Sockets.TcpClient
https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.tcpclient?view=net-6.0
===============
Rob VandenBrink
rob@coherentsecurity.com
Comments
thqmas
Nov 1st 2022
2 years ago
I added a latency output and shortened the result object creation
$targetservers = "77.21.200.99"
$ports = "443"
$result = @()
foreach ($target in $targetservers) {
foreach ($port in $ports) {
$obj = new-Object system.Net.Sockets.TcpClient
$connect = $obj.BeginConnect($target,$port,$null,$null)
$latency=(Measure-Command {
$Wait = $connect.AsyncWaitHandle.WaitOne(100,$false)
}).TotalMilliseconds
If (-Not $Wait) {
write-host $target 'port' $port 'Closed - Timeout'
}
else {
$value = "Open"
write-host $target 'port' $port $value ("{0:0.##}" -f $latency)
$result += [PSCustomObject]@{
host = $target
port = $port
state = $value
latency = ("{0:0.##}" -f $latency)
}
}
}
}
oppiman
Nov 2nd 2022
2 years ago
oppiman
Nov 3rd 2022
2 years ago
https://devblogs.microsoft.com/powershell/powershell-foreach-object-parallel-feature/
then you can run everything in parallel if you need to scan many ports or many IPs.
Remember to use thread-safe objects for result. I.e. ConcurrentDictionary
oppiman
Nov 3rd 2022
2 years ago