На любом хосте Windows, напрямую подключённом к Интернету, с открытым наружу портом RDP периодически будут фиксироваться попытки удаленного перебора паролей. Для эффективной защиты стандартного RDP порта 3389
от перебора паролей и эксплуатации уязвимостей рекомендуется разместить RDP сервер за VPN или шлюзом RD Gateway . Если реализовать такую схему невозможно, нужно внедрять дополнительные средства для защиты RDP:
- Не использовать стандартные имена учетных записей в Windows (если это не сделать, популярные имена учетных записей {
admin
,administrator
,user1
}будут периодически блокироваться в соответствии с настройками политики паролей Windows ); - Использовать сложные политики паролей пользователей ;
- Изменить стандартный RDP порт 3389 на другой;
- Настроить 2FA для RDP входа (пример настройки двухфакторной аутентификации для RDP с помощью open-source multiOTP ).
В этой статье мы настроим автоматическую блокировку IP адресов, с которых выполняются неудачные попытки RDP аутентификации.
PowerShell скрипт для блокировки IP адресов-источников RDP атак
При неудачной попытке аутентификации в Windows по RDP в журнале событий Security регистрируется событие с EventID 4625 (неудачный вход — An account failed to log on
и LogonType = 3
, см. статью Анализ RDP логов в Windows ). В описании события указано имя пользователя, под которым выполнялась попытка подключения и IP адрес источника.
Можно вывести список таких событий с неудачными попытками входа за последний час по с помощью PowerShell командлета Get-WinEvent :
$result = Get-WinEvent -FilterHashtable @{LogName='Security';ID=4625; EndTime=(get-date).AddHours(-1)} | ForEach-Object {
$eventXml = ([xml]$_.ToXml()).Event
[PsCustomObject]@{
UserName = ($eventXml.EventData.Data | Where-Object { $_.Name -eq 'TargetUserName' }).'#text'
IpAddress = ($eventXml.EventData.Data | Where-Object { $_.Name -eq 'IpAddress' }).'#text'
EventDate = [DateTime]$eventXml.System.TimeCreated.SystemTime
}
}
$result
С помощью PowerShell скрипта можно анализировать этот лог, и, если с конкретного IP адреса за последние 2 часа было зафиксировано более 5 неудачных попыток входа по RDP, IP адрес будет добавлен в блокирующее правило Windows Firewall.
# количество неудачных попыток входа с одного IP адреса, при достижении которого нужно заблокировать IP_x000D_$badAttempts = 5_x000D_# Просмотр лога за последние 2 часа_x000D_$intervalHours = 2_x000D_# Если в блокирующем правиле более 3000 уникальных IP адресов, создать новое правило Windows Firewall_x000D_$ruleMaxEntries = 3000_x000D_# номер порта, на котором слушает RDP_x000D_$RdpLocalPort=3389_x000D_# файл с логом работы PowerShell скрипта_x000D_$log = "c:psrdp_block.log"_x000D_# Список доверенных IP адресов, которые нельзя блокировать_x000D_$trustedIPs = @("192.168.1.100", "192.168.1.101","8.8.8.8") _x000D_ $startTime = [DateTime]::Now.AddHours(-$intervalHours)_x000D_$badRDPlogons = Get-EventLog -LogName 'Security' -After $startTime -InstanceId 4625 |_x000D_ Where-Object { $_.Message -match 'logon type:s+(3)s' } |_x000D_ Select-Object @{n='IpAddress';e={$_.ReplacementStrings[-2]}}_x000D_$ipsArray = $badRDPlogons |_x000D_ Group-Object -Property IpAddress |_x000D_ Where-Object { $_.Count -ge $badAttempts } |_x000D_ ForEach-Object { $_.Name }_x000D_# Удалить доверенные IP адреса _x000D_$ipsArray = $ipsArray | Where-Object { $_ -notin $trustedIPs }_x000D_if ($ipsArray.Count -eq 0) {_x000D_ return_x000D_}_x000D_[System.Collections.ArrayList]$ips = @()_x000D_[System.Collections.ArrayList]$current_ip_lists = @()_x000D_$ips.AddRange([string[]]$ipsArray)_x000D_$ruleCount = 1_x000D_$ruleName = "BlockRDPBruteForce" + $ruleCount_x000D_$foundRuleWithSpace = 0_x000D_while ($foundRuleWithSpace -eq 0) {_x000D_ $firewallRule = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue_x000D_ if ($null -eq $firewallRule) {_x000D_ New-NetFirewallRule -DisplayName $ruleName –RemoteAddress 1.1.1.1 -Direction Inbound -Protocol TCP –LocalPort $RdpLocalPort -Action Block_x000D_ $firewallRule = Get-NetFirewallRule -DisplayName $ruleName_x000D_ $current_ip_lists.Add(@(($firewallRule | Get-NetFirewallAddressFilter).RemoteAddress))_x000D_ $foundRuleWithSpace = 1_x000D_ } else {_x000D_ $current_ip_lists.Add(@(($firewallRule | Get-NetFirewallAddressFilter).RemoteAddress)) _x000D_ if ($current_ip_lists[$current_ip_lists.Count – 1].Count -le ($ruleMaxEntries – $ips.Count)) {_x000D_ $foundRuleWithSpace = 1_x000D_ } else {_x000D_ $ruleCount++_x000D_ $ruleName = "BlockRDPBruteForce" + $ruleCount_x000D_ }_x000D_ }_x000D_}_x000D_# Удалить IP адреса, которые уже есть в правиле _x000D_for ($i = $ips.Count – 1; $i -ge 0; $i--) {_x000D_ foreach ($current_ip_list in $current_ip_lists) {_x000D_ if ($current_ip_list -contains $ips[$i]) {_x000D_ $ips.RemoveAt($i)_x000D_ break_x000D_ }_x000D_ }_x000D_}_x000D_if ($ips.Count -eq 0) {_x000D_ exit_x000D_}_x000D_# Заблокировать IP в firewall и записать в лог_x000D_$current_ip_list = $current_ip_lists[$current_ip_lists.Count – 1]_x000D_foreach ($ip in $ips) {_x000D_ $current_ip_list += $ip_x000D_ (Get-Date).ToString().PadRight(22) + ' | ' + $ip.PadRight(15) + ' | The IP address has been blocked due to ' + ($badRDPlogons | Where-Object { $_.IpAddress -eq $ip }).Count + ' failed login attempts over ' + $intervalHours + ' hours' >> $log_x000D_}_x000D_Set-NetFirewallRule -DisplayName $ruleName -RemoteAddress $current_ip_list_x000D_
$badRDPlogons = Get-EventLog -LogName ...
На блок кода:
$ipPattern = 'b(?:d{1,3}.){3}d{1,3}b'
$badRDPlogons = @()
$events = Get-WinEvent -FilterHashTable @{LogName='Microsoft-Windows-RemoteDesktopServices-RdpCoreTS/Operational';ID='140';StartTime=$startTime}
$events.message | ForEach-Object {
if ($_ -match $ipPattern) {
$ipObject = [PSCustomObject]@{
IpAddress = $matches[0]
}
$badRDPlogons += $ipObject
}
}