Skip to main content

Automated firewall manager for multiple windows devices

Code can be found here.


For the upcoming CCDC regionals, I'll need to manage multiple different windows machines and servers. In the state competition, I had made a simple script that created some basic firewall rules mostly targetting Active Directory services. I then applied these to both the Windows server 2012 DC and the Windows server 2016 Hyper-V/Docker machine. The rules worked fine for both, especially considering that I decided to 'reduce my threat surface' by removing the net adapters from the Docker box. However, for regionals, I'll be managing multiple Active Directories, Exchange servers, and Docker at the very least. 

Having a rigid script that applies the same firewall rules to every box won't work as well as it did in state, and creating a new script for each machine is too time-consuming. So I decided to create something that could detect what services and roles were installed on a given machine and then install the necessary firewall rules based on what is detected. 

It's a relatively simple script, one that still needs a lot of fine-tuning and simplification, but currently, it supports AD, Exchange, DHCP, DNS, and 'Basic' servers. Basic here is a group of rules applied to all devices to allow for basic functions such as ports 80, 443, 53, etc. The other supported services and roles have a full suite of TCP and UDP rules, as well as range block rules blocking communications on unnecessary ports. Creating range block rules isn't the best design, but serves as an extra backup layer, and allows me to free a specific range of ports at a moment's notice. 


To scan the servers, I use a combination of service scanning and windows feature scanning. I also check to see if a given device is connected to a domain and if it needs to have AD communication ports open for authentication. To do this, I have the RoleCall function.


function RollCall(){
$RoleCheck =@("DNS","AD","DHCP")
#correlates to $role switch
foreach($x in $RoleCheck){if(Get-WindowsFeature | where Installed | %{out-string -InputObject $_.Name} | ?{$_ -match $x}){FirewallRoles($x);if($x -eq 'AD'){$AD=$true}}}
#Pass installed roles to firewall creator
if((get-service | select-object Name, Status | %{$_.Name -match 'MSExchangeServiceHost'}) -eq 'True'){FirewallRoles('Exchange')}
#If true, create exchange rules
if(-Not $AD){if((gwmi win32_computersystem).partofdomain -eq $true){FirewallRoles('AD')}}
#If the device is part of a domain, create the AD rules to allow communication
}


This is a simple function that utilizes the built-in get-service and get-windowsfeature commands to get a list of everything that is installed on the device. It then compares these installed services and roles to a pre-determined list of roles and services and passes the matches to the FirewallRoles function that actually creates the rules. To break it down:


foreach($x in $RoleCheck){if(Get-WindowsFeature | where Installed | %{out-string -InputObject $_.Name} | ?{$_ -match $x}){FirewallRoles($x);if($x -eq 'AD'){$AD=$true}}}


To break it down further, Get-WindowsFeature outputs a list of windows features, which include server roles installed through the server manager. This list is then filtered down with where Installed, which restricts the list to things that are actually installed on the device. However, this still outputs as an object rather than a string. So each entry on the list is passed as a windows object, which makes it difficult to compare it to a string. To make it easier to handle, we then pass the list of installed features to %{out-string -InputObject $_.Name}. This takes each Name value of the objects and turns it into a string that we can use. After converting the data to a readable format, we compare it to the list of roles we're looking for using ?{$_ -match $x}. This filters all of our string name list down to things that match what is in our RoleCheck array. After all of the filtering and comparing is done, we pass the successful values to the FirewallRoles function for rule creation. We also assign a true value to $AD if we found that the device is running an Active Directory. This allows us to skip some checks later, or to enable them depending on the value of $AD. 


if((get-service | select-object Name, Status | %{$_.Name -match 'MSExchangeServiceHost'}) -eq 'True'){FirewallRoles('Exchange')}

This line is for service checks. It's rather simple at the moment, but will eventually be fleshed out more as more services are added to the script. At the moment, it uses get-service to generate a list of all services, stopped or running, and then uses select-object Name, Status to list only their names and status. Following this, it checks each name against MSExchangeServiceHost, which exists in Exchange servers, to see if it needs to install exchange server rules. When you run %{$_.Name -match 'MSExchangeServiceHost'} the console outputs either a 'True' or 'False' string. Thus it is necessary to then compare the output value of the command with -eq 'True' to ensure that the service exists. If it does, it sends the Exchange value to FirewallRoles. Eventually, this will check against an array like the role-based scan does, but as Exchange is the only supported service currently, it only checks for that. To change it later on, the line will be wrapped in a foreach($x in $ServiceCheck){} and will look similar to RoleCheck. 


if(-Not $AD){if((gwmi win32_computersystem).partofdomain -eq $true){FirewallRoles('AD')}}
This line is a simple check to see if a device is connected to a domain, and thus needs to have AD authentication ports open. If(-Not $AD) uses the $AD variable that is assigned in the RoleCheck line to determine if the device is an AD server or not. If it isn't, then it will run (gwmi win32_computersystem).partofdomain -eq $true to determine if the device is joined to a domain. If it is, then it passes AD to FirewallRoles to create AD rules. This ensures that devices that do not have AD roles installed, will not be locked out of the domain by blocking necessary communication and authentication ports. 


The actual firewall creation function is rather bland. It uses a switch statement to check the values passed to it, and then creates the TCP and UDP rules necessary for each service. As an example, this is the AD rule creation process.


