When you see Event ID 4625 with "Remote session from client name exceeded maximum failed logons" in Windows Security logs, you're witnessing a brute force attack in progress. Modern attackers use distributed IPs and credential stuffing techniques that bypass traditional account lockout policies.
# PowerShell: Temporarily block suspicious IPs
$attackIPs = Get-WinEvent -FilterHashtable @{LogName='Security';ID=4625} |
Where-Object {$_.Properties[19].Value -like '*a*'} |
Select-Object -ExpandProperty Properties |
Where-Object {$_.Value -like '*.*'} |
Select-Object -Unique Value
$attackIPs | ForEach-Object {
New-NetFirewallRule -DisplayName "BlockRDPBruteForce_$($_.Value)" -Direction Inbound -LocalPort 3389 -Protocol TCP -Action Block -RemoteAddress $_.Value
}
For persistent protection, implement these techniques:
# Configure Windows Defender Firewall with Advanced Security
Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled True
Set-NetFirewallRule -DisplayGroup "Remote Desktop" -Enabled True -Action Block -Profile Any
# Enable Network Level Authentication (NLA)
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" -Name "UserAuthentication" -Value 1
For Linux-style IP banning on Windows:
# fail2ban equivalent using PowerShell scheduled tasks
$scriptBlock = {
$failedLogons = Get-WinEvent -FilterHashtable @{LogName='Security';ID=4625} -MaxEvents 100 |
Where-Object { $_.TimeCreated -gt (Get-Date).AddMinutes(-5) }
$ipFrequency = $failedLogons | Group-Object { $_.Properties[19].Value } |
Where-Object { $_.Count -gt 3 } |
Select-Object -ExpandProperty Name
$ipFrequency | ForEach-Object {
if (-not (Get-NetFirewallRule -DisplayName "AutoBlock_$_" -ErrorAction SilentlyContinue)) {
New-NetFirewallRule -DisplayName "AutoBlock_$_" -Direction Inbound -Action Block -RemoteAddress $_
}
}
}
Register-ScheduledTask -TaskName "RDPBruteForceProtection" -Trigger (New-ScheduledTaskTrigger -AtStartup) -Action (New-ScheduledTaskAction -Execute "PowerShell" -Argument "-Command & {$scriptBlock}") -RunLevel Highest
When hardening RDP isn't enough:
# Set up PowerShell Remoting as backup
Enable-PSRemoting -Force
Set-NetFirewallRule -Name "WINRM-HTTP-In-TCP" -RemoteAddress (Get-NetIPAddress -InterfaceAlias "Ethernet*" | Where-Object {$_.AddressFamily -eq "IPv4"}).IPAddress
# Create real-time monitoring with Event Log triggers
$query = @"
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">
*[System[(EventID=4625)]]
and
*[EventData[Data[@Name='TargetUserName']!='']]
and
*[EventData[Data[@Name='WorkstationName']='']]
</Select>
</Query>
</QueryList>
"@
$action = {
$event = $args[0]
$ip = $event.Properties[19].Value
$username = $event.Properties[5].Value
Send-MailMessage -To "admin@domain.com" -From "rdpmonitor@domain.com" -Subject "RDP Bruteforce Alert" -Body "Attack detected from $ip targeting $username"
}
Register-WinEvent -Query $query -Action $action -SourceIdentifier "RDPAttackMonitor"
Seeing "Remote session exceeded maximum failed logon attempts" in your Event Viewer means one thing - you're under a dictionary attack. This happens to every exposed RDP server eventually. Let's implement real defenses beyond just renaming the Administrator account.
While originally for Linux, Fail2Ban works beautifully on Windows for RDP protection. Here's how to set it up:
# Install Fail2Ban via Chocolatey
choco install fail2ban -y
# Sample jail.local configuration for RDP
[RDP]
enabled = true
port = 3389
filter = rdp
logpath = C:\Windows\System32\winevt\Logs\Security.evtx
maxretry = 3
bantime = 1h
Combine with PowerShell automation for dynamic blocking:
# Create firewall rule to block repeated offenders
$RuleName = "RDP Bruteforce Block"
$Threshold = 5 # Failed attempts
$TimeWindow = New-TimeSpan -Minutes 30
# Run this as scheduled task every 15 minutes
$FailedLogons = Get-WinEvent -FilterHashtable @{
LogName='Security'
ID='4625'
} | Where-Object { $_.TimeCreated -gt (Get-Date).AddMinutes(-30) }
$IPsToBlock = $FailedLogons | Group-Object -Property {$_.Properties[19].Value} |
Where-Object {$_.Count -ge $Threshold} | Select-Object -ExpandProperty Name
foreach ($IP in $IPsToBlock) {
if (-not (Get-NetFirewallRule -DisplayName "$RuleName $IP" -ErrorAction SilentlyContinue)) {
New-NetFirewallRule -DisplayName "$RuleName $IP" -Direction Inbound -Action Block -RemoteAddress $IP
}
}
- Enable Network Level Authentication (NLA)
- Restrict RDP access to specific IPs when possible
- Implement account lockout policies (careful with production servers)
- Change default RDP port (security through obscurity)
- Enable Windows Defender Attack Surface Reduction rules
Create a SIEM query to track RDP attacks:
# KQL query for Azure Sentinel/Security Center
SecurityEvent
| where EventID == 4625 and LogonType == 10
| summarize AttemptCount=count() by AttackerIP=IpAddress, TargetAccount=TargetUserName
| where AttemptCount > 3
| sort by AttemptCount desc