49

Is it possible to ban an IP address after X number of unsuccessful login attempts to a Windows Server? Not to a particular account, which I know how to do, but to the whole machine.

We get hit pretty hard by brute force attacks trying to guess usernames, so this would really help get some load off the server.

HopelessN00b
  • 53,385
  • 32
  • 133
  • 208
HeavyWave
  • 745
  • 1
  • 6
  • 9
  • 8
    *nix has fial2ban... not sure if there's a Windows equivalent/port. http://www.fail2ban.org/wiki/index.php/Main_Page – Chris Nava Feb 11 '11 at 19:06
  • 5
    From Evan Anderson: http://serverfault.com/questions/43360/cygwin-sshd-autoblock-failed-logins/43900#43900 ...looks to be a good equivalent of fail2ban's functionality but as your question is not specific enough, I don't know if you're looking to ban IPs attempting to login to a hosted website, your server (via SSH) or your domain. Clarification would go a long way. Additionally you could rate limit at your firewall, but that is implementation-dependent. –  Feb 11 '11 at 19:35
  • 4
    You might want to have a look at http://serverfault.com/questions/216995/is-it-worth-the-effort-to-block-failed-login-attempts/217066#217066 for a previous discussion about how useful automated banning based on IP is. – pehrs Feb 13 '11 at 18:16
  • 1
    If you're talking about Terminal Services / Remote Desktop have a look here: http://serverfault.com/a/335976/7200 – Evan Anderson Nov 29 '11 at 23:57
  • 3
    I made a windows service on github to do just that: https://github.com/jjxtra/Windows-IP-Ban-Service – jjxtra Feb 20 '12 at 18:06
  • If you get "hit hard" by these, would this end up effectively perpetually locking you out of your own server? – Joel Coel Dec 04 '12 at 22:41
  • @HeavyWave - were you getting hit via RDP, FTP, or something else? If it's RDP and/or FTP, I posted a Powershell script [below][http://serverfault.com/a/571903/107701] that will automatically block IPs after X attempts. – kevinmicke Feb 26 '14 at 21:08

13 Answers13

27

You can do this with powershell and task manager. It's probably not perfect solution, but it works quite well and i have about 100 blocked IP addresses in two months. I wrote script, that select from EventLog specified events ("audit failure"). If there are many failed logins from any IP address, then it's added to firewall rule (created manually) named "BlockAttackers" which blocks any traffic to specified ip addresses.

PS1 Script:

$DT = [DateTime]::Now.AddDays(-1) # check only last 24 hours

$l = Get-EventLog -LogName 'Security' -InstanceId 4625 -After $DT | Select-Object @{n='IpAddress';e={$_.ReplacementStrings[-2]} } # select Ip addresses that has audit failure 
$g = $l | group-object -property IpAddress  | where {$_.Count -gt 20} | Select -property Name # get ip adresses, that have more than 20 wrong logins

$fw = New-Object -ComObject hnetcfg.fwpolicy2 # get firewall object

$ar = $fw.rules | where {$_.name -eq 'BlockAttackers'} # get firewall rule named 'BlockAttackers' (must be created manually)

$arRemote = $ar.RemoteAddresses -split(',') #split the existing IPs into an array so we can easily search for existing IPs

$w = $g | where {$_.Name.Length -gt 1 -and  !($arRemote -contains $_.Name + '/255.255.255.255') } # get ip addresses that are not already in firewal rule. Include the subnet mask which is automatically added to the firewall remote IP declaration.

$w| %{$ar.remoteaddresses += ',' + $_.Name} # add IPs to firewall rule

Create task in scheduler and set trigger to event 4625 (windows login including terminal services). But you can set trigger to run e.g. twice per hour to avoid unnecessary loading the server.

Scheduler trigger

and after trigger run powershell script. You must also set higher privileges to run this script, otherwise it will fail with security exception.

runing powershell script

You can also bind this script to other security events.

remunda
  • 410
  • 8
  • 11
  • 1
    Excellent script @remunda - thanks! I was getting a lot of 4625s from FTP as well, which the Security log doesn't have IP addresses for, so I expanded upon your script to have it check the current day's FTP log as well. Please see my answer below for more info: http://serverfault.com/a/571903/107701 – kevinmicke Mar 04 '14 at 17:17
  • There are a lot of gotchas and edge cases with the event logs, ip address logging, etc. that I've handled in IPBan - free and open source at https://github.com/jjxtra/Windows-IP-Ban-Service – jjxtra Dec 25 '17 at 15:19
7

I know this question is old but it was actually the first forum post I stumbled across when I started trying to do this exact same thing a couple weeks ago. I've managed to come up with a working script that will parse the event logs 24 hours back for only bad login event log entries, grab the ones that have more than 10 bad logins, and then put them into an ipsec filter list using the netsh command. Then I wrote a batch file with this line powershell .\*scriptname.ps1* and created a scheduled task to run the batch file every 24 hours (for some reason it wouldn't execute directly).

$DATE = [DateTime]::Now.AddDays(-1)

$EVS = Get-EventLog Security -InstanceId 529 -after $DATE

$EVS | select-string -inputobject {$_.message} -pattern "Source Network Address:(.)*\.*\.*\.*"  -allmatches | foreach-object {$_.Matches} | foreach-object {$_.Value} | foreach-object {$_.replace("Source Network Address:", "")} | group-object -property $_ | where-object {$_.count -gt 10} | select-object -property name | format-list | out-file c:\rdpblock.txt 

