🡠 Back to all articles

Blocking IP addresses from which failed logon attempts were made

Contents

  1. Problem: computer has many failed logon attempts from different addresses
  2. Blocking malicious IP addresses on firewall
  3. Checking whether IP-addresses were blocked and removing them from the firewall rule

Problem: computer has many failed logon attempts from different addresses

It is considered a bad practice to make RDP servers accessible from the internet. One of the reasons for that we can see by checking event log of a such server: often it contains a number of failed logon attempts made from different IP addresses:

Index TimeGenerated AccountName AccountDomain LogonType WorkstationName SourceNetworkAddress ----- ------------- ----------- ------------- --------- --------------- -------------------- 33900 2020-07-24 10:41:28 John.Doe SRV045 2 SRV045 127.0.0.1 33890 2020-07-24 10:36:05 Admin NB21 3 NB21 10.15.10.11 33880 2020-07-24 10:00:34 Guest SRV001 3 SRV001 - 31865 2020-07-24 09:17:39 Administrator NB18 3 NB18 10.15.10.70 31840 2020-07-24 09:17:39 AdminGG1 3 Server002 31833 2020-07-24 09:15:13 ADMINISTRATOR 3 - 172.217.20.14 31831 2020-07-24 09:13:14 ADMINISTRATOR 3 - 172.217.20.14 31830 2020-07-24 09:11:13 ADMINISTRATOR 3 - 172.217.20.14 31827 2020-07-24 09:10:14 GUEST WORKGROUP 3 AEC4CE70FFFD 74.6.231.20 31810 2020-07-24 09:09:35 Administrator WIN-FDQCT15JG8I 3 Windows7 98.137.11.163 31810 2020-07-24 09:09:35 UID002134 CONTOSO 3 WS003 10.15.10.11

This exerpt is only a small portion of what we can find on some servers: sometimes it comes to daunting tens of thousands malicious logon attempts per day that can even prevent legitimate users from logging on to the server.

Correct solution for this problem is to hide the server from direct access from the outside world. If that is not possible to do soon, as a temporary workaround we can allow connections only from certain trusted IP addresses. If it also is not an option, we can block connections only from those addresses, from which failed logon atempts have already been made. Let’s take a look on how the last variant can be implemented. Once again, this is a bad solution, but still it reduces risk of being brute forced and it’s better than nothing.

Blocking malicious IP addresses on firewall

We are going to use the following components:

  1. A firewall rule that blocks inbound connections from the IP addresses that have many failed logon attempts associated with them.
  2. A script that updates the blocked addresses list of the firewall rule. It retrieves malicious IP addresses from the event log entries (event 4625 — “failed logon attempt”) and adds them to the blocklist of the rule.
  3. A scheduled task that runs the script on some schedule or each time when “failed logon attempt” event occurs.

During the first runs of the task the script will block most of the malicious addresses (at least, the most “active” ones), and number of failed logon attempts will become not that big.

Firewall rule

Before starting, we need to be sure that it is possible to connect to the server with console or IPMI — for the case if we accidentally block remote access for ourselves while playing with firewall rules. After that is settled, we can create a firewall rule:

