Защита Windows Server от брутфорса (RDP и SMB): скоростной и безопасный аналог Fail2ban на PowerShell


Защита Windows Server от RDP и SMB брутфорса: скоростной аналог Fail2ban на PowerShell и Windows Firewall с защитой от самоблокировки администратора.

Если сервер Windows смотрит сетевым интерфейсом в открытый интернет, журнал безопасности (Security Log) за считанные минуты переполняется событиями с кодом 4625 (неудачная попытка входа). Автоматизированные ботнеты непрерывно подбирают пароли к учетным записям admin, administrator, root, админ.

Стандартный подход — автоматизировать блокировку атакующих через PowerShell и Windows Firewall. Однако написание такого скрипта «в лоб» таит в себе неочевидные ловушки: от полной изоляции удаленного сервера до глухого зависания PowerShell при разборе журналов.

Боевой опыт: скрипт безопасности обязан быть отказоустойчивым. Любая ошибка в логике правил брандмауэра может заблокировать доступ к серверу без возможности удаленного восстановления.

3 критические ловушки при создании защиты

  1. Ловушка дефолтов Windows Firewall:
    Команда New-NetFirewallRule -Action Block без немедленно переданного списка IP по умолчанию блокирует ВЕСЬ входящий трафик со всего мира на все порты. Сервер моментально становится «кирпичом».
  2. Ловушка протоколов (RDP vs SMB):
    Боты часто перебирают пароли не только через RDP (порт 3389), но и через SMB (порт 445 / NetBIOS). Блокировка только RDP-порта оставляет сервер уязвимым для атак через общий доступ к файлам. Злоумышленника необходимо блокировать по всем портам целиком.
  3. Ловушка производительности 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"
    }
Итог: Готовое решение работает автономно, обрабатывает сотни атак за доли секунды, наглухо перекрывает доступ нарушителям по всем протоколам и полностью защищает администратора от случайной самоблокировки.
Читать полностью »

Мой 2024 - фото и видео

 Январь 2024


Работа в итальянских Альпах в основном сезонная. Зима в здешних краях - самый туристический сезон. А значит, не время для фото и видео - только работа по 10 часов в сутки мойщиком посуды в гостинице. Поэтому даже Новый год не встретил дома - с работы вернулся в час ночи. Но вот честь января осталась в виде елочной игрушки, привезенной из Германии в ноябре 2023:


 

Февраль 2024


В феврале - всё та же работа, но в один из выходных получилось вырваться покататься на снегоходах на перевале Пассо дель Тонале, который разделяет две провинции: Трентино и Ломбардия.



Март 2024


Марта не было совсем. Точнее, он был, но от него ничего не осталось... Даже нашего ЗАЗ Сенс 1,5 2006, который какое-то время был нашим единственным домом, не осталось...



Апрель 2024


В апреле работы стало поменьше, поэтому удалось вырваться в выходные за пределы дома. Тем более, что в деревне Оссана по соседству находится полуразрушенный замок Castel San Michele o Castello di Ossana. И не в туристический сезон вход в него бесплатный.



Вид со смотровой башни замка на деревню Фучине и долину Валь ди Пейо (Val di Peio) :


А на Пейо 3000 еще лежит снег:


Над Оссаной находится живописная и тихая, особенно не в сезон, долина Вальпьяна (Valpiana), но для поездки туда на авто, нужно быть уверенным, что машина выдержит крутой подъем и не перегреется.


Весна в Альпах - это время цветения. Эстафету цветения начинают крокусы:



Иногда мы получаем посылки из Украины, для этого нам нужно съездить в наш "областной центр" (центр провинции) - город Тренто:



Май 2024


В мае наконец-то долгожданный отпуск между рабочими сезонами. Приезжали гости из Германии, забирал их из Тренто с Фликсбуса, пока ждал автобуса, сделал пару фото в Тренто на берегу реки Адидже (Adige) :



Ну раз приехали гости, нужно повозить их по нашему региону. В Трентино-Альто-Адидже не бывает скучного времени года.

В двух часах езды от нас есть бассейный комплекс Бормио Терме Спа. Но дело даже не в двух часах, а в том, что дорога проходит через два перевала: Пассо дель Тонале и Априка.

Городок Априка наверное самый дорогой в нашем регионе...


