Recently a client needed me to quickly set up an IP Tables firewall on a production server that was effectively open on the Internet. I knew very little about the machine, and we couldn't afford to break any of the production traffic to and from the box.
It occurred to me that a decent first approximation would be to simply look at the network services currently in use, and create a firewall based on that. The resulting policy would probably be a bit more loose than it needed to or should be, but it would be infinitely better than no firewall at all!
I went with lsof, because I found the output easier to parse than netstat:
# lsof -i -nlP | awk '{print $1, $8, $9}' | sort -u COMMAND NODE NAME httpd TCP *:80 named TCP 127.0.0.1:53 named TCP 127.0.0.1:953 named TCP [::1]:953 named TCP 150.123.32.3:53 named UDP 127.0.0.1:53 named UDP 150.123.32.3:53 ntpd UDP [::1]:123 ntpd UDP *:123 ntpd UDP 127.0.0.1:123 ntpd UDP 150.123.32.3:123 ntpd UDP [fe80::baac:6fff:fe8e:a0f1]:123 ntpd UDP [fe80::baac:6fff:fe8e:a0f2]:123 portreser UDP *:783 sendmail TCP 150.123.32.3:25 sendmail TCP 150.123.32.3:25->58.50.15.213:1526 sendmail TCP *:587 sshd TCP *:22 sshd TCP 150.123.32.3:22->121.28.56.2:39054
I could have left off the process name, but it helped me decide which ports were important to include in the new firewall rules. Honestly, the output above was good enough for me to quickly throw together some workable IP Tables rules. I simply saved the output to a text file and hacked things together with a text editor.
But maybe you only care about the port information:
# lsof -i -nlP | awk '{print $9, $8, $1}' | sed 's/.*://' | sort -u 123 UDP ntpd 1526 TCP sendmail 22 TCP sshd 25 TCP sendmail 39054 TCP sshd 53 TCP named 53 UDP named 587 TCP sendmail 783 UDP portreser 80 TCP httpd 953 TCP named NAME NODE COMMAND
Note that I inverted the field output order, just to make my sed a little easier to write
If you wanted to go really crazy, you could even create and load the actual rules on the fly. I don't recommend this at all, but it will make Tim's life harder in the next section, so here goes:
lsof -i -nlP | tail -n +2 | awk '{print $9, $8}' | sed 's/.*://' | sort -u | tr A-Z a-z | while read port proto; do ufw allow $port/$proto; done
I added a "tail -n +2" to get rid of the header line. I also dropped the command name from my awk output. There's a new "tr A-Z a-z" in there to lower-case the protocol name. Finally we end with a loop that takes the port and protocol and uses the ufw command line interface to add the rules. You could do the same with the iptables command and its nasty syntax, but if you're on a Linux distro with UFW, I strongly urge you to use it!
So, Tim, I figure you can parse netstat output pretty easily. How about the command-line interface to the Windows firewall? Remember, adversity builds character...
Tim builds characterWhen I first saw this I thought, "Man, this is going to be easy with the new cmdlets in PowerShell v4!" There are a lot of new cmdlets available in PowerShell version 4, and both Windows 8.1 and Server 2012R2 ship with PowerShell version 4. In addition, PowerShell version 4 is available for Windows 7 SP1 (and later) and Windows Server 2008 R2 SP1 (and later).
The first cmdlet that will help us out here is Get-NetTCPConnection. According to the help page this cmdlet "gets current TCP connections. Use this cmdlet to view TCP connection properties such as local or remote IP address, local or remote port, and connection state." This is going to be great! But...
It doesn't mention the process ID or process name. Nooooo! This can't be. Let's look at all the properties of the output objects.
PS C:\> Get-NetTCPConnection | Format-List * State : Established AppliedSetting : Internet Caption : Description : ElementName : InstanceID : 192.168.1.167++445++10.11.22.33++49278 CommunicationStatus : DetailedStatus : HealthState : InstallDate : Name : OperatingStatus : OperationalStatus : PrimaryStatus : Status : StatusDescriptions : AvailableRequestedStates : EnabledDefault : 2 EnabledState : OtherEnabledState : RequestedState : 5 TimeOfLastStateChange : TransitioningToState : 12 AggregationBehavior : Directionality : LocalAddress : 192.168.1.167 LocalPort : 445 RemoteAddress : 10.11.22.33 RemotePort : 49278 PSComputerName : CimClass : ROOT/StandardCimv2:MSFT_NetTCPConnection CimInstanceProperties : {Caption, Description, ElementName, InstanceID...} CimSystemProperties : Microsoft.Management.Infrastructure.CimSystemProperties
Dang! This will get most of what we want (where "want" was defined by that Hal guy), but it won't get the process ID or the process name. So much for rubbing the new cmdlets in his face.
Let's forget about Hal for a second and get what we can with this cmdlet.
PS C:\> Get-NetTCPConnection | Select-Object LocalPort | Sort-Object -Unique LocalPort LocalPort --------- 135 139 445 3587 5357 49152 49153 49154 49155 49156 49157 49164
This is helpful for getting a list of ports, but not useful for making decisions about what should be allowed. Also, we would need to run Get-NetUDPEndpoint to get the UDP connections. This is so close, yet so bloody far. We have to resort to the old school netstat command and the -b option to get the executable name. In episode 123 we needed parsed netstat output. I recommended the Get-Netstat script available at poshcode.org. Sadly, we are going to have to resort to that again. With this script we can quickly get the port, protocol, and process name.
PS C:\> .\get-netstat.ps1 | Select-Object ProcessName, Protocol, LocalPort | Sort-Object -Unique LocalPort, Protocol, ProcessName ProcessName Protocol Localport ----------- -------- --------- svchost TCP 135 System UDP 137 System UDP 138 System TCP 139 svchost UDP 1900 svchost UDP 3540 svchost UDP 3544 svchost TCP 3587 dasHost UDP 3702 svchost UDP 3702 System TCP 445 svchost UDP 4500 ...
It should be pretty obvious that the port 137-149 and 445 should not be accessible from the internet. We can filter these ports out so that we don't allow these ports through the firewall.
PS C:\> ... | Where-Object { (135..139 + 445) -NotContains $_.LocalPort } ProcessName Protocol Localport ----------- -------- --------- svchost UDP 1900 svchost UDP 3540 svchost UDP 3544 svchost TCP 3587 dasHost UDP 3702 svchost UDP 3702 svchost UDP 4500 ...
Now that we have the ports and protocols we can create new firewall rules using the new New-NetFirewallRule cmdlet. Yeah!
PS C:\> .\get-netstat.ps1 | Select-Object Protocol, LocalPort | Sort-Object -Unique * | Where-Object { (135..139 + 445) -NotContains $_.LocalPort } | ForEach-Object { New-NetFirewallRule -DisplayName AllowedByScript -Direction Outbound -Action Allow -LocalPort $_.LocalPort -Protocol $_.Protocol } Name : {d15ca484-5d16-413f-8460-a29204ff06ed} DisplayName : AllowedByScript Description : DisplayGroup : Group : Enabled : True Profile : Any Platform : {} Direction : Outbound Action : Allow EdgeTraversalPolicy : Block LooseSourceMapping : False LocalOnlyMapping : False Owner : PrimaryStatus : OK Status : The rule was parsed successfully from the store. (65536) EnforcementStatus : NotApplicable PolicyStoreSource : PersistentStore PolicyStoreSourceType : Local ...
These new firewall cmdlets really make things easier, but if you don't have PowerShellv4 you can still use the old netsh command to add the firewall rules. Also, the Get-Netstat will support older version of PowerShell as well, so this is nicely backwards compatible. All we need to do is replace the command inside the ForEach-Object cmdlet's script block.
PS C:\> ... | ForEach-Object { netsh advfirewall firewall add rule name="AllowedByScript" dir=in action=allow protocol=$_.Protocol localport=$_.LocalPort }