Загрузка данных
# ======================================================
# Название: Блокировка брутфорса VPN/RRAS (IAS/NPS)
# Описание: Событие 6273 (NPS). IP атакующего = CallingStationID.
# При 5+ неудачных входах с IP - блокирует на 1 год.
# Запуск: Каждые 5 минут через планировщик задач
# ======================================================
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
# --- НАСТРОЙКИ ---
$Threshold = 5 # 5+ неудач с одного IP за период
$CheckMinutes = 10
$BlockMinutes = 525600 # 1 год
$FirewallRuleName = "Block-BruteForce-Powershell"
$WhiteList = @(
"192.168.1.100",
"10.0.0.50",
"176.212.166.157",
"26.175.174.142" # ваш NAS — на всякий случай
)
# --- ЛОГИРОВАНИЕ ---
function Write-Log {
param([string]$Message)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Write-Host "[$timestamp] $Message"
}
function Test-IsPrivateIP {
param([string]$IP)
if ($IP -eq "127.0.0.1" -or $IP -eq "::1" -or $IP -eq "-") { return $true }
if ($IP -match '^10\.') { return $true }
if ($IP -match '^172\.(1[6-9]|2[0-9]|3[0-1])\.') { return $true }
if ($IP -match '^192\.168\.') { return $true }
if ($IP -match '^169\.254\.') { return $true }
if ($IP -match '^26\.') { return $true } # ваш диапазон NAS
return $false
}
# --- ОСНОВНАЯ ЛОГИКА ---
Write-Log "Start checking (period: $CheckMinutes minutes)"
$now = Get-Date
$start = $now.AddMinutes(-$CheckMinutes)
Write-Log "Checking 6273 events from $($start.ToString('yyyy-MM-dd HH:mm:ss')) to $($now.ToString('yyyy-MM-dd HH:mm:ss'))"
# --- ЧТЕНИЕ СОБЫТИЙ 6273 (NPS) ---
try {
$events = Get-WinEvent -FilterHashtable @{
LogName = "Security"
Id = 6273
StartTime = $start
EndTime = $now
} -ErrorAction Stop
} catch {
if ($_.Exception.Message -match "No events were found") {
Write-Log "No 6273 events found (normal if no failed VPN logins)"
exit 0
} else {
Write-Log "Error reading event log: $_"
exit 1
}
}
if (-not $events) {
Write-Log "No 6273 events found"
exit 0
}
Write-Log "Found events 6273: $($events.Count)"
# --- ПАРСИНГ СОБЫТИЙ ---
$ipList = New-Object System.Collections.Generic.List[object]
foreach ($event in $events) {
try {
# Быстрый доступ через Properties (индексы для 6273)
# 0=SubjectUserSid, 1=SubjectUserName, 2=SubjectDomainName,
# 3=FullyQualifiedSubjectUserName, 4=SubjectMachineSID,
# 5=SubjectMachineName, 6=FullyQualifiedSubjectMachineName,
# 7=MachineInventory, 8=CalledStationID, 9=CallingStationID,
# 10=NASIPv4Address, 11=NASIPv6Address, 12=NASIdentifier,
# 13=NASPortType, 14=NASPort, 15=ClientName,
# 16=ClientIPAddress, 17=ProxyPolicyName, 18=NetworkPolicyName,
# 19=AuthenticationProvider, 20=AuthenticationServer,
# 21=AuthenticationType, 22=EAPType, 23=AccountSessionIdentifier,
# 24=ReasonCode, 25=Reason, 26=LoggingResult
$props = $event.Properties
$user = $props[1].Value
$callingId = $props[9].Value # IP атакующего
$reasonCode = $props[24].Value
# Нам нужны только неудачи (ReasonCode 16 = неверные креды;
# 0 = успех — пропускаем)
if ([int]$reasonCode -eq 0) { continue }
if ($callingId -and $callingId -ne "-") {
$ipList.Add([PSCustomObject]@{
IpAddress = $callingId
TargetUser = $user
TimeCreated = $event.TimeCreated
ReasonCode = $reasonCode
})
}
} catch {
continue
}
}
if ($ipList.Count -eq 0) {
Write-Log "No valid CallingStationID found in 6273 events"
exit 0
}
Write-Log "Valid failed-auth events: $($ipList.Count)"
# --- ФИЛЬТРАЦИЯ ---
$filteredIPs = @()
foreach ($item in $ipList) {
if ($WhiteList -contains $item.IpAddress) { continue }
if (Test-IsPrivateIP -IP $item.IpAddress) { continue }
$filteredIPs += $item
}
if ($filteredIPs.Count -eq 0) {
Write-Log "All IPs are whitelisted or private"
exit 0
}
Write-Log "After filtering: $($filteredIPs.Count) events"
# --- ГРУППИРОВКА ПО IP ---
$groupedIPs = @{}
foreach ($item in $filteredIPs) {
if ($groupedIPs.ContainsKey($item.IpAddress)) {
$groupedIPs[$item.IpAddress].Count++
$groupedIPs[$item.IpAddress].Users += ", " + $item.TargetUser
$groupedIPs[$item.IpAddress].LastAttempt = $item.TimeCreated
} else {
$groupedIPs[$item.IpAddress] = @{
Count = 1
Users = $item.TargetUser
FirstAttempt = $item.TimeCreated
LastAttempt = $item.TimeCreated
}
}
}
# --- ОПРЕДЕЛЕНИЕ НАРУШИТЕЛЕЙ ---
$violators = @()
$violatorDetails = @{}
foreach ($key in $groupedIPs.Keys) {
if ($groupedIPs[$key].Count -ge $Threshold) {
$violators += $key
$violatorDetails[$key] = $groupedIPs[$key]
}
}
if ($violators.Count -eq 0) {
Write-Log "No violators found (threshold: $Threshold)"
exit 0
}
Write-Log "Found violators: $($violators.Count)"
foreach ($ip in $violators) {
$d = $violatorDetails[$ip]
Write-Log " - $ip ($($d.Count) attempts in $CheckMinutes min, users: $(($d.Users -split ', ' | Select-Object -Unique) -join ','))"
}
# --- БЛОКИРОВКА В FIREWALL ---
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if (-not $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Log "ERROR: Administrator rights required!"
exit 1
}
$rule = Get-NetFirewallRule -DisplayName $FirewallRuleName -ErrorAction SilentlyContinue
try {
if ($rule) {
Write-Log "Rule '$FirewallRuleName' found, updating..."
$addressFilter = $rule | Get-NetFirewallAddressFilter -ErrorAction SilentlyContinue
$currentIps = @()
if ($addressFilter) { $currentIps = @($addressFilter.RemoteAddress) }
# Объединяем старые + новые
$allIps = @()
foreach ($ip in ($currentIps + $violators)) {
if ($ip -and $allIps -notcontains $ip) { $allIps += $ip }
}
$rule | Remove-NetFirewallRule -ErrorAction Stop
New-NetFirewallRule -DisplayName $FirewallRuleName `
-Direction Inbound `
-Action Block `
-Enabled True `
-RemoteAddress $allIps `
-Profile Any `
-Description "Auto block brute-force (updated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss'))" `
-ErrorAction Stop
Write-Log "Rule recreated. Total IPs: $($allIps.Count)"
} else {
Write-Log "Rule not found, creating new..."
New-NetFirewallRule -DisplayName $FirewallRuleName `
-Direction Inbound `
-Action Block `
-Enabled True `
-RemoteAddress $violators `
-Profile Any `
-Description "Auto block brute-force (created: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss'))" `
-ErrorAction Stop
Write-Log "New rule created. Blocked: $($violators.Count)"
}
} catch {
Write-Log "ERROR with firewall: $_"
exit 1
}
# --- ЛОГИРОВАНИЕ В ФАЙЛ ---
$logDir = "C:\Logs"
if (-not (Test-Path $logDir)) {
try { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } catch {}
}
if (Test-Path $logDir) {
$logFile = "$logDir\BlockedIPs_5min.log"
foreach ($ip in $violators) {
$d = $violatorDetails[$ip]
$uniqueUsers = ($d.Users -split ', ' | Select-Object -Unique) -join ', '
$logMessage = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | BLOCKED IP: $ip | Attempts: $($d.Count) in $CheckMinutes min | Users: $uniqueUsers"
Add-Content -Path $logFile -Value $logMessage
}
Write-Log "Block info written to $logFile"
}
# --- ПРОВЕРКА ---
$verifyRule = Get-NetFirewallRule -DisplayName $FirewallRuleName -ErrorAction SilentlyContinue
if ($verifyRule) {
$verifyIPs = @((Get-NetFirewallAddressFilter -AssociatedObject $verifyRule).RemoteAddress)
Write-Log "Total IPs in rule: $($verifyIPs.Count)"
foreach ($ip in $violators) {
if ($verifyIPs -contains $ip) {
Write-Log " OK $ip - blocked"
} else {
Write-Log " FAIL $ip - NOT found in rule!"
}
}
} else {
Write-Log "Rule not found for verification!"
}
Write-Log "Blocked $($violators.Count) IPs for $BlockMinutes minutes (1 year)"
exit 0