Защита Windows Server от RDP и SMB брутфорса: скоростной аналог Fail2ban на PowerShell и Windows Firewall с защитой от самоблокировки администратора.
Если сервер Windows смотрит сетевым интерфейсом в открытый интернет, журнал безопасности (Security Log) за считанные минуты переполняется событиями с кодом 4625 (неудачная попытка входа). Автоматизированные ботнеты непрерывно подбирают пароли к учетным записям admin, administrator, root, админ.
Стандартный подход — автоматизировать блокировку атакующих через PowerShell и Windows Firewall. Однако написание такого скрипта «в лоб» таит в себе неочевидные ловушки: от полной изоляции удаленного сервера до глухого зависания PowerShell при разборе журналов.
Боевой опыт: скрипт безопасности обязан быть отказоустойчивым. Любая ошибка в логике правил брандмауэра может заблокировать доступ к серверу без возможности удаленного восстановления.
3 критические ловушки при создании защиты
-
Ловушка дефолтов Windows Firewall:
КомандаNew-NetFirewallRule -Action Blockбез немедленно переданного списка IP по умолчанию блокирует ВЕСЬ входящий трафик со всего мира на все порты. Сервер моментально становится «кирпичом». -
Ловушка протоколов (RDP vs SMB):
Боты часто перебирают пароли не только через RDP (порт 3389), но и через SMB (порт 445 / NetBIOS). Блокировка только RDP-порта оставляет сервер уязвимым для атак через общий доступ к файлам. Злоумышленника необходимо блокировать по всем портам целиком. -
Ловушка производительности PowerShell (тяжелый XML):
Использование[xml]$Event.ToXml()в цикле при атаке из сотен или тысяч попыток намертво вешает скрипт на парсинге XML-дерева. Чтение заранее скомпилированных свойств события напрямую из памяти через$Event.Propertiesускоряет работу в десятки раз: 600+ событий обрабатываются за 1 секунду.
Архитектура безопасного скрипта
- Автоматическая защита сессии админа: скрипт перед каждой операцией опрашивает активные TCP-соединения (
Get-NetTCPConnection) и принудительно заносит IP вашей текущей сессии в белый список. Вы никогда не заблокируете сами себя. - Строгое точное совпадение (Exact Match): проверка через
-containsисключает ложные срабатывания на легитимных пользователей (например,admin_corpилиuser_backup). Доменные префиксы видаDOMAIN\userотсекаются автоматически. - Блокировка по всем портам и протоколам: для обнаруженного IP создается правило
Protocol: Any, LocalPort: Any.
Финальный турбо-скрипт: Block-RDP-BruteForce.ps1
Сохраните следующий код в файл C:\Scripts\Block-RDP-BruteForce.ps1:
# =====================================================================
# Турбо-скрипт защиты от брутфорса RDP/SMB (блокировка по ВСЕМ портам)
# Совместимость: Windows Server 2016+ / PowerShell 5.1
# =====================================================================
$RuleName = "RDP-BruteForce-BlockList"
$MinutesBack = 10 # За сколько последних минут анализировать журнал
$Threshold = 2 # Порог попыток для бана
$LogPath = "C:\Scripts\rdp_block.log"
# 1. Белый список постоянных доверенных адресов
$StaticWhitelist = @("127.0.0.1", "::1")
# 2. Имена-ловушки (строгое совпадение, регистр не имеет значения)
$HoneypotNames = @(
"admin", "administrator", "админ", "администратор",
"root", "guest", "гость", "test", "support"
)
function Write-Log {
param([string]$Message)
$Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
$LogEntry = "[$Timestamp] $Message"
Write-Host $LogEntry
try { Add-Content -Path $LogPath -Value $LogEntry -ErrorAction SilentlyContinue } catch {}
}
# 3. Автоопределение RDP-порта и защита активной сессии администратора
$RdpPort = (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name PortNumber -ErrorAction SilentlyContinue).PortNumber
if (-not $RdpPort) { $RdpPort = 3389 }
$ActiveWhitelist = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
foreach ($ip in $StaticWhitelist) { if ($ip) { [void]$ActiveWhitelist.Add($ip.Trim()) } }
try {
$Established = Get-NetTCPConnection -LocalPort $RdpPort -State Established -ErrorAction SilentlyContinue
if ($Established) {
foreach ($conn in $Established) {
if ($conn.RemoteAddress -and $conn.RemoteAddress -ne "0.0.0.0" -and $conn.RemoteAddress -ne "::") {
[void]$ActiveWhitelist.Add($conn.RemoteAddress)
}
}
}
} catch {}
# 4. Чтение журнала безопасности (событие 4625)
$StartTime = (Get-Date).AddMinutes(-$MinutesBack)
try {
# Ограничение 1000 событий защищает память от переполнения при DDoS
$Events = Get-WinEvent -FilterHashtable @{ LogName='Security'; ID=4625; StartTime=$StartTime } -MaxEvents 1000 -ErrorAction Stop
} catch {
exit 0 # Событий нет — штатный выход
}
# 5. Мгновенный разбор свойств события из оперативной памяти
$CandidateIPs = [System.Collections.Generic.List[string]]::new()
foreach ($Event in $Events) {
try {
# Свойства Event 4625 в Windows Server: [5]=TargetUserName, [10]=LogonType, [19]=IpAddress
$RawUser = [string]$Event.Properties[5].Value
$LogonType = [string]$Event.Properties[10].Value
$IpStr = [string]$Event.Properties[19].Value
# Только сетевые входы (3 = Network/SMB/NLA, 10 = RDP)
if ($LogonType -ne '3' -and $LogonType -ne '10') { continue }
if ([string]::IsNullOrWhiteSpace($IpStr) -or $IpStr -eq '-' -or $IpStr -eq '127.0.0.1' -or $IpStr -eq '::1') { continue }
$parsedIP = $null
if (-not [System.Net.IPAddress]::TryParse($IpStr, [ref]$parsedIP)) { continue }
# Очистка имени от домена (DOMAIN\admin -> admin, admin@domain.local -> admin)
$CleanUser = $RawUser.Trim() -replace '^[^\\]+\\', '' -replace '@.+$', ''
if ($HoneypotNames -contains $CleanUser) {
$CandidateIPs.Add($IpStr)
}
} catch {}
}
if ($CandidateIPs.Count -eq 0) { exit 0 }
# 6. Группировка и проверка порога
$Violators = $CandidateIPs | Group-Object | Where-Object { $_.Count -ge $Threshold } | Select-Object -ExpandProperty Name
if (-not $Violators) { exit 0 }
# 7. Получение текущих правил брандмауэра
$ExistingRule = Get-NetFirewallRule -DisplayName $RuleName -ErrorAction SilentlyContinue
$CurrentBlocked = @()
if ($ExistingRule) {
$FilterObj = Get-NetFirewallAddressFilter -AssociatedNetFirewallRule $ExistingRule
if ($FilterObj.RemoteAddress) {
$CurrentBlocked = @($FilterObj.RemoteAddress) | Where-Object { $_ -ne "Any" -and $_ -ne "" }
}
}
# 8. Фильтрация белого списка
$IPsToAdd = @()
foreach ($vIP in $Violators) {
if ($ActiveWhitelist.Contains($vIP)) { continue }
if ($CurrentBlocked -notcontains $vIP) { $IPsToAdd += $vIP }
}
if ($IPsToAdd.Count -eq 0) { exit 0 }
# 9. Блокировка нарушителей по ВСЕМ портам и протоколам
$AllBlocked = @($CurrentBlocked + $IPsToAdd) | Select-Object -Unique
if ($AllBlocked.Count -gt 0) {
if ($ExistingRule) {
Set-NetFirewallRule -DisplayName $RuleName -RemoteAddress $AllBlocked
} else {
New-NetFirewallRule -DisplayName $RuleName `
-Name $RuleName `
-Description "Auto-block brute-force attackers across ALL ports" `
-Direction Inbound `
-Action Block `
-RemoteAddress $AllBlocked `
-Profile Any | Out-Null
}
foreach ($blockedIP in $IPsToAdd) {
Write-Log "НАГЛУХО ЗАБЛОКИРОВАН IP: $blockedIP (по ВСЕМ портам)"
}
}
Развертывание и автоматизация
Шаг 1. Включение аудита безопасности Windows
Убедитесь, что система протоколирует неудачные попытки входа:
auditpol /set /subcategory:"Вход в систему" /failure:enable
Шаг 2. Запуск по расписанию (каждые 5 минут)
Регистрируем задачу в Планировщике заданий Windows от имени учетной записи SYSTEM:
$Action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -WindowStyle Hidden -File `"C:\Scripts\Block-RDP-BruteForce.ps1`""
$Trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5)
$Principal = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName "Block-RDP-BruteForce" -Action $Action -Trigger $Trigger -Principal $Principal -Force
Шпаргалка по управлению блокировками
- Проверить параметры блокировки портов:
Get-NetFirewallPortFilter -AssociatedNetFirewallRule (Get-NetFirewallRule -DisplayName "RDP-BruteForce-BlockList") # В полях Protocol и LocalPort должно стоять 'Any' - Посмотреть список заблокированных IP:
$Rule = Get-NetFirewallRule -DisplayName "RDP-BruteForce-BlockList" -ErrorAction SilentlyContinue if ($Rule) { (Get-NetFirewallAddressFilter -AssociatedNetFirewallRule $Rule).RemoteAddress } else { "Список пуст" } - Посмотреть историю блокировок в логе:
Get-Content "C:\Scripts\rdp_block.log" -Tail 20 - Разблокировать конкретный IP вручную (например, 1.2.3.4):
$Rule = Get-NetFirewallRule -DisplayName "RDP-BruteForce-BlockList" $IPs = @((Get-NetFirewallAddressFilter -AssociatedNetFirewallRule $Rule).RemoteAddress) | Where-Object { $_ -ne "1.2.3.4" } if ($IPs.Count -gt 0) { Set-NetFirewallRule -DisplayName "RDP-BruteForce-BlockList" -RemoteAddress $IPs } else { Remove-NetFirewallRule -DisplayName "RDP-BruteForce-BlockList" }
Итог: Готовое решение работает автономно, обрабатывает сотни атак за доли секунды, наглухо перекрывает доступ нарушителям по всем протоколам и полностью защищает администратора от случайной самоблокировки.











































































