$AD=@('88','135','138','139','389','445','464','636','3268','3269')
switch($Role){
'AD'{foreach($x in $AD){New-NetFirewallrule -DisplayName "AD Port $x" -Direction Inbound -LocalPort $x -Protocol TCP -Action Allow
New-NetFirewallrule -DisplayName "AD Port $x (UDP)" -Direction Inbound -LocalPort $x -Protocol UDP -Action Allow
New-NetFirewallrule -DisplayName "AD Port $x" -Direction Outbound -LocalPort $x -Protocol TCP -Action Allow
New-NetFirewallrule -DisplayName "AD Port $x (UDP)" -Direction Outbound -LocalPort $x -Protocol UDP -Action Allow}}


For roles and services that don't need all ports open on both TCP and UDP, there are array names such as $DHCPUDP that are used for UDP rule creation. These have their own foreach() that creates the rules separately. 


There is also an initialization function that turns the firewall on and creates the basic rules and range blocks. This is set up the same as the role rule creation, where $BasicRules is passed through a foreach loop, and rules are created for each given value. The range blocks are created the same way. 


function FirewallInit(){
Set-NetFirewallProfile -Enabled True
#Enable firewall
(New-Object -ComObject HNetCfg.FwPolicy2).RestoreLocalFirewallDefaults()
#Reset to defaults
$BasicRules =@('53','80','443')
$BasicRulesUDP =@('53','123')
foreach($x in $BasicRules){New-NetFirewallrule -DisplayName "Basic Port $x" -Direction Outbound -LocalPort $x -Protocol TCP -Action Allow}
foreach($x in $BasicRulesUDP){New-NetFirewallrule -DisplayName "Basic Port $x (UDP)" -Direction Outbound -LocalPort $x -Protocol UDP -Action Allow
    New-NetFirewallrule -DisplayName "Basic Port $x (UDP)" -Direction Inbound -LocalPort $x -Protocol UDP -Action Allow}

We also restore the firewall to its defaults in case of tampering, something that happens frequently in the competition environment. The (New-Object -ComObject HNetCfg.FwPolicy2).RestoreLocalFirewallDefaults() line is what allows us to do a full reset. This can be tricky, however, on the off-chance that there are necessary services installed that are not caught by the script, which rely on certain ports remaining open. The reset will remove those rules and potentially interrupt those services. This can be mitigated by creating a backup of the firewall rules before running the script. A backup function was not included by default in the script because I don't believe it is necessary for most scenarios. For my typical use case, I don't need to worry about backing up rules. I typically use this script on fresh installs or devices where extraneous services are unnecessary and often unwanted. However, it is good practice to create a firewall back up with the original values if there is any concern about service uptime and connectivity. This can be done manually in the firewall manager, or through PowerShell with netsh advfirewall export "c:\advfirewallpolicy.wfw". Import is the same, netsh advfirewall import "c:advfirewallpolicy.wfw".


Comments

  1. I think your ideas are excellent, and I am very grateful for your sharing. I have benefited enormously from them.https://www.finewaterprint.com/

    ReplyDelete

Post a Comment

Popular posts from this blog

Using PGPy to encrypt and decrypt files and messages

 PGPy is a library for python that enables the creation, storage, and encryption/decryption of PGP keys and files in python. Recently, in a small project to reacquaint myself with python, I used PGPy for key generation and encryption and decryption. That project can be found in my github at  https://github.com/lpowell . The goal of the project was to use command-line switches to control the program, and to provide basic encryption and decryption capabilities, along with rot13 and base64 encoding.  First, to load in a key use key, _ = pgpy.PGPKey.from_file(keyfilename) . This loads the key from either a binary or ASCII armored file. You can swap out .from_file for .from_blob , if you plan on using a key stored in a string or bytes object rather than a file. In my example code, I pull the key from a file, as I found it to be the simpler method.  Next, you'll need to open a file or create a string or bytes object that contains the message you wish to encrypt. We'll cal...

Using the Ubertooth One to sniff and intercept Bluetooth packets

While researching for my individual video project I came across this tool which allows for the sniffing and interception of bluetooth packets. This article covers some of the basic functionality of an Ubertooth One.  It's really quite interesting to see all the possibilities with devices like these. The tech behind them is very interesting as well. Hopefully, I'll be able to integrate some of this technology into my project video and include a demo of some of the interesting things it can do.

Huntress CTF Challenge Writeups: HumanTwo: MoveIt IoC Analysis Challenge

HumanTwo: MoveIT IoC Analysis Challenge The HumanTwo challenge is a malware CTF from the 2023 Huntress CTF. This write-up walks through the initial discovery, de-obfuscation, and solving of the challenge. The actual flag will be redacted from the document, but interested parties should be able to follow the steps and derive it themselves. While the write-up assumes a base level of knowledge regarding the command line and Linux. Most tools and commands will be accompanied by short explanations. Step 1: Initial Analysis To start off, we are given an archive with 1000 files named after their file hash. The hint we are given is that there are minor differences between each file. We also know that HumanTwo relates to the MoveIT vulnerability and exploit. The easy way to progress is to look up articles that tell you about the vulnerability and what stands out in each exploit script. However, I didn’t do that, so I’ll put the process I followed down instead. First, because I knew that...