get-content -path c:\rdpblock.txt | foreach-object {$_.replace("Name :", "")} | out-file c:\rdpblockcleaned.txt 

get-content -path c:\rdpblockcleaned.txt | select-object -unique | out-file c:\rdpblocknospaces.txt

$RDPIP = get-content -path c:\rdpblocknospaces.txt | select-object -skip 1

$RDPIP | foreach-object {$_.replace("     ", "")} | foreach-object {netsh ipsec static add filter filterlist=RDP_BLOCK srcaddr=$($_) dstaddr=any}

I know that this script is probably inefficient but when I started working on this I had absolutely no experience in powershell, so my ability to optimize scripts leaves alot to be desired. However, despite this fact I thought I would share this with anyone who could use it.

I thank Remunda for giving me the initial idea, that poster is the one that turned me on to the idea of using powershell to search the event logs.

mrlizard
  • 71
  • 1
  • 1
4

This script builds on remunda's answer and goes a little further https://serverfault.com/a/397637/155102 It accounts for the "BlockAttackers" rule not have any IPs entered yet (which returns a "*" as a string). It also writes a comment to a log file to let you know when the IP was added to the rule.

A good tip is to create the "BlockAttackers" rule that blocks the IP addresses BUT make it disabled at first. Then, run this script once manually so it can populate the "RemoteAddresses" field with actual IP addresses that should be blocked. Take a look at those IP addresses to make sure nothing critical has been added and then enable the firewall rule. Add this rule to your firewall as remunda described.

The git for this script

#Checks for IP addresses that used incorrect password more than 10 times
#within 24 hours and blocks them using a firewall rule 'BlockAttackers'

#Check only last 24 hours
$DT = [DateTime]::Now.AddHours(-24)

#Select Ip addresses that has audit failure
$l = Get-EventLog -LogName 'Security' -InstanceId 4625 -After $DT | Select-Object @{n='IpAddress';e={$_.ReplacementStrings[-2]} }

#Get ip adresses, that have more than 10 wrong logins
$g = $l | group-object -property IpAddress | where {$_.Count -gt 10} | Select -property Name

#Get firewall object
$fw = New-Object -ComObject hnetcfg.fwpolicy2

#Get firewall rule named 'BlockAttackers' (must be created manually)
$ar = $fw.rules | where {$_.name -eq 'BlockAttackers'}

#Split the existing IPs into an array so we can search it for existing IPs
$arRemote = $ar.RemoteAddresses -split(',')

#Only collect IPs that aren't already in the firewall rule
$w = $g | where {$_.Name.Length -gt 1 -and !($arRemote -contains $_.Name + '/255.255.255.255') }

#Add the new IPs to firewall rule
$w| %{
  if ($ar.RemoteAddresses -eq '*') {
    $ar.remoteaddresses = $_.Name
  }else{
    $ar.remoteaddresses += ',' + $_.Name
  }
}

#Write to logfile
if ($w.length -gt 1) {
  $w| %{(Get-Date).ToString() + ' ' + $_.Name >> '.\blocked.txt'}
}
3

I can't take credit for this answer, but https://serverfault.com/users/7200/evan-anderson has mentioned his project http://opensource.wellbury.com/projects/windows_sshd_block/newest-release/

becomingwisest
  • 3,278
  • 19
  • 17
2

It's generally not a good idea to let somebody else control your firewall rules. That's basically what you're asking for here.

Thorsten
  • 158
  • 5
  • 1
    +1, this is an excellent way to set yourself up for a denial of service attack. And if you're using rate-limiting, most automated brute force tools space their login attempts far enough apart to avoid getting caught. –  Feb 11 '11 at 19:40
  • 12
    Automatically banning IPs after a certain number of failed logins is *very* common practice. I see hosts being banned on an hourly basis after trying to guess FTP passwords. The only way this can be a DoS attack is if someone managed to spoof your IP (impossible on TCP connections), or if you repeatedly mistype your password (in which case it's not someone else controlling firewall rules, it's you) – devicenull Feb 12 '11 at 02:35
  • 18
    Sorry, but I didn't ask if it was a good idea. – HeavyWave Feb 13 '11 at 02:24
  • 1
    Of course there is no reason exceptions couldn't be set for one or more specific IP addresses, which would pretty much eliminate the DoS concern. – John Gardeniers Feb 15 '11 at 01:56
2

