How to Automatically Block Suspicious IPs After Multiple Failed RDP Login Attempts on Windows Server 2008


38 views

Windows Server 2008's native account lockout policy is a good first line of defense, but sophisticated attackers often rotate through thousands of username/password combinations from a single IP address without triggering account lockouts. I recently encountered a case where an attacker was making exactly 1 login attempt per second - carefully staying below the threshold that would lock any particular account.

Account lockout policies protect against password spraying (trying one common password against many accounts). IP-based blocking handles credential stuffing (trying many passwords against a few accounts). The ideal solution combines both approaches.

We'll create a PowerShell script that:

  1. Monitors Windows Security Event Log for failed login attempts (Event ID 4625)
  2. Tracks IPs that exceed a threshold of failed attempts
  3. Automatically adds firewall rules to block malicious IPs
  4. Includes an expiration mechanism (30 minute blocks)

Here's the complete script that handles both detection and blocking:


# Configure these variables
$maxAttempts = 5        # Max failed attempts before blocking
$blockDuration = 30     # Minutes to block the IP
$eventID = 4625         # Failed logon event ID

# Main monitoring function
function Monitor-FailedLogons {
    $blockedIPs = @{}
    
    # Query events in a loop
    while ($true) {
        $events = Get-WinEvent -FilterHashtable @{
            LogName='Security'
            ID=$eventID
            StartTime=(Get-Date).AddMinutes(-1)
        } -ErrorAction SilentlyContinue

        # Process each failed logon event
        foreach ($event in $events) {
            $ip = ($event.Properties[19].Value -split ":")[0]
            
            if ($ip -and $ip -ne "-") {
                if ($blockedIPs.ContainsKey($ip)) {
                    # Existing IP - increment count
                    $blockedIPs[$ip].Count++
                    
                    # Block if threshold reached
                    if ($blockedIPs[$ip].Count -ge $maxAttempts -and 
                        -not $blockedIPs[$ip].Blocked) {
                        Block-IP $ip
                        $blockedIPs[$ip].Blocked = $true
                        $blockedIPs[$ip].BlockTime = Get-Date
                    }
                } else {
                    # New IP - initialize tracking
                    $blockedIPs[$ip] = @{
                        Count = 1
                        Blocked = $false
                        BlockTime = $null
                    }
                }
            }
        }

        # Remove expired blocks
        $now = Get-Date
        foreach ($ip in $blockedIPs.Keys) {
            if ($blockedIPs[$ip].Blocked -and 
                ($now - $blockedIPs[$ip].BlockTime).TotalMinutes -ge $blockDuration) {
                Remove-IPBlock $ip
                $blockedIPs.Remove($ip)
            }
        }

        Start-Sleep -Seconds 30
    }
}

# Helper function to add firewall rule
function Block-IP {
    param($ip)
    
    $ruleName = "AutoBlock_$ip"
    if (-not (Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue)) {
        New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -Action Block 
            -RemoteAddress $ip -Protocol Any -Enabled True | Out-Null
        Write-Host "[$(Get-Date)] Blocked IP: $ip"
    }
}

# Helper function to remove firewall rule
function Remove-IPBlock {
    param($ip)
    
    $ruleName = "AutoBlock_$ip"
    if (Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue) {
        Remove-NetFirewallRule -DisplayName $ruleName | Out-Null
        Write-Host "[$(Get-Date)] Unblocked IP: $ip"
    }
}

# Start monitoring
Monitor-FailedLogons

For production use:

  • Run the script as a Windows Scheduled Task with highest privileges
  • Consider increasing the sleep interval to 2-5 minutes for reduced overhead
  • Log blocked IPs to a file for forensic analysis
  • Combine with RDP port obfuscation or VPN access for maximum security

For environments with multiple servers, you can centralize monitoring by:

  1. Configuring Windows Event Forwarding to send Security logs to a central server
  2. Modifying the script to process forwarded events
  3. Implementing distributed blocking by pushing firewall rules to target servers

When monitoring Windows Server 2008 security logs, you'll typically see Event ID 4625 for failed logins. A common pattern looks like:

Event ID: 4625
Logon Type: 3 (Network)
Source Network Address: 192.168.1.100
Account Name: Administrator

Here's a complete PowerShell solution that automatically adds firewall rules for repeated offenders:

# Failed Login Threshold
$threshold = 5
# Block Duration in minutes
$blockDuration = 30

# Parse Security Log for Failed Attempts
$failedLogins = Get-WinEvent -FilterHashtable @{
    LogName='Security'
    ID=4625
} -MaxEvents 1000 | Where-Object {
    $_.Properties[5].Value -eq 3 -and  # Network logon
    $_.Properties[19].Value -ne '-'    # Has IP address
}

# Group by IP and filter
$offenders = $failedLogins | Group-Object { $_.Properties[19].Value } | 
    Where-Object { $_.Count -ge $threshold }

# Block each offender in Windows Firewall
foreach ($offender in $offenders) {
    $ip = $offender.Name
    $ruleName = "Block_BruteForce_$ip"
    
    # Check if rule already exists
    if (-not (Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue)) {
        New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -Action Block 
            -RemoteAddress $ip -Protocol Any -Enabled True
        
        # Schedule unblock after duration
        $unblockTime = (Get-Date).AddMinutes($blockDuration)
        $action = {
            Remove-NetFirewallRule -DisplayName $ruleName
        }
        Register-ScheduledTask -TaskName "Unblock_$ip" -Trigger (New-ScheduledTaskTrigger -Once -At $unblockTime) 
            -Action (New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-Command "$action"") -Force
    }
}

To run this script automatically:

  1. Save it as BlockBruteForce.ps1
  2. Create a scheduled task that triggers on Event ID 4625
  3. Set the action to run the PowerShell script

For a more robust solution, consider using Fail2Ban with Windows:

# Sample fail2ban jail configuration
[windows-rdp]
enabled  = true
port     = 3389
filter   = windows-rdp
logpath  = %windir%\System32\winevt\Logs\Security.evtx
maxretry = 5
bantime  = 1800
action   = iptables-allports[name=WindowsRDP,protocol=all]

When implementing this solution, monitor these additional Event IDs:

  • 4768: Kerberos authentication (TGT request)
  • 4771: Kerberos pre-authentication failed
  • 4624: Successful logon (to detect successful breaches)