New-NetFirewallRule ` -DisplayName "Block IP addresses from which failed logon attempts were made" ` -Direction Inbound ` -Action Block ` -Profile Any ` -LocalPort 3389 ` -Protocol TCP ` -Program %SystemRoot%\system32\svchost.exe ` -Service TermService ` -RemoteAddress 142.250.180.206

It’s necessary to specify at least one IP address to be blocked, otherwise the rule will block connections from all addresses. To block only RDP connections we need to specify LocalPort, Protocol, Program and Service parameter values (as shown in the example); to block all connections from the malicious addresses we can omit these parameters — they will be assigned values of “Any”/“All”.

The script will add other malicious IP addresses to the blocklist of this rule.

Script

The script does the following:

  1. Get IP addresses from “failed logon attempt” events (event ID 4625) generated in the specified time period (for example, in last 3 days).
  2. Select from these IP addresses the ones to be blocked:
  3. Add selected IP addresses to the blocklist of the firewall rule.
# Name of the firewall rule $FirewallRuleDisplayName = "Block IP addresses from which failed logon attempts were made" # Get failed logon events that occurred in last 72 hours $Hours = 72 # We will block IP addresses with more than 3 logon attempts $FailedLogonAttemptsThreshold = 3 # Do not block the followig IP addresses $WhitelistedIPAddresses = @( "185.20.185.21" # John Doe — home "186.21.185.22" # Contoso office external address (1) "187.22.185.23" # Contoso office external address (2) ) # Do not block IP addresses that belong to the following networks $WhitelistedNetworks = @( "10.1.0.0/16" # Adatum office network — wired "10.2.0.0/16" # Adatum office network — Wi-Fi ) # Get IP addresses from "failed logon attempt" events (event ID 4625) $IPAddressesInEvents = Get-EventLog ` -LogName 'Security' ` -InstanceId 4625 ` -After (Get-Date).AddHours(-$Hours) ` | foreach { $_.ReplacementStrings[-2] } # Get rid of values that are not valid IP addresses (sometimes IP address contained # in the ReplacementStrings property of the event contains "-" instead of actual IP address) $IPAddressesInEvents = $IPAddressesInEvents | where { $_ -match "^(0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])(\.(0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])){3}$" } # Do not block whitelisted IP addresses $IPAddressesInEvents = $IPAddressesInEvents | where { $_ -NotIn $WhitelistedIPAddresses } # Do not block IP addresses that are in the whitelisted networks $IPAddressesInEvents = $IPAddressesInEvents | where { -not [Array]::Find( $WhitelistedNetworks, [System.Predicate[string]] { param ($WhitelistedNetwork) Test-IfIPAddressBelongsToNetwork -IPAddress $_ -Network $WhitelistedNetwork } ) } # Select addresses with number of failed logon attempts greater than threshold value [string[]] $IPAddressesToBeBlocked = $IPAddressesInEvents ` | group ` | where { $_.Count -gt $FailedLogonAttemptsThreshold } ` | select -ExpandProperty Name # Add addresses with failed logon attempts to the blocking firewall rule [string[]] $BlockedIPAddresses = Get-NetFirewallRule -DisplayName $FirewallRuleDisplayName ` | Get-NetFirewallAddressFilter ` | select -ExpandProperty RemoteAddress $BlockedIPAddresses += $IPAddressesToBeBlocked $BlockedIPAddresses = $BlockedIPAddresses | select -Unique Set-NetFirewallRule -DisplayName $FirewallRuleDisplayName -RemoteAddress $BlockedIPAddresses ########################################### # Functions ########################################### # Convert a string with an IP address to its integer representation. # Parameters: # IPAddressString — string with IP address, for example: "192.168.10.1". # Range of acceptable addresses: 0.0.0.0–255.255.255.255 # Returns: # Int32 with IP address if the string passed to it is a valid IP address function Convert-StringIPToIntIP { param ( [Parameter(Mandatory)] [ValidatePattern( "^(0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])(\.(0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])){3}$" )] [string] $IPAddressString ) $IPAddressString.Split(".") | foreach { $IPAddress = 0; $IPAddressByte = 0 } { [int]::TryParse($_, [ref] $IPAddressByte) | Out-Null $IPAddress = $IPAddress -shl 8 -bor $IPAddressByte } return $IPAddress } # Create Int32 with mask of the specified length. # Parameters: # MaskLength — length of the mask. Must be in range from 1 to 32 (inclusively) # Returns: # Int32 with mask of the specified length function Create-MaskInt { param ( [Parameter(Mandatory)] [ValidateRange(1, 32)] [int] $MaskLength ) return [int] "0xFFFFFFFF" -shl (32 - $MaskLength) } # Check whether IP address belongs to the specified network. # Parameters: # IPAddress — IP address to check, for example: "192.168.10.2" # Network — network address and mask, for example: "192.168.10.0/24" # Returns: # $true, if the address belongs to the specified network # $false, if the address does not belong to the specified network function Test-IfIPAddressBelongsToNetwork { param ( [Parameter(Mandatory)] [ValidatePattern( "^(0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])(\.(0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])){3}$" )] [string] $IPAddress ,[Parameter(Mandatory)] [ValidatePattern( "^(0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])(\.(0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])){3}/(\d|[1-2]\d?|3[0-2])$" )] [string] $Network ) $IPAddressInt = Convert-StringIPToIntIP -IPAddressString $IPAddress $NetworkAddressInt = Convert-StringIPToIntIP -IPAddressString $Network.Split("/")[0] $MaskInt = Create-MaskInt -MaskLength $Network.Split("/")[1] return ($IPAddressInt -band $MaskInt) -eq ($NetworkAddressInt -band $MaskInt) }

Let’s save this script on the server: C:\Scripts\Block connections from malicious IP addresses.ps1.

Scheduled task

We can schedule task to run each time the “failed logon attempt” (event ID 4625) occurs:

The scheduled task should execute the script:

powershell.exe -ExecutionPolicy Bypass -File "C:\Scripts\Block connections from malicious IP addresses.ps1"

Checking whether IP addresses are blocked and removing them from the firewall rule blocklist

Eventually the number of blocked IP addresses may grow big:

PS C:\> ((Get-NetFirewallRule -DisplayName "Block IP addresses from which failed logon attempts were made" ` >> | Get-NetFirewallAddressFilter).RemoteAddress).Length 8493

And among them there are some IPs of legitimate users that have failed to logon multiple times in a row and now they cannot connect to the server. We need to remove these addresses from the firewall rule blocklist. Doing it manually in GUI may be not the best idea, considering the number of blocked addresses. So this is how we can check whether the IP addresses are present in the firewall rule blocklist:

# Check whether the IP addresses are present in the firewall rule blocklist @( "100.110.120.140" "150.160.170.180" ) ` | foreach { $_ -in ( ` Get-NetFirewallAddressFilter -AssociatedNetFirewallRule ` (Get-NetFirewallRule -DisplayName "Block IP addresses from which failed logon attempts were made") ` ).RemoteAddress }

And this is how we can remove IP addresses from the blocklist:

# Remove IP address from firewall rule blocklist $AddressToRemove = "100.110.120.140" $BlockedIPAddresses = ( ` Get-NetFirewallAddressFilter -AssociatedNetFirewallRule ` (Get-NetFirewallRule -DisplayName "Block IP addresses from which failed logon attempts were made") ` ).RemoteAddress $BlockedIPAddressesList = [System.Collections.ArrayList]::new($BlockedIPAddresses) $BlockedIPAddressesList.Remove($AddressToRemove) Set-NetFirewallRule ` -DisplayName "Block IP addresses from which failed logon attempts were made" ` -RemoteAddress $BlockedIPAddressesList.ToArray()

We can save these snippets on our server and use them to unblock IP addresses of legitimate users:

PS C:\> & 'C:\Scripts\Check whether IP address is blocked.ps1' True False PS C:\> PS C:\> & 'C:\Scripts\Remove IP address from blocklist.ps1' PS C:\> PS C:\> & 'C:\Scripts\Check whether IP address is blocked.ps1' False False

To prevent blocking addresses in the future, we can add them to the whitelisted addresses list in the script ($WhitelistedIPAddresses variable).