RDP brute force engelleme: Windows Server'da PowerShell ile 4625 raporu ve saldırgan IP'leri otomatik engelleme
Windows Server'da RDP brute force engelleme için Event ID 4625 kayıtlarından kullanıcı, kaynak IP ve SubStatus raporu çıkarın. Eşiği aşan IP'leri beyaz liste korumasıyla Windows Güvenlik Duvarı'nda otomatik engelleyen, süre dolunca engeli kaldıran bir PowerShell betiği kurun.
sunucu.com.tr Ekibi14 dk okuma
RDP portu internete açık bir Windows Server'ın Güvenlik günlüğüne bakarsanız büyük olasılıkla dakikada onlarca Event ID 4625 kaydı görürsünüz. Bunlar administrator, admin, user gibi adlarla parola deneyen otomatik tarayıcılardır. RDP brute force engelleme için Linux'taki fail2ban'in yaptığını Windows'ta PowerShell ile yapmak mümkün: 4625 kayıtlarını okuyup eşiği aşan IP'leri Windows Güvenlik Duvarı'nda otomatik engellemek. Yazı, RDP'yi doğrudan açık tutmak zorunda olan sistem yöneticileri ve KOBİ BT sorumluları için hazırlandı.
Ön koşullar ve oturum açma denetimini etkinleştirme
İhtiyacınız olanlar:
- Windows Server 2016, 2019, 2022 ya da 2025 (betikler PowerShell 5.1 ile uyumlu olacak şekilde hazırlandı).
- Yerel yönetici yetkisi ve yükseltilmiş PowerShell oturumu.
- Sağlayıcınızın verdiği konsol erişimi (VNC, IPMI ya da web konsolu). Güvenlik duvarı kuralı yazacaksınız; bir hata yaparsanız RDP'ye değil konsola ihtiyacınız olacak.
4625 kaydı ancak başarısız oturum açma denetimi açıksa üretilir. Etki alanındaki sunucularda bunu Grup İlkesi ile yapın: Computer Configuration > Policies > Windows Settings > Security Settings > Advanced Audit Policy Configuration > Audit Policies > Logon/Logoff > Audit Logon altında "Failure" kutusunu işaretleyin. Tek başına duran bir sunucuda aşağıdaki betik aynı işi yerel olarak yapar.
Betik, "Logon" alt kategorisini Türkçe ya da İngilizce arayüzden bağımsız çalışsın diye GUID ile hedefler, başarılı ve başarısız denetimi açar ve Güvenlik günlüğünün boyutunu büyütür. Windows Server 2016 ve sonrası, PowerShell 5.1 için hazırlandı. Geri almak için auditpol /set /subcategory:"{0CCE9215-69AE-11D9-BED3-505054503030}" /failure:disable komutunu çalıştırmanız yeterli; günlük boyutu değişikliği zararsızdır.
# Oturum açma denetimini etkinleştirir (yükseltilmiş PowerShell gerekir)
# "Logon" alt kategorisi GUID ile hedeflenir; dil bağımsızdır.
$logonGuid = '{0CCE9215-69AE-11D9-BED3-505054503030}'
auditpol /set /subcategory:"$logonGuid" /success:enable /failure:enable
# Mevcut durumu göster
auditpol /get /subcategory:"$logonGuid"
# Saldırı yoğunken eski kayıtlar hızla silinmesin diye Güvenlik günlüğünü 512 MB yap
# (değer zaten büyükse küçültmez; mevcut değeri kontrol ediyoruz)
$log = Get-WinEvent -ListLog Security
if ($log.MaximumSizeInBytes -lt 512MB) {
wevtutil sl Security /ms:536870912
}
Etki alanına bağlı sunucuda Grup İlkesi yerel ayarı ezer. Değişiklik geri dönüyorsa ilkeyi GPO tarafında düzenleyin.
Event ID 4625 raporu: son 24 saatin başarısız oturum açmaları
Engellemeden önce ne olduğunu görmek gerekir. 4625 kaydının alanları sırayla dizinle de okunabilir, fakat alan adına göre XML'den okumak sürümler arasında daha güvenlidir. Kaydın alan listesi Microsoft'un 4625 olay belgesinde yer alır.
Aşağıdaki betik son 24 saatin 4625 kayıtlarını Get-WinEvent ile çeker, kullanıcı adı, kaynak IP, LogonType ve SubStatus alanlarını çıkarır, sonra IP bazında özet ve en çok denenen kullanıcı adlarını listeler. Hiçbir şey değiştirmez, yalnızca okur; geri alınacak bir adımı yoktur. Windows Server 2016 ile 2025 arası ve PowerShell 5.1 için hazırlandı.
# 4625 raporu: son 24 saat (yalnızca okur, değişiklik yapmaz)
$saat = 24
$baslangic = (Get-Date).AddHours(-$saat)
# SubStatus kodlarının Türkçe karşılıkları
$subStatusAciklama = @{
'0xc000006a' = 'Yanlış parola'
'0xc0000064' = 'Kullanıcı yok'
'0xc0000234' = 'Hesap kilitli'
'0xc0000072' = 'Hesap devre dışı'
'0xc0000071' = 'Parola süresi dolmuş'
'0xc0000193' = 'Hesap süresi dolmuş'
'0xc0000224' = 'Parola değişmeli'
'0xc000006f' = 'İzin verilen saat dışında'
'0xc0000070' = 'İş istasyonu kısıtlaması'
'0xc000015b' = 'Bu oturum türü için yetki yok'
}
$olaylar = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4625
StartTime = $baslangic
} -ErrorAction SilentlyContinue
if (-not $olaylar) { Write-Host "Son $saat saatte 4625 kaydı yok."; return }
$kayitlar = foreach ($o in $olaylar) {
$xml = [xml]$o.ToXml()
$d = @{}
foreach ($alan in $xml.Event.EventData.Data) { $d[$alan.Name] = $alan.'#text' }
$sub = ([string]$d['SubStatus']).ToLower()
[pscustomobject]@{
Zaman = $o.TimeCreated
Kullanici = $d['TargetUserName']
KaynakIP = ($d['IpAddress'] -replace '^::ffff:', '')
LogonType = $d['LogonType']
SubStatus = $sub
Aciklama = $(if ($subStatusAciklama.ContainsKey($sub)) { $subStatusAciklama[$sub] } else { 'Diğer' })
}
}
Write-Host "`n== IP bazında özet (ilk 20) =="
$kayitlar | Group-Object KaynakIP | Sort-Object Count -Descending | Select-Object -First 20 |
ForEach-Object {
[pscustomobject]@{
KaynakIP = $_.Name
Deneme = $_.Count
Kullanici = ($_.Group.Kullanici | Select-Object -Unique | Select-Object -First 5) -join ', '
LogonTypes = ($_.Group.LogonType | Select-Object -Unique) -join ','
Ilk = ($_.Group.Zaman | Measure-Object -Minimum).Minimum
Son = ($_.Group.Zaman | Measure-Object -Maximum).Maximum
}
} | Format-Table -AutoSize
Write-Host "== En çok denenen kullanıcı adları =="
$kayitlar | Group-Object Kullanici | Sort-Object Count -Descending | Select-Object -First 15 Count, Name | Format-Table -AutoSize
Write-Host "== SubStatus dağılımı =="
$kayitlar | Group-Object Aciklama | Sort-Object Count -Descending | Select-Object Count, Name | Format-Table -AutoSize
# İsterseniz tam listeyi CSV olarak saklayın:
# $kayitlar | Export-Csv -Path C:\Scripts\4625-rapor.csv -NoTypeInformation -Encoding UTF8
LogonType ve boş IP alanı
RDP denemeleri iki farklı LogonType ile görünebilir. Ağ Düzeyinde Kimlik Doğrulama (NLA) açıksa kimlik doğrulama oturum açılmadan önce yapılır ve başarısız denemeler çoğunlukla LogonType 3 (ağ) olarak düşer. NLA kapalıysa denemeler LogonType 10 (RemoteInteractive) olarak görünür. Yalnızca 10'u filtreleyen bir betik, NLA açık sunucuda saldırının büyük kısmını kaçırır.
Bazı durumlarda 4625 kaydında IpAddress alanı - olarak gelir. Böyle kayıtlar engellenemez; kaynak adresi için Microsoft-Windows-RemoteDesktopServices-RdpCoreTS/Operational günlüğündeki 140 numaralı olaya bakabilirsiniz. Betik - değerlerini zaten atlar.
SubStatus kodlarını okumak
SubStatus, denemenin neden başarısız olduğunu söyler ve saldırının niteliğini ayırt etmeye yarar:
- 0xC0000064, kullanıcı yok: Saldırgan kullanıcı adı tahmin ediyor. Bu kodun çokluğu tipik otomatik taramadır.
- 0xC000006A, yanlış parola: Kullanıcı adı doğru. Gerçek bir hesap hedefte demektir. Hangi hesap olduğuna bakın, parolası güçlü mü kontrol edin.
- 0xC0000234, hesap kilitli: Hesap kilitleme ilkesi devreye girmiş. Saldırı sürüyorsa meşru kullanıcı da giriş yapamaz; bu da bir tür hizmet kesintisidir.
- 0xC0000072, hesap devre dışı: Devre dışı hesaba deneme yapılıyor. Kullanıcı adınız dışarıda biliniyor olabilir.
Raporda kendi ofis IP'nizden gelen tekil 0xC000006A kayıtları normaldir, birinin parolasını yanlış yazmasıdır. Tek bir yabancı IP'den yüzlerce 0xC0000064 ise doğrudan engellenecek adaydır.
Eşiği aşan IP'leri otomatik engelleyen PowerShell betiği
Asıl betik şu işleri yapar:
- Son
PencereDakikaiçindeki 4625 kayıtlarından kaynak IP'leri toplar. Esiksayısına ulaşan IP'leri, beyaz listede değilse engel listesine ekler.- Engel listesini bir JSON durum dosyasında, eklenme zamanıyla saklar.
BanGunsüresi dolan IP'leri listeden çıkarır.- Listeyi yalnızca RDP portuna (TCP ve UDP 3389) uygulanan engelleme kurallarına yazar. Liste büyürse kuralları 500 adreslik parçalara böler, gereksiz kalan parçaları siler.
Kurallar New-NetFirewallRule ile oluşturulur ve RDP-Ban grubunda toplanır. Betik yalnızca bu gruptaki kurallara dokunur. Her çalıştırmada listeyi durum dosyasından baştan kurduğu için tekrar tekrar çalıştırmak güvenlidir.
Kendini kilitleme uyarısı: Windows Güvenlik Duvarı'nda engelleme kuralı izin kuralından önceliklidir. Kendi IP'niz bir kez engellenirse "izin ver" kuralı eklemek sizi kurtarmaz. Betiği çalıştırmadan önce bağlandığınız sabit IP'yi ve yönetim ağınızı
$BeyazListeiçine yazın. RDP erişiminizin kesilmesi ihtimaline karşı sağlayıcınızın konsol erişiminin çalıştığını önceden doğrulayın.
Windows Server 2016, 2019, 2022 ve 2025, PowerShell 5.1 için hazırlandı. Değiştirmeniz gerekenler: $BeyazListe içindeki belge amaçlı adresleri (192.0.2.10, 198.51.100.0/24) kendi adreslerinizle, RDP portunu değiştirdiyseniz $RdpPort değerini. Geri alma adımları bir sonraki bölümde.
# C:\Scripts\RdpBan\Invoke-RdpBan.ps1
# 4625 kayıtlarına göre RDP saldırgan IP'lerini Windows Güvenlik Duvarı'nda engeller.
#Requires -RunAsAdministrator
param(
[int]$Esik = 10, # Pencere içinde bu kadar başarısız deneme yapan IP engellenir
[int]$PencereDakika = 60, # Kaç dakikalık 4625 kaydına bakılacak
[int]$BanGun = 7, # Engel kaç gün sürecek
[int]$RdpPort = 3389, # RDP portu (değiştirdiyseniz güncelleyin)
[int]$ParcaBoyutu = 500, # Bir kuraldaki en fazla adres sayısı
[string]$KuralGrubu = 'RDP-Ban',
[string]$Klasor = 'C:\Scripts\RdpBan',
# ÖNEMLİ: Kendi yönetim IP'lerinizi ve ağlarınızı yazın. Örnek değerler belge amaçlıdır.
[string[]]$BeyazListe = @('192.0.2.10', '198.51.100.0/24', '127.0.0.1', '::1')
)
$ErrorActionPreference = 'Stop'
if (-not (Test-Path $Klasor)) { New-Item -Path $Klasor -ItemType Directory | Out-Null }
$DurumDosyasi = Join-Path $Klasor 'durum.json'
$GunlukDosyasi = Join-Path $Klasor 'rdpban.log'
function Write-Gunluk([string]$Mesaj) {
"{0:yyyy-MM-dd HH:mm:ss} {1}" -f (Get-Date), $Mesaj | Add-Content -Path $GunlukDosyasi -Encoding UTF8
}
# IP bir adrese ya da CIDR bloğuna dahil mi? (IPv4 ve IPv6)
function Test-IpAralikta([string]$Ip, [string]$Aralik) {
if ($Aralik -notmatch '/') { return $Ip -eq $Aralik }
$parca = $Aralik.Split('/')
$ag = [System.Net.IPAddress]::Parse($parca[0])
$bit = [int]$parca[1]
$adres = [System.Net.IPAddress]::Parse($Ip)
if ($adres.AddressFamily -ne $ag.AddressFamily) { return $false }
$a = $adres.GetAddressBytes(); $n = $ag.GetAddressBytes()
for ($i = 0; $i -lt $a.Length; $i++) {
if ($bit -ge 8) { $maske = 255 }
elseif ($bit -le 0) { $maske = 0 }
else { $maske = (0xFF -shl (8 - $bit)) -band 0xFF }
if (($a[$i] -band $maske) -ne ($n[$i] -band $maske)) { return $false }
$bit -= 8
}
return $true
}
function Test-BeyazListede([string]$Ip) {
foreach ($b in $BeyazListe) { if (Test-IpAralikta -Ip $Ip -Aralik $b) { return $true } }
return $false
}
# 1) Durum dosyasını yükle: IP -> eklenme zamanı (UTC)
$durum = @{}
if (Test-Path $DurumDosyasi) {
$icerik = Get-Content $DurumDosyasi -Raw
if ($icerik) {
foreach ($k in @($icerik | ConvertFrom-Json)) {
$durum[$k.Ip] = [datetime]::Parse($k.EklendiUtc, $null, 'RoundtripKind')
}
}
}
# 2) Son penceredeki 4625 kayıtlarından IP say
$olaylar = Get-WinEvent -FilterHashtable @{
LogName = 'Security'; Id = 4625; StartTime = (Get-Date).AddMinutes(-$PencereDakika)
} -ErrorAction SilentlyContinue
$sayac = @{}
foreach ($o in @($olaylar)) {
if (-not $o) { continue }
$xml = [xml]$o.ToXml()
$ip = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'IpAddress' }).'#text'
$ip = ([string]$ip) -replace '^::ffff:', ''
$tmp = $null
if (-not [System.Net.IPAddress]::TryParse($ip, [ref]$tmp)) { continue } # '-' ve boşları atla
$sayac[$ip] = 1 + [int]$sayac[$ip]
}
# 3) Eşiği aşanları ekle
$simdi = (Get-Date).ToUniversalTime()
foreach ($ip in $sayac.Keys) {
if ($sayac[$ip] -lt $Esik) { continue }
if (Test-BeyazListede $ip) { Write-Gunluk "Beyaz listede, atlandı: $ip ($($sayac[$ip]) deneme)"; continue }
if (-not $durum.ContainsKey($ip)) {
$durum[$ip] = $simdi
Write-Gunluk "Engellendi: $ip ($($sayac[$ip]) deneme / $PencereDakika dk)"
}
}
# 4) Süresi dolanları ve sonradan beyaz listeye alınanları çıkar
foreach ($ip in @($durum.Keys)) {
if ($durum[$ip] -lt $simdi.AddDays(-$BanGun) -or (Test-BeyazListede $ip)) {
$durum.Remove($ip)
Write-Gunluk "Engel kaldırıldı: $ip"
}
}
# 5) Durumu kaydet
@($durum.Keys | ForEach-Object { [pscustomobject]@{ Ip = $_; EklendiUtc = $durum[$_].ToString('o') } }) |
ConvertTo-Json | Set-Content -Path $DurumDosyasi -Encoding UTF8
# 6) Güvenlik duvarı kurallarını listeden yeniden kur (yalnızca RDP portu)
$ipler = @($durum.Keys | Sort-Object)
$gerekli = @()
for ($i = 0; $i -lt $ipler.Count; $i += $ParcaBoyutu) {
$son = [math]::Min($i + $ParcaBoyutu, $ipler.Count) - 1
$parca = @($ipler[$i..$son])
$no = [int][math]::Floor($i / $ParcaBoyutu) + 1
foreach ($protokol in 'TCP', 'UDP') {
$ad = "$KuralGrubu-$protokol-$no"
$gerekli += $ad
if (Get-NetFirewallRule -Name $ad -ErrorAction SilentlyContinue) {
Set-NetFirewallRule -Name $ad -RemoteAddress $parca
} else {
New-NetFirewallRule -Name $ad -DisplayName $ad -Group $KuralGrubu `
-Direction Inbound -Action Block -Protocol $protokol -LocalPort $RdpPort `
-RemoteAddress $parca -Profile Any | Out-Null
}
}
}
# Artık gerekmeyen parça kurallarını sil (yalnızca bu grupta)
Get-NetFirewallRule -Group $KuralGrubu -ErrorAction SilentlyContinue |
Where-Object { $gerekli -notcontains $_.Name } |
Remove-NetFirewallRule
Write-Gunluk "Çalıştı: toplam engelli IP $($ipler.Count)"
İlk çalıştırmayı elle yapın ve sonucu kontrol edin:
# Betiği bir kez elle çalıştır ve sonucu gözden geçir
powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\RdpBan\Invoke-RdpBan.ps1
Get-Content C:\Scripts\RdpBan\rdpban.log -Tail 20
Betiği 4625 olayında otomatik tetikleme
Zamanlanmış görev iki tetikleyiciyle çalışır: her 4625 olayında anında, ayrıca süresi dolan engellerin kaldırılması için 15 dakikada bir. IgnoreNew ayarı, saldırı yoğunken aynı anda onlarca kopyanın başlamasını önler; bir çalışma bitmeden gelen tetiklemeler atlanır, bir sonraki çalışma zaten tüm pencereyi okur.
Windows Server 2016 ve sonrası, PowerShell 5.1 için hazırlandı. -Force sayesinde tekrar çalıştırıldığında görevi günceller. Geri almak için: Unregister-ScheduledTask -TaskName 'RDP-Ban' -Confirm:$false.
# RDP-Ban zamanlanmış görevini oluşturur ya da günceller
$betik = 'C:\Scripts\RdpBan\Invoke-RdpBan.ps1'
$eylem = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$betik`""
# Tetikleyici 1: Güvenlik günlüğüne 4625 düştüğünde
$sinif = Get-CimClass -ClassName MSFT_TaskEventTrigger -Namespace Root/Microsoft/Windows/TaskScheduler
$olayTetik = New-CimInstance -CimClass $sinif -ClientOnly
$olayTetik.Enabled = $true
$olayTetik.Subscription = '<QueryList><Query Id="0" Path="Security"><Select Path="Security">*[System[(EventID=4625)]]</Select></Query></QueryList>'
# Tetikleyici 2: 15 dakikada bir (süresi dolan engelleri temizlemek için)
$periyodik = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) `
-RepetitionInterval (New-TimeSpan -Minutes 15)
$ayarlar = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew `
-ExecutionTimeLimit (New-TimeSpan -Minutes 10) -StartWhenAvailable
$hesap = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName 'RDP-Ban' -Action $eylem -Trigger @($olayTetik, $periyodik) `
-Settings $ayarlar -Principal $hesap -Description '4625 tabanlı RDP IP engelleme' -Force
Doğrulama
Kurulumun çalıştığını şu adımlarla kontrol edin:
# Görev durumu ve son çalışma sonucu (0 başarılı demektir)
Get-ScheduledTaskInfo -TaskName 'RDP-Ban' | Select-Object LastRunTime, LastTaskResult
# Oluşan kurallar ve her kuraldaki adres sayısı
Get-NetFirewallRule -Group 'RDP-Ban' | ForEach-Object {
$adres = ($_ | Get-NetFirewallAddressFilter).RemoteAddress
[pscustomobject]@{ Kural = $_.Name; Etkin = $_.Enabled; AdresSayisi = @($adres).Count }
}
# Belirli bir IP engelli mi? (belge amaçlı örnek adres)
$aranan = '203.0.113.50'
(Get-Content C:\Scripts\RdpBan\durum.json -Raw | ConvertFrom-Json) | Where-Object Ip -eq $aranan
Gerçek bir test için beyaz listede olmayan bir makineden yanlış parolayla eşik kadar deneme yapın. Birkaç saniye içinde bağlantı zaman aşımına düşmeli ve günlükte "Engellendi" satırı görünmeli. Rapor betiğini bir gün sonra yeniden çalıştırdığınızda 4625 sayısının belirgin biçimde düştüğünü görmelisiniz.
Eski engelleri temizleme ve kural boyutunu yönetme
Engeller BanGun sonunda otomatik kalkar. Taramaların büyük kısmı dönen IP havuzlarından gelir; bir adresi süresiz engellemek listeyi şişirir ama koruma pek artırmaz. Yedi gün makul bir başlangıçtır.
Tek kuraldaki adres sayısı arttıkça kural düzenleme yavaşlar, bu yüzden betik listeyi 500 adreslik parçalara böler. Listeniz sürekli binleri buluyorsa sorun betikte değildir: RDP'nin internete bu kadar açık kalmaması gerekir (aşağıdaki bölüme bakın).
Tek bir IP'nin engelini elle kaldırmak için durum dosyasını düzenleyip betiği yeniden çalıştırın:
# Tek IP'nin engelini kaldırır (örnek: belge amaçlı adres)
$kaldir = '203.0.113.50'
$dosya = 'C:\Scripts\RdpBan\durum.json'
Copy-Item $dosya "$dosya.yedek" -Force # önce yedek
$liste = @(Get-Content $dosya -Raw | ConvertFrom-Json) | Where-Object Ip -ne $kaldir
@($liste) | ConvertTo-Json | Set-Content $dosya -Encoding UTF8
powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\RdpBan\Invoke-RdpBan.ps1
Kurulumu tamamen geri almak için görevi kaldırın, yalnızca RDP-Ban grubundaki kuralları silin ve durum dosyasını yedekleyip kaldırın:
# Tam geri alma: yalnızca bu çözümün oluşturduğu nesneler
Unregister-ScheduledTask -TaskName 'RDP-Ban' -Confirm:$false -ErrorAction SilentlyContinue
Get-NetFirewallRule -Group 'RDP-Ban' -ErrorAction SilentlyContinue | Remove-NetFirewallRule
$dosya = 'C:\Scripts\RdpBan\durum.json'
if (Test-Path $dosya) {
Copy-Item $dosya "$dosya.yedek" -Force
Remove-Item -Path $dosya
}
Sık yapılan hatalar
Kendini kilitlemek. En sık sorun budur. Evden dinamik IP ile bağlanan bir yönetici parolasını birkaç kez yanlış yazar ve engellenir. Sabit IP yoksa eşiği düşük tutmayın, yönetim için VPN kullanın ve konsol erişimini her zaman hazır bulundurun.
Yalnızca LogonType 10'a bakmak. NLA açık sunucuda denemeler LogonType 3 olarak gelir. Bu betik LogonType filtrelemez. Aynı sunucuda SMB ya da başka bir ağ hizmeti de dışarı açıksa, oradan gelen 4625'ler de sayılır; bu çoğu zaman istenen bir davranıştır.
Tüm portları engellemek. Engelleme kuralını -LocalPort olmadan yazmak, aynı IP'den gelen meşru web ya da e-posta trafiğini de keser. Paylaşılan IP arkasındaki bir müşteri yanlışlıkla engellendiğinde sitenize de ulaşamaz. Betik yalnızca RDP portunu kapatır.
İzin kuralıyla beyaz liste yapmaya çalışmak. Windows Güvenlik Duvarı'nda engelleme kuralı kazanır. Beyaz liste mutlaka betiğin içinde uygulanmalı, betik de bunu yapıyor.
RDP portunu değiştirip betiği güncellememek. Port farklıysa $RdpPort değerini değiştirin, yoksa kurallar boş bir portu engeller.
Betiğin ötesinde: kalıcı RDP güvenliği
IP engelleme bir yan önlemdir; dağıtık botnetlere karşı tek başına yetmez. Asıl korumayı şu katmanlar sağlar:
- NLA açık olsun. Kimlik doğrulama oturum açılmadan yapılır, sunucu kaynağı daha az harcanır.
- Hesap kilitleme ilkesi. Örneğin
net accounts /lockoutthreshold:10 /lockoutwindow:15 /lockoutduration:15. Yerleşik Administrator hesabı varsayılanda kilitleme dışında kalabilir; adını değiştirin ya da devre dışı bırakıp ayrı bir yönetici hesabı kullanın. - RDP'yi IP ile kısıtlama. Ofisiniz sabit IP'liyse varsayılan "Remote Desktop" izin kurallarını yalnızca bu adreslere daraltmak, betikten çok daha etkilidir.
- VPN ya da RD Gateway. RDP portunu internete hiç açmamak en iyisidir. RD Gateway kurulumunu Windows Server 2025'te RDS ve RD Gateway rehberinde adım adım anlattık.
Çok kullanıcılı bir uzak masaüstü ortamını kendiniz yönetmek yerine hazır bir terminal sunucu çözümü de değerlendirilebilir. Test için ayrı bir makineye ihtiyacınız varsa aynı adımları bir bulut sunucu üzerinde deneyebilirsiniz.
Sık sorulan sorular
Bu betik fail2ban'in tam karşılığı mı?
Temel mantık aynı: günlüğü okur, eşiği aşan IP'yi belirli bir süre engeller, süre dolunca kaldırır. fail2ban birçok hizmet için hazır filtre sunar; bu betik yalnızca 4625 kayıtlarına ve RDP portuna odaklanır. Başka hizmetler için olay kimliğini ve portu uyarlamanız gerekir.
Eşik ve pencere değerlerini nasıl seçmeliyim?
60 dakikada 10 deneme, insan hatalarını tolere edip otomatik taramaları yakalayan dengeli bir başlangıçtır. Rapor betiğinin çıktısına bakın: saldırgan IP'ler genellikle yüzlerce deneme yapar, meşru kullanıcılar birkaç tane. Kendi adresleriniz beyaz listedeyse eşiği 5'e çekebilirsiniz.
4625 kayıtlarında kaynak IP neden boş geliyor?
Bazı NLA senaryolarında ve yerel denemelerde IpAddress alanı - olarak kaydedilir. Betik bu kayıtları atlar. Boş IP'li kayıtlar çoksa RdpCoreTS Operational günlüğündeki 140 olayını inceleyin; kaynak adres orada görünür.
Engellenen IP'ler sunucu yeniden başlayınca kalır mı?
Evet. Güvenlik duvarı kuralları kalıcıdır, durum dosyası da diskte durur. Yeniden başlatmadan sonra zamanlanmış görev ilk tetiklemede listeyi yine doğrular ve süresi dolanları kaldırır.
Etki alanı denetleyicisinde kullanabilir miyim?
Teknik olarak çalışır, fakat etki alanı denetleyicisinde 4625 kayıtları istemcilerin parola hatalarını da içerir ve iç ağdaki makineler engellenebilir. İç ağ bloklarınızı mutlaka beyaz listeye ekleyin. En doğrusu, etki alanı denetleyicisinin RDP portunu internete hiç açmamaktır.
Sonuç
Önce oturum açma denetimini açın ve rapor betiğiyle saldırının boyutunu görün. Ardından beyaz listenizi doldurup engelleme betiğini elle çalıştırın, sonra zamanlanmış göreve bağlayın. Böylece RDP brute force denemelerinin büyük kısmı birkaç denemeden sonra güvenlik duvarında durur. Kalıcı çözüm ise RDP'yi VPN ya da RD Gateway arkasına almak ve portu internete kapatmaktır. Betik bu geçiş tamamlanana kadar sunucuyu korur.
- #rdp
- #powershell
- #event id 4625
- #windows güvenlik duvarı
- #brute force
- #windows server
- #fail2ban alternatifi