This is an old thread. I was using the script provided by kevinmicke in 2014-2015. Then it just stopped working. So I had to edit it a bit to adopt to Windows Network Security authentication that does not leave IP addresses in the security log. Also, since I don't have regular FTP running I removed that part as it was causing errors because there was no log folder. The main change is in the source of RDP events.

    $current_date_utc = (Get-Date).ToUniversalTime()

    # Set number of failed login attempts after which an IP address will be blocked
    $int_block_limit = 10

    # Time window during which to check the Security log, which is currently set to check only the last 24 hours
    $dat_time_window = [DateTime]::Now.AddDays(-1)

    $arr_new_bad_ips_all = (get-winevent -filterhashtable @{ logname='Microsoft-Windows-RemoteDesktopServices-RdpCoreTS/Operational'; starttime=$dat_time_window; id=140 }).message |
        % { if ($_ -match "of (.+) failed") { $Matches[1] }} |
        Group-Object |
        Where {$_.Count -ge $int_block_limit} |
        Select -property Name

    # Sort the array, selecting only unique IPs (in case one IP shows up in both the Security and FTP logs)
    $arr_new_bad_ips_all = $arr_new_bad_ips_all | Foreach-Object { [string]$_.Name } | Select-Object -unique

    # Get firewall object
    $firewall = New-Object -comobject hnetcfg.fwpolicy2

    # Get all firewall rules matching "BlockAttackers*"
    $arr_firewall_rules = $firewall.Rules | Where {$_.Name -like 'BlockAttackers*'}

    # If no "BlockAttackers*" firewall rule exists yet, create one and set it to a variable
    if ($arr_firewall_rules -eq $null) {
        $str_new_rule_name = "BlockAttackers (Created " + $current_date_utc.ToString("yyyy-MM-dd HH:mm:ss") + " UTC)"
        netsh advfirewall firewall add rule dir=in action=block name=$str_new_rule_name description="Rule automatically created." enable=yes remoteip="0.0.0.0" | Out-Null
        $arr_firewall_rules = $firewall.Rules | Where {$_.Name -like 'BlockAttackers*'}
    }

    # Split the existing IPs from current "BlockAttackers*" firewall rule(s) into an array so we can easily search them
    $arr_existing_bad_ips = @()
    foreach ($rule in $arr_firewall_rules) {
        $arr_existing_bad_ips += $rule.RemoteAddresses -split(',')
    }

    # Clean subnet masks off of IPs that are currently blocked by the firewall rule(s)
    $arr_existing_bad_ips_without_masks = $arr_existing_bad_ips | ForEach-Object {$_ -replace "/.*", ""}

    # Select IP addresses to add to the firewall, but only ones that...
    $arr_new_bad_ips_for_firewall = $arr_new_bad_ips_all | Where {
        # contain an IP address (i.e. aren't blank or a dash, which the Security log has for systems that failed FTP logins)
        $_.Length -gt 6 -and
        # aren't already in the firewall rule(s)
        !($arr_existing_bad_ips_without_masks -contains $_) -and
        # aren't the local loopback
        !($_.StartsWith('127.0.0.1')) -and
        # aren't part of the local subnet
        !($_.StartsWith('192.168.')) -and
        !($_.StartsWith('0.0.'))
    }

    # If there are IPs to block, do the following...
    if ($arr_new_bad_ips_for_firewall -ne $null) {
        # Write date and time to script-specific log file
        [DateTime]::Now | Out-File -Append -Encoding utf8 C:\Security\blockattackers.txt
        # Write newly-blocked IP addresses to log file
        $arr_new_bad_ips_for_firewall | Out-File -Append -Encoding utf8 C:\Security\blockattackers.txt

        # Boolean to make sure the new IPs are only added on one rule
        $bln_added_to_rule = 0

        # Array to hold bad IPs from each rule one at a time, so we can count to make sure adding the new ones won't exceed 1000 IPs
        $arr_existing_bad_ips_current_rule = @()

        # For each "BlockAttackers*" rule in the firewall, do the following...
        foreach ($rule in $arr_firewall_rules) {
            if ($bln_added_to_rule -ne 1) {
                # Split the existing IPs from the current rule into an array so we can easily count them
                $arr_existing_bad_ips_current_rule = $rule.RemoteAddresses -split(',')

                # If the number of IPs to add is less than 1000 minus the current number of IPs in the rule, add them to this rule
                if ($arr_new_bad_ips_for_firewall.Count -le (1000 - $arr_existing_bad_ips_current_rule.Count)) {
                    # Add new IPs to firewall rule
                    $arr_new_bad_ips_for_firewall | %{$rule.RemoteAddresses += ',' + $_}

                    # Write which rule the IPs were added to to log file
                    echo "New IP addresses above added to Windows Firewall rule:" $rule.Name | Out-File -Append -Encoding utf8 C:\Security\blockattackers.txt

                    # Set boolean so any other rules are skipped when adding IPs
                    $bln_added_to_rule = 1
                }
            }
        }

        # If there wasn't room in any other "BlockAttackers*" firewall rule, create a new one and add the IPs to it
        if ($bln_added_to_rule -ne 1) {
            $str_new_rule_name = "BlockAttackers (Created " + $current_date_utc.ToString("yyyy-MM-dd HH:mm:ss") + " UTC)"
            netsh advfirewall firewall add rule dir=in action=block name=$str_new_rule_name description="Rule automatically created." enable=yes remoteip="0.0.0.0" | Out-Null
            $new_rule = $firewall.rules | Where {$_.Name -eq $str_new_rule_name}

            # Add new IPs to firewall rule
            $arr_new_bad_ips_for_firewall | %{$new_rule.RemoteAddresses += ',' + $_}

            # Write which rule the IPs were added to to log file
            echo "New IP addresses above added to newly created Windows Firewall rule:" $new_rule.Name | Out-File -Append -Encoding utf8 C:\Security\blockattackers.txt
        }
    }

The above script will work on Windows 2012. If you are still using Remote Desktop with network access level authentication on Windows 2008 then you might need to do the following trick. Windows 2008 does not have IP addresses in the security log and does not seem to have them in the Microsoft-Windows-RemoteDesktopServices-RdpCoreTS log either. So I had to actually use 2 logs - match events from the security log to successful access attempts to port 3389 in the firewall log. This is a guess work, but it seems to be detecting password attacks. Here is the part that collects violating IPs:

    $current_date_utc = (Get-Date).ToUniversalTime()

    # Set number of failed login attempts after which an IP address will be blocked
    $int_block_limit = 10

    $dat_time_window = [DateTime]::Now.AddDays(-1)

    $logfn = (netsh advfirewall show allprofiles | Select-String Filename | select-object -unique | % { $_ -replace "%systemroot%",$env:systemroot }).substring(10).trimstart().trimend()

    $badevts = Get-EventLog -LogName 'Security' -InstanceId 4625 -After $dat_time_window | foreach-object { [datetime]$_.TimeWritten } | sort-object

    $fwlog = Select-String -Path $logfn -Pattern "ALLOW TCP" |
        % {
            if ($_ -match "(201.-..-..) (.+) ALLOW TCP (.+) (.+) (.+) 3389") 
            {
                new-object psobject -property @{ 
                  dt = $Matches[1] + ' ' + $Matches[2]
                  ip = $Matches[3]
                }
            }
        }

    $ipa = @()
    $j = 0

    for ($i=0; $i -lt $fwlog.Count; $i++)
    {
        $conn = ([datetime]$fwlog[$i].dt).ticks
        while (($j -lt $badevts.Count) -and (($badevts[$j]).ticks -lt $conn)) { $j++ }
        if ($j -ge $badevts.Count) { break }
        if ((($badevts[$j]).ticks - $conn) -le 30000000) { $ipa += ,($fwlog[$i].ip) }
    }

    $arr_new_bad_ips_all = $ipa |
        Group-Object |
        Where {$_.Count -ge $int_block_limit} |
        Select -property Name

NOTE: Don't forget to enable firewall logs. NOTE 2: I am not a powershell expert so it would be nice if some gurus can correct/improve my code.

Calm Down
  • 21
  • 2
1

Using remunda's great script as a starting point, I added the one major thing that was missing: blocking IP addresses from failed FTP logins. Windows Server doesn't log the IP address to the Security log when someone fails to login via FTP, but instead sets the "Source Network Address" to a dash. FTP is a very common attack vector for brute force attacks, so I added to his script the ability to scan the current day's FTP logs for multiple login failures and block those IP addresses as well.

Update 2014/02/07: When I made some tweaks to this to process all my old FTP logs, I realized when they had immense numbers of attempts (50,000+), the arrays it created would be huge and make the processing incredibly slow. I've since rewritten it to make it much more efficient when processing FTP logs.

I also found out that there's an arbitrary hard limit of 1000 for how many IPs can be in one Windows Firewall rule. Because of that limit, I needed it to automatically create a new rule when the latest one fills up. It now does that, and also creates the initial firewall rule (if you don't create your own) so that the only setup to do is adding it to the Scheduler to run when there's an event 4625.

Here's the code, which has been tested on both Windows Server 2008 R2 and Windows 7:

# This Windows Powershell script will automatically block IP addresses that attempt to login to the system
# and fail the number of times set below with the $int_block_limit variable or more. Is scans both the Security
# log, which covers Remote Desktop and other attempts, as well as the current day's FTP log. If the $int_block_limit
# limit is hit on either of those logs (separately, not combined), then the IP address will be added to the
# firewall rule.
#
# The script will automatically create a firewall rule named "BlockAttackers (Created yyyy-MM-dd HH:mm:ss UTC)" using
# the current time if one with a name that includes "BlockAttackers" doesn't already exist. Because there's a hard
# limit of 1000 entries (IP addresses) you can block per rule, it will also create similarly-named rules once that
# limit is reached for the latest one.
#
# I recommend setting the script to run as a scheduled task triggered by event 4625 login audit failures from the
# Security log, or alternatively you could set it to run after some amount of time (i.e. every 10 minutes).
#
# Authors:
# Majority of script written by serverfault.com user kevinmicke
# Windows Security Log portion written by serverfault.com user remunda, which provided the starting point for kevinmicke
#
# Details: https://serverfault.com/questions/233222/ban-ip-address-based-on-x-number-of-unsuccessful-login-attempts


# Set number of failed login attempts after which an IP address will be blocked
$int_block_limit = 10

# Time window during which to check the Security log, which is currently set to check only the last 24 hours
$dat_time_window = [DateTime]::Now.AddDays(-1)

# Select from the Security log all IP addresses that have more than $int_block_limit audit failures (event 4625) within $dat_time_window
$arr_new_bad_ips_security_log = @()
$arr_new_bad_ips_security_log = Get-EventLog -LogName 'Security' -InstanceId 4625 -After $dat_time_window |
    Select-Object @{n='IpAddress';e={$_.ReplacementStrings[-2]}} |
    Group-Object -property IpAddress |
    Where {$_.Count -ge $int_block_limit} |
    Select -property Name

# Get current time UTC to figure out filename for current FTP log
$current_date_utc = (Get-Date).ToUniversalTime()

# Set path to today's FTP log file
$str_log_file_name = "C:\inetpub\logs\LogFiles\FTPSVC2\u_ex" + $current_date_utc.ToString("yyMMdd") + ".log"

# Search today's FTP log file for "530 1326" to find lines that contain IPs of systems that failed to log in,
# get just the IP from each line, group the IPs by IP to count the attempts from each one, and select only the
# IPs that have $int_block_limit or more bad logins today
$arr_new_bad_ips_ftp = @()
$arr_new_bad_ips_ftp = Select-String $str_log_file_name -pattern "530 1326" |
    ForEach-Object {$_.Line.Substring(20,15) -replace " .*", ""} |
    Group |
    Where {$_.Count -ge $int_block_limit} |
    Select -property Name

# Concatenate the two arrays of IPs (one from Security log, one from FTP log)
$arr_new_bad_ips_all = @()
# $arr_new_bad_ips_all = @($arr_new_bad_ips_security_log) + @($arr_new_bad_ips_ftp_over_limit)
$arr_new_bad_ips_all = @($arr_new_bad_ips_security_log) + @($arr_new_bad_ips_ftp)

# Sort the array, selecting only unique IPs (in case one IP shows up in both the Security and FTP logs)
$arr_new_bad_ips_all_sorted = @()
$arr_new_bad_ips_all_sorted = $arr_new_bad_ips_all |
    Foreach-Object { [string]$_.Name } |
    Select-Object -unique

# Get firewall object
$firewall = New-Object -comobject hnetcfg.fwpolicy2

# Get all firewall rules matching "BlockAttackers*"
$arr_firewall_rules = $firewall.Rules | Where {$_.Name -like 'BlockAttackers*'}

# If no "BlockAttackers*" firewall rule exists yet, create one and set it to a variable
if ($arr_firewall_rules -eq $null) {
    $str_new_rule_name = "BlockAttackers (Created " + $current_date_utc.ToString("yyyy-MM-dd HH:mm:ss") + " UTC)"
    netsh advfirewall firewall add rule dir=in action=block name=$str_new_rule_name description="Rule automatically created by BlockAttackers Powershell script written by Kevin Micke." enable=yes remoteip="0.0.0.0" | Out-Null
    $arr_firewall_rules = $firewall.Rules | Where {$_.Name -like 'BlockAttackers*'}
}

# Split the existing IPs from current "BlockAttackers*" firewall rule(s) into an array so we can easily search them
$arr_existing_bad_ips = @()
foreach ($rule in $arr_firewall_rules) {
    $arr_existing_bad_ips += $rule.RemoteAddresses -split(',')
}

# Clean subnet masks off of IPs that are currently blocked by the firewall rule(s)
$arr_existing_bad_ips_without_masks = @()
$arr_existing_bad_ips_without_masks = $arr_existing_bad_ips | ForEach-Object {$_ -replace "/.*", ""}

# Select IP addresses to add to the firewall, but only ones that...
$arr_new_bad_ips_for_firewall = @()
$arr_new_bad_ips_for_firewall = $arr_new_bad_ips_all_sorted | Where {
    # contain an IP address (i.e. aren't blank or a dash, which the Security log has for systems that failed FTP logins)
    $_.Length -gt 6 -and
    # aren't already in the firewall rule(s)
    !($arr_existing_bad_ips_without_masks -contains $_) -and
    # aren't the local loopback
    !($_.StartsWith('127.0.0.1')) -and
    # aren't part of the local subnet
    !($_.StartsWith('192.168.')) -and
    !($_.StartsWith('10.0.'))
}

# If there are IPs to block, do the following...
if ($arr_new_bad_ips_for_firewall -ne $null) {
    # Write date and time to script-specific log file
    [DateTime]::Now | Out-File -Append -Encoding utf8 C:\blockattackers.txt
    # Write newly-blocked IP addresses to log file
    $arr_new_bad_ips_for_firewall | Out-File -Append -Encoding utf8 C:\blockattackers.txt

    # Boolean to make sure the new IPs are only added on one rule
    $bln_added_to_rule = 0

    # Array to hold bad IPs from each rule one at a time, so we can count to make sure adding the new ones won't exceed 1000 IPs
    $arr_existing_bad_ips_current_rule = @()

    # For each "BlockAttackers*" rule in the firewall, do the following...
    foreach ($rule in $arr_firewall_rules) {
        if ($bln_added_to_rule -ne 1) {
            # Split the existing IPs from the current rule into an array so we can easily count them
            $arr_existing_bad_ips_current_rule = $rule.RemoteAddresses -split(',')

            # If the number of IPs to add is less than 1000 minus the current number of IPs in the rule, add them to this rule
            if ($arr_new_bad_ips_for_firewall.Count -le (1000 - $arr_existing_bad_ips_current_rule.Count)) {
                # Add new IPs to firewall rule
                $arr_new_bad_ips_for_firewall | %{$rule.RemoteAddresses += ',' + $_}

                # Write which rule the IPs were added to to log file
                echo "New IP addresses above added to Windows Firewall rule:" $rule.Name | Out-File -Append -Encoding utf8 C:\blockattackers.txt

                # Set boolean so any other rules are skipped when adding IPs
                $bln_added_to_rule = 1
            }
        }
    }

    # If there wasn't room in any other "BlockAttackers*" firewall rule, create a new one and add the IPs to it
    if ($bln_added_to_rule -ne 1) {
        $str_new_rule_name = "BlockAttackers (Created " + $current_date_utc.ToString("yyyy-MM-dd HH:mm:ss") + " UTC)"
        netsh advfirewall firewall add rule dir=in action=block name=$str_new_rule_name description="Rule automatically created by BlockAttackers Powershell script written by Kevin Micke." enable=yes remoteip="0.0.0.0" | Out-Null
        $new_rule = $firewall.rules | Where {$_.Name -eq $str_new_rule_name}

        # Add new IPs to firewall rule
        $arr_new_bad_ips_for_firewall | %{$new_rule.RemoteAddresses += ',' + $_}

        # Write which rule the IPs were added to to log file
        echo "New IP addresses above added to newly created Windows Firewall rule:" $new_rule.Name | Out-File -Append -Encoding utf8 C:\blockattackers.txt
    }
}
kevinmicke
  • 411
  • 1
  • 4
  • 16
  • FYI: For those that haven't run a powershell script on a system before, you'll need to first open a new powershell and run `Set-ExecutionPolicy RemoteSigned` so you can run local scripts. Otherwise you'll get an error: "blockattackers.ps1 cannot be loaded because the execution of scripts is disabled on this system." – kevinmicke Feb 17 '14 at 18:27
1

I'm using ts_block freeby.

Basically it's a "VBScript program that acts as a WMI event sink to receive events logged by Windows in response to invalid Terminal Services logons."

Seems to work perfectly, and the script is straightforward if you need to mod it. You can either let it log attempts and then ban based on your number of allowed attempts, and/or you can hard-code login names you don't want to give access to.

I got caught out by accidentally adding the same name twice and the service just goes into an endless loop restarting every 1500ms, but very easy to fix/mod if you're ok with vbs.

My current settings are just one retry and you're banned for 2 days, with logins like 'admin' 'Admin' 'Administrator' 'guest' etc automatically banned. Should be straightforward to change to ip?

Kinda addictive to go in and see which critters have been banned overnight...

chipbug
  • 11
  • 1
0

Do you mean logging on to the server/domain or logging on to a web site running on the server? If you mean logging on to the server/domain then the answer is no. Windows has no concept of blocking ip addresses based on failed logon attempts as ip addresses aren't security entities. There may be third party tools that can do this, but I'm not aware of any as I've never looked in to it.

joeqwerty
  • 108,377
  • 6
  • 80
  • 171
0

If there is a webserver that is being attacked you can install the dynamic IP restrictions extension. If this is for standard authentication to the server then you should be able to implement domain and server isolation which would limit the scope of the attacks to domain joined computers, and could be set to only allow attempts from the systems you need to have access to the server. In windows the prevention of brute force attacks is to set the account lockout policy to a setting like 10 mins and a bad password policy to 3 attempts - this means that the account being attacked would lock for 10 mins after 3 attempts. IP connections are not lockable by default in windows. ( As an aside I am also curious as to how many logon attempts it is taking per second to impact the system)

Jim B
  • 23,938
  • 4
  • 35
  • 58
  • On my AWS small instance, 1 attempt every 4 seconds is enough to consume 50% CPU. Pretty crap if you ask me... – RomanSt Mar 02 '12 at 22:34
  • Wow, I wonder why the utilization is so high. I'll have to do some testing to see how many attempts it takes on my VMs to register. – Jim B Mar 04 '12 at 01:57
  • I think it’s because I’m not using Network Level Authentication, so every login attempt spins up a logon UI to actually present to the attacker through a remote desktop session. I can see why that might be expensive. – RomanSt Mar 04 '12 at 14:46
0

http://nerderies.blogspot.co.at/2012/12/automatically-banning-ips-with-windows.html

If what you want is an out of the box solution (Install & done) you can find a free tool here, and probably should go on reading this:

Current Version: 1.2 (.NET Framework 4.0 Client Profile) ->Download the current version of EvlWatcher (free for personal and commercial use)

New in 1.2 (more info in the documentation):

  • Management Console
  • WCF Service Pattern
  • Blacklisting
  • Auto-moving to the blacklist after 3 strikes (per default)

For older servers (.NET Framework 2.0)

->Download the reduced version of EvlWatcher (free for personal and commercial use)

Mastro
  • 111
  • 3
0

The script of remuda, edited by kevinmicke (Feb 7 at 21:59) didn't check the FTP's control channel, which has an own folder on my system (Windows Server 2008 R2). Also 530 11001-events haven't been recognized, which seem to appear when the hacker only tries to access to the control channel. So I appended some lines in the script to check a second FTP-log-folder:

# This Windows Powershell script will automatically block IP addresses that attempt to login to the system
# and fail the number of times set below with the $int_block_limit variable or more. Is scans both the Security
# log, which covers Remote Desktop and other attempts, as well as the current day's FTP log. If the $int_block_limit
# limit is hit on either of those logs (separately, not combined), then the IP address will be added to the
# firewall rule.
#
# The script will automatically create a firewall rule named "BlockAttackers (Created yyyy-MM-dd HH:mm:ss UTC)" using
# the current time if one with a name that includes "BlockAttackers" doesn't already exist. Because there's a hard
# limit of 1000 entries (IP addresses) you can block per rule, it will also create similarly-named rules once that
# limit is reached for the latest one.
#
# I recommend setting the script to run as a scheduled task triggered by event 4625 login audit failures from the
# Security log, or alternatively you could set it to run after some amount of time (i.e. every 10 minutes).
#
# Authors:
# Majority of script written by serverfault.com user kevinmicke
# Windows Security Log portion written by serverfault.com user remunda, which provided the starting point for kevinmicke
# Checking of the FTP's control channel added by serverfault.com user Uwe Martens
#
# Details: https://serverfault.com/questions/233222/ban-ip-address-based-on-x-number-of-unsuccessful-login-attempts


# Set number of failed login attempts after which an IP address will be blocked
$int_block_limit = 3

# Time window during which to check the Security log, which is currently set to check only the last 24 hours
$dat_time_window = [DateTime]::Now.AddDays(-1)

# Select from the Security log all IP addresses that have more than $int_block_limit audit failures (event 4625) within $dat_time_window
$arr_new_bad_ips_security_log = @()
$arr_new_bad_ips_security_log = Get-EventLog -LogName 'Security' -InstanceId 4625 -After $dat_time_window |
    Select-Object @{n='IpAddress';e={$_.ReplacementStrings[-2]}} |
    Group-Object -property IpAddress |
    Where {$_.Count -ge $int_block_limit} |
    Select -property Name

# Get current time UTC to figure out filename for current FTP log
$current_date_utc = (Get-Date).ToUniversalTime()

# Set path to today's FTP Control Channel log file
$str_log_file_name_control_channel = "C:\inetpub\logs\LogFiles\FTPSVC\u_ex" + $current_date_utc.ToString("yyMMdd") + ".log"

# Search today's FTP Control Channel log file for "530 1" to find lines that contain IPs of systems that failed to log in,
# get just the IP from each line, group the IPs by IP to count the attempts from each one, and select only the
# IPs that have $int_block_limit or more bad logins today
$arr_new_bad_ips_ftp_control_channel = @()
$arr_new_bad_ips_ftp_control_channel = Select-String $str_log_file_name_control_channel -pattern "530 1" |
    ForEach-Object {$_.Line.Substring(20,15) -replace " .*", ""} |
    Group |
    Where {$_.Count -ge $int_block_limit} |
    Select -property Name

# Set path to today's FTP log file
$str_log_file_name = "C:\inetpub\logs\LogFiles\FTPSVC*\u_ex" + $current_date_utc.ToString("yyMMdd") + ".log"

# Search today's FTP log file for "530 1" to find lines that contain IPs of systems that failed to log in,
# get just the IP from each line, group the IPs by IP to count the attempts from each one, and select only the
# IPs that have $int_block_limit or more bad logins today
# In FTPSVC* has to be added the ID of the FTP-server instead of *, or just take the right log-folder
$arr_new_bad_ips_ftp = @()
$arr_new_bad_ips_ftp = Select-String $str_log_file_name -pattern "530 1" |
    ForEach-Object {$_.Line.Substring(20,15) -replace " .*", ""} |
    Group |
    Where {$_.Count -ge $int_block_limit} |
    Select -property Name

# Concatenate the two arrays of IPs (one from Security log, one from FTP log)
$arr_new_bad_ips_all = @()
# $arr_new_bad_ips_all = @($arr_new_bad_ips_security_log) + @($arr_new_bad_ips_ftp_over_limit)
$arr_new_bad_ips_all = @($arr_new_bad_ips_security_log) + @($arr_new_bad_ips_ftp_control_channel) + @($arr_new_bad_ips_ftp)

# Sort the array, selecting only unique IPs (in case one IP shows up in both the Security and FTP logs)
$arr_new_bad_ips_all_sorted = @()
$arr_new_bad_ips_all_sorted = $arr_new_bad_ips_all |
    Foreach-Object { [string]$_.Name } |
    Select-Object -unique

# Get firewall object
$firewall = New-Object -comobject hnetcfg.fwpolicy2

# Get all firewall rules matching "BlockAttackers*"
$arr_firewall_rules = $firewall.Rules | Where {$_.Name -like 'BlockAttackers*'}

# If no "BlockAttackers*" firewall rule exists yet, create one and set it to a variable
if ($arr_firewall_rules -eq $null) {
    $str_new_rule_name = "BlockAttackers (Created " + $current_date_utc.ToString("yyyy-MM-dd HH:mm:ss") + " UTC)"
    netsh advfirewall firewall add rule dir=in action=block name=$str_new_rule_name description="Rule automatically created." enable=yes remoteip="0.0.0.0" | Out-Null
    $arr_firewall_rules = $firewall.Rules | Where {$_.Name -like 'BlockAttackers*'}
}

# Split the existing IPs from current "BlockAttackers*" firewall rule(s) into an array so we can easily search them
$arr_existing_bad_ips = @()
foreach ($rule in $arr_firewall_rules) {
    $arr_existing_bad_ips += $rule.RemoteAddresses -split(',')
}

# Clean subnet masks off of IPs that are currently blocked by the firewall rule(s)
$arr_existing_bad_ips_without_masks = @()
$arr_existing_bad_ips_without_masks = $arr_existing_bad_ips | ForEach-Object {$_ -replace "/.*", ""}

# Enter your server's IP (IPv4 and IPv6) in line 115 and 116.
# Select IP addresses to add to the firewall, but only ones that...
$arr_new_bad_ips_for_firewall = @()
$arr_new_bad_ips_for_firewall = $arr_new_bad_ips_all_sorted | Where {
    # contain an IP address (i.e. aren't blank or a dash, which the Security log has for systems that failed FTP logins)
    $_.Length -gt 6 -and
    # aren't already in the firewall rule(s)
    !($arr_existing_bad_ips_without_masks -contains $_) -and
    # aren't the local loopback
    !($_.StartsWith('127.0.0.1')) -and
    # aren't part of the local subnet
    !($_.StartsWith('192.168.')) -and
    !($_.StartsWith('0.0.')) -and
    !($_.StartsWith('10.0.')) -and
    !($_.StartsWith('*.*.*.*')) -and
    !($_.StartsWith('*:*:*:*:*:*'))
}

# If there are IPs to block, do the following...
if ($arr_new_bad_ips_for_firewall -ne $null) {
    # Write date and time to script-specific log file
    [DateTime]::Now | Out-File -Append -Encoding utf8 C:\inetpub\logs\LogFiles\blockattackers.txt
    # Write newly-blocked IP addresses to log file
    $arr_new_bad_ips_for_firewall | Out-File -Append -Encoding utf8 C:\inetpub\logs\LogFiles\blockattackers.txt

    # Boolean to make sure the new IPs are only added on one rule
    $bln_added_to_rule = 0

    # Array to hold bad IPs from each rule one at a time, so we can count to make sure adding the new ones won't exceed 1000 IPs
    $arr_existing_bad_ips_current_rule = @()

    # For each "BlockAttackers*" rule in the firewall, do the following...
    foreach ($rule in $arr_firewall_rules) {
        if ($bln_added_to_rule -ne 1) {
            # Split the existing IPs from the current rule into an array so we can easily count them
            $arr_existing_bad_ips_current_rule = $rule.RemoteAddresses -split(',')

            # If the number of IPs to add is less than 1000 minus the current number of IPs in the rule, add them to this rule
            if ($arr_new_bad_ips_for_firewall.Count -le (1000 - $arr_existing_bad_ips_current_rule.Count)) {
                # Add new IPs to firewall rule
                $arr_new_bad_ips_for_firewall | %{$rule.RemoteAddresses += ',' + $_}

                # Write which rule the IPs were added to to log file
                echo "New IP addresses above added to Windows Firewall rule:" $rule.Name | Out-File -Append -Encoding utf8 C:\inetpub\logs\LogFiles\blockattackers.txt

                # Set boolean so any other rules are skipped when adding IPs
                $bln_added_to_rule = 1
            }
        }
    }

    # If there wasn't room in any other "BlockAttackers*" firewall rule, create a new one and add the IPs to it
    if ($bln_added_to_rule -ne 1) {
        $str_new_rule_name = "BlockAttackers (Created " + $current_date_utc.ToString("yyyy-MM-dd HH:mm:ss") + " UTC)"
        netsh advfirewall firewall add rule dir=in action=block name=$str_new_rule_name description="Rule automatically created." enable=yes remoteip="0.0.0.0" | Out-Null
        $new_rule = $firewall.rules | Where {$_.Name -eq $str_new_rule_name}

        # Add new IPs to firewall rule
        $arr_new_bad_ips_for_firewall | %{$new_rule.RemoteAddresses += ',' + $_}

        # Write which rule the IPs were added to to log file
        echo "New IP addresses above added to newly created Windows Firewall rule:" $new_rule.Name | Out-File -Append -Encoding utf8 C:\inetpub\logs\LogFiles\blockattackers.txt
    }
}

The name of the FTP's log-folder FTPSVC* on line 54 has to be completed of cause. In line 115 and 116 have to be entered your server's IP (IPv4 and IPv6), otherwise the own servers IP might be added to the firewall-rule a hundred times. The variable $int_block_limit I'm setting to 1 at my server, so the script is blocking a hackers attack causing a 4625-event within two seconds. I'm still thinking about to run the script additionally to occurring 4625-events in a time period of a few minutes. Of cause, it would also be possible, to separate the scripts and let one script check the 4625-events triggered by the 4625-event and another one the FTP's log-folders checking periodically every 5 or 10 minutes, even with separate firewall-rule and log-file.

-1

I have added mine for SQL

# Select from the Application log (SQL) all IP addresss that have more than $int_block_limit logon failure within $dat_time_window
$arr_new_bad_ips_SQL_log = @()
$arr_new_bad_ips_SQL_log = Get-EventLog -LogName 'Application' -After $dat_time_window |
    Where-Object{$_.EventID -eq 18456} |
    Select-Object @{n='CLIENT';e={$_.ReplacementStrings[-1]}} |
    Group-Object -property CLIENT |
    Where {$_.Count -ge $int_block_limit} |
    Select -property Name |
    {
        $_.Name = $_.Name.Replace(" [CLIENT: ", "");
        $_.Name = $_.Name.Replace("]", "");
        return $_;
    }

Then you will have to add the array into ips_all

$arr_new_bad_ips_all = @($arr_new_bad_ips_SQL_log) + @($arr_new_bad_ips_security_log) + @($arr_new_bad_ips_ftp_control_channel) + @($arr_new_bad_ips_ftp)