Ну а сам комплекс термальных бассейнов Бормио Терме - сказочное место. Вход на весь день на семью из трех человек обойдется в день 65 EUR. Но оно того стоит... Горячие, теплые и обычные бассейны, сауны, водные горки, и многочисленные джакузи в том числе на открытом воздухе на фоне еще покрытых снегом Альп...


По дороге из бассейна домой заехали в красивую церковь с необычной ахитектурой 

Chiesa prepositurale di San Giuseppe в городке Грозьё:



А потом город Тирано - крайний город на границе со Швейцарией. Из этого города в Швейцарию ходит известный туристический поезд Tirano - St. Moritz. При чем поезд в Тирано едет прямо по улицам города.




Вот как раз мимо этой церкви по городу едет поезд. Мы буквально на 5 минут разминулись с последним...


В другой день отправили гостей на рафтинг по нашей горной реке Торренте Ноче:



В день, когда погода была не очень, повез моих гостей по соседней долине Валь ди Нон.

Первое интересное место в долине Нон - каньон Рио Сасс. Каньон находится около городка Фондо:





Внутри церкви Chiesa Parrocchiale di San Martino:



Вид с одной из центральных улиц Фондо на долину Нон:




Ну и как же в путешествии без Порше... Это конечно не музей, но фотографировать можно бесплатно:





В каньоне - 2 маршрута: бесплатный с каньоном и Изумрудным озером (на озере есть кафе с очень вкусными обедами) .

Бесплатный маршрут:





Платный маршрут:

На экскурсию по платному маршруту нужно записываться заранее.







На дорогое после каньонов:



А дорога наша лежала к храму на горе - Санктуарио сан Ромедио (Santuario di San Romedio) :










Читать полностью »

Музей Порше в Штутгарте 2023

 

Музей Порше в Штутгарте - обложка статьи

Фотоотчет о посещении музея Порше в Штутгарте. Фотографии автомобилей Porsche в высоком качестве.


Штутгарт, известный своим автомобильным наследием, является домом для одного из самых захватывающих мест для поклонников автомобилей - музея Порше. Этот музей не просто представляет историю легендарного бренда Porsche, но и является источником вдохновения для всех, кто ценит инновации, дизайн и скорость.

В музее можно не только посмотреть на автомобили, но и совершить полноценную экскурсию с персональным аудиогидом (аудиогид доступен на нескольких языках). Также можно сфотографироваться внутри нескольких (не всех) автомобилей, погонять на гонках в симуляторе, собрать свой собственный автомобиль в виртуальной реальности и купить сувениры и мерч в магазине музея.


Музей Порше - 01

Музей Порше - 02

Музей Порше - 03

Музей Порше - 04

Музей Порше - 05

Музей Порше - 07

Музей Порше - 08

Музей Порше - 09

Музей Порше - 10

Музей Порше - 11

Музей Порше - 12

Музей Порше - 13

Музей Порше - 14

Музей Порше - 15

Музей Порше - 16

Музей Порше - 17

Музей Порше - 18

Музей Порше - 19

Музей Порше - 20

Музей Порше - 21

Музей Порше - 22

Музей Порше - 23

Музей Порше - 24

Музей Порше - 25

Музей Порше - 26

Музей Порше - 27

Музей Порше - 28

Музей Порше - 29

Музей Порше - 30

Музей Порше - 31

Музей Порше - 32

Музей Порше - 33

Музей Порше - 34

Музей Порше - 35

Музей Порше - 36

Музей Порше - 37

Музей Порше - 38

Музей Порше - 39

Музей Порше - 40

Музей Порше - 41

Музей Порше - 42

Музей Порше - 43

Музей Порше - 44

Музей Порше - 45

Музей Порше - 46

Музей Порше - 47

Музей Порше - 48

Музей Порше - 49

Музей Порше - 50

Музей Порше - 51

Музей Порше - 52

Музей Порше - 53

Музей Порше - 54

Музей Порше - 55

Музей Порше - 56

Музей Порше - 57

Музей Порше - 58

Музей Порше - 59

Музей Порше - 60

Музей Порше - 61

Музей Порше - 62

Музей Порше - 63


Фотографии сделаны с помощью фотоаппарата Sony A6400 с объективом Sigma 30/1.4 DN.
Фото обработаны в редакторах Adobe LightRoom и PhotoShop.



© 2023 Andrew Lazarev Blog – личный фото видео блог. Фото и видео репортажи. Путешествия. Техника. Хобби.
Читать полностью »