Take the 2-minute tour ×
Server Fault is a question and answer site for professional system and network administrators. It's 100% free, no registration required.

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.

share|improve this question
*nix has fial2ban... not sure if there's a Windows equivalent/port. fail2ban.org/wiki/index.php/Main_Page –  Chris Nava Feb 11 '11 at 19:06
From Evan Anderson: serverfault.com/questions/43360/… ...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. –  kce Feb 11 '11 at 19:35
You might want to have a look at serverfault.com/questions/216995/… for a previous discussion about how useful automated banning based on IP is. –  pehrs Feb 13 '11 at 18:16
If you're talking about Terminal Services / Remote Desktop have a look here: serverfault.com/a/335976/7200 –  Evan Anderson Nov 29 '11 at 23:57
I made a windows service on github to do just that: github.com/jjxtra/Windows-IP-Ban-Service –  PsychoDad Feb 20 '12 at 18:06

10 Answers 10

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 + '/') } # 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.

share|improve this answer
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: serverfault.com/a/571903/107701 –  kevinmicke Mar 4 at 17:17

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.

share|improve this answer

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

share|improve this answer
+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. –  kce Feb 11 '11 at 19:40
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 2:35
Sorry, but I didn't ask if it was a good idea. –  HeavyWave Feb 13 '11 at 2:24
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 1:56

This script builds on remunda's answer and goes a little further http://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 + '/') }

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

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

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...

share|improve this answer

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.

share|improve this answer

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)

share|improve this answer
On my AWS small instance, 1 attempt every 4 seconds is enough to consume 50% CPU. Pretty crap if you ask me... –  romkyns Mar 2 '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 4 '12 at 1: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. –  romkyns Mar 4 '12 at 14:46


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)

share|improve this answer

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: http://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="" | 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('')) -and
    # aren't part of the local subnet
    !($_.StartsWith('192.168.')) -and

# 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="" | 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
share|improve this answer
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 at 18:27

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.