PowerShell ile Windows Güvenlik Denetimi: Kapsamlı Rehber
Güvenlik denetimi denince aklıma hep şu senaryo geliyor: Saat gece 02:00, telefon çalıyor, “sistemde garip bir şeyler var” diyor müdürün. Sen de oturup elle tek tek log dosyalarına bakıyorsun. Oysa PowerShell ile bu işlerin büyük çoğunluğunu otomatize edebilir, hatta bir güvenlik denetim scripti yazıp düzenli çalıştırabilirsin. Bu yazıda tam olarak bunu yapacağız.
Neden PowerShell ile Güvenlik Denetimi?
Windows ortamında güvenlik denetimi için piyasada onlarca araç var. Ama PowerShell’in rakip tanımayan birkaç avantajı var. Öncelikle native çalışıyor, yani ek bir yazılım kurmanı gerektirmiyor. Her modern Windows sistemde hazır bulunuyor. Bunun yanında WMI, COM, .NET ve Windows API’lerine doğrudan erişim sağlıyor. Sonuçları CSV, JSON, HTML gibi formatlarda raporlayabiliyorsun. Ve tabii ki her şeyi otomatize edip zamanlanmış görev olarak çalıştırabiliyorsun.
Ben kişisel olarak büyük şirket ortamlarında Nessus, Qualys gibi araçlarla çalışmış biri olarak söylüyorum: Özel ihtiyaçlar için PowerShell scriptleri bu araçları bile geride bırakıyor. O araçlar sana hazır template veriyor, ama senin ortamına özel kontroller için script yazmak zorunda kalıyorsun zaten.
Temel Hazırlık ve Execution Policy
Başlamadan önce PowerShell execution policy ayarlarını bilmen gerekiyor. Varsayılan olarak Windows sistemlerde script çalıştırma kısıtlıdır.
# Mevcut execution policy'yi kontrol et
Get-ExecutionPolicy -List
# Güvenlik denetim scriptleri için RemoteSigned önerilir
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# Sadece o oturum için geçici olarak değiştirmek istersen
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
Bir de şunu belirteyim: Denetim scriptlerini her zaman Administrator yetkisiyle çalıştır. Bazı güvenlik bilgilerine erişmek için yüksek yetki şart.
# PowerShell'i admin olarak başlatmak için
Start-Process powershell -Verb RunAs
# Script içinde admin kontrolü yapmak için
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if (-not $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Error "Bu script Administrator yetkisi gerektiriyor!"
exit 1
}
Kullanıcı Hesapları ve Grup Üyelikleri Denetimi
İlk yapman gereken şey sistemdeki kullanıcı hesaplarına bakmak. Özellikle aktif olmayan, şifresi hiç dolmayan veya beklenmedik şekilde admin grubuna eklenmiş hesaplar alarm zilleri çaldırmalı.
# Tüm yerel kullanıcıları ve önemli özelliklerini listele
Get-LocalUser | Select-Object Name, Enabled, LastLogon, PasswordExpires, PasswordLastSet, Description |
Sort-Object Enabled -Descending |
Format-Table -AutoSize
# 90 günden fazla giriş yapmamış aktif hesapları bul
$threshold = (Get-Date).AddDays(-90)
Get-LocalUser | Where-Object {
$_.Enabled -eq $true -and
$_.LastLogon -lt $threshold -and
$_.LastLogon -ne $null
} | Select-Object Name, LastLogon, Description
# Hiç giriş yapmamış aktif hesapları bul (potansiyel ghost account)
Get-LocalUser | Where-Object {
$_.Enabled -eq $true -and
$_.LastLogon -eq $null
} | Select-Object Name, PasswordLastSet, Description
# Administrators grubundaki tüm üyeleri listele
Get-LocalGroupMember -Group "Administrators" |
Select-Object Name, ObjectClass, PrincipalSource
# Remote Desktop Users grubunu kontrol et
Get-LocalGroupMember -Group "Remote Desktop Users" |
Select-Object Name, ObjectClass, PrincipalSource
Bu scriptleri çalıştırdığında genellikle şu sürprizlerle karşılaşırsın: Yıllar önce işten ayrılan birinin hesabı hala aktif, ya da birisi test amaçlı oluşturduğu bir hesabı admin grubuna eklemiş ve unutmuş. Bunlar küçük görünen ama büyük risk taşıyan bulgulardır.
Şifre Politikası Kontrolü
# Yerel şifre politikasını görüntüle
net accounts
# PowerShell ile daha detaylı bilgi
$passwordPolicy = Get-LocalUser | Select-Object Name, PasswordNeverExpires, PasswordRequired
# Şifresi hiç expire olmayan hesapları listele (servis hesapları hariç tutulmalı)
Get-LocalUser | Where-Object {
$_.PasswordNeverExpires -eq $true -and
$_.Enabled -eq $true
} | Select-Object Name, Description, LastLogon
Açık Portlar ve Ağ Bağlantıları Denetimi
Sisteminizde hangi portların dinlediğini ve hangi uygulamaların ağ bağlantısı kurduğunu bilmek kritik. Bir zararlı yazılım genellikle kendini bir servis olarak gizler ve ağ bağlantısı kurar.
# Tüm dinleyen portları ve ilgili işlemleri göster
Get-NetTCPConnection -State Listen |
Select-Object LocalAddress, LocalPort, State,
@{Name="ProcessName"; Expression={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).Name}},
@{Name="ProcessPath"; Expression={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).Path}} |
Sort-Object LocalPort |
Format-Table -AutoSize
# Şüpheli dış bağlantıları kontrol et (ESTABLISHED durumundaki)
Get-NetTCPConnection -State Established |
Where-Object { $_.RemoteAddress -ne "127.0.0.1" -and $_.RemoteAddress -ne "::1" } |
Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort,
@{Name="ProcessName"; Expression={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).Name}} |
Format-Table -AutoSize
# Bilinen tehlikeli portları kontrol et
$suspiciousPorts = @(4444, 5555, 6666, 7777, 8888, 9999, 31337, 12345)
Get-NetTCPConnection | Where-Object { $_.LocalPort -in $suspiciousPorts -or $_.RemotePort -in $suspiciousPorts } |
Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort, State,
@{Name="Process"; Expression={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).Name}}
Bu portlar listesinde 4444 görürsen dur bir dakika. Metasploit’in varsayılan dinleme portu 4444’tür. Tabi meşru uygulamalar da bu portları kullanabilir ama her halukarda inceleme gerektirir.
Windows Servisler ve Zamanlanmış Görevler Denetimi
Zararlı yazılımların en sevdiği ısrarcılık (persistence) yöntemlerinden ikisi Windows servisleri ve zamanlanmış görevlerdir.
# Çalışan tüm servisleri ve dosya yollarını listele
Get-WmiObject Win32_Service |
Where-Object { $_.State -eq "Running" } |
Select-Object Name, DisplayName, State, StartMode, PathName, StartName |
Sort-Object Name |
Format-Table -AutoSize -Wrap
# İmzasız veya şüpheli yollarda çalışan servisleri bul
Get-WmiObject Win32_Service |
Where-Object { $_.State -eq "Running" -and $_.PathName -notlike "*system32*" -and $_.PathName -notlike "*Program Files*" } |
Select-Object Name, PathName, StartName
# SYSTEM yetkisiyle çalışan servisler (bunlar yüksek riskli)
Get-WmiObject Win32_Service |
Where-Object { $_.StartName -eq "LocalSystem" -and $_.State -eq "Running" } |
Select-Object Name, PathName | Sort-Object Name
# Zamanlanmış görevleri denetle
Get-ScheduledTask |
Where-Object { $_.State -ne "Disabled" } |
Select-Object TaskName, TaskPath, State,
@{Name="Action"; Expression={($_.Actions | Select-Object -First 1).Execute}},
@{Name="LastRunTime"; Expression={(Get-ScheduledTaskInfo -TaskName $_.TaskName -TaskPath $_.TaskPath -ErrorAction SilentlyContinue).LastRunTime}} |
Where-Object { $_.Action -notlike "*system32*" -and $_.Action -ne $null } |
Format-Table -AutoSize -Wrap
# Şüpheli script çalıştıran zamanlanmış görevler
Get-ScheduledTask | ForEach-Object {
$task = $_
$task.Actions | Where-Object {
$_.Execute -like "*.ps1*" -or
$_.Execute -like "*.bat*" -or
$_.Execute -like "*.vbs*" -or
$_.Execute -like "*cmd.exe*"
} | ForEach-Object {
[PSCustomObject]@{
TaskName = $task.TaskName
TaskPath = $task.TaskPath
Action = $_.Execute
Arguments = $_.Arguments
}
}
}
Windows Event Log Güvenlik Analizi
Event loglar güvenlik olaylarının altın madenidir. Özellikle Security log’undaki başarısız giriş denemeleri, yetki yükseltme girişimleri ve hesap değişiklikleri kritik bilgiler içerir.
# Son 24 saatteki başarısız giriş denemelerini say (Event ID 4625)
$startTime = (Get-Date).AddHours(-24)
$failedLogins = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4625
StartTime = $startTime
} -ErrorAction SilentlyContinue
Write-Host "Son 24 saatteki başarısız giriş denemesi: $($failedLogins.Count)"
# Hangi hesaplara başarısız giriş yapılmış
$failedLogins | ForEach-Object {
$xml = [xml]$_.ToXml()
$targetUser = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq "TargetUserName" })."#text"
$sourceIP = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq "IpAddress" })."#text"
[PSCustomObject]@{
Time = $_.TimeCreated
TargetUser = $targetUser
SourceIP = $sourceIP
}
} | Group-Object SourceIP | Sort-Object Count -Descending |
Select-Object Count, Name | Format-Table -AutoSize
# Başarılı giriş olaylarını listele (Event ID 4624)
Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4624
StartTime = $startTime
} -ErrorAction SilentlyContinue |
Select-Object -First 20 TimeCreated, Message | Format-List
# Yeni kullanıcı oluşturma olayları (Event ID 4720)
Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4720
StartTime = (Get-Date).AddDays(-7)
} -ErrorAction SilentlyContinue | Select-Object TimeCreated, Message
Bir sistemde saatte 100’den fazla Event ID 4625 görüyorsan bu büyük ihtimalle brute force saldırısıdır. Bu tür anomalileri tespit etmek için bu scripti düzenli çalıştırman gerekiyor.
Kayıt Defteri (Registry) Güvenlik Kontrolü
Registry, Windows’un sinir sistemidir ve kötü amaçlı yazılımlar buraya bayılır. Özellikle startup key’leri sürekli hedef alınır.
# Tüm otomatik başlangıç registry konumlarını kontrol et
$autorunPaths = @(
"HKLM:SOFTWAREMicrosoftWindowsCurrentVersionRun",
"HKLM:SOFTWAREMicrosoftWindowsCurrentVersionRunOnce",
"HKCU:SOFTWAREMicrosoftWindowsCurrentVersionRun",
"HKCU:SOFTWAREMicrosoftWindowsCurrentVersionRunOnce",
"HKLM:SOFTWAREWOW6432NodeMicrosoftWindowsCurrentVersionRun"
)
foreach ($path in $autorunPaths) {
if (Test-Path $path) {
Write-Host "`n=== $path ===" -ForegroundColor Yellow
Get-ItemProperty -Path $path |
Select-Object -Property * -ExcludeProperty PS* |
Format-List
}
}
# UAC ayarlarını kontrol et
$uacPath = "HKLM:SOFTWAREMicrosoftWindowsCurrentVersionPoliciesSystem"
$uacSettings = Get-ItemProperty -Path $uacPath
Write-Host "EnableLUA: $($uacSettings.EnableLUA)"
Write-Host "ConsentPromptBehaviorAdmin: $($uacSettings.ConsentPromptBehaviorAdmin)"
# RDP etkin mi kontrol et
$rdpPath = "HKLM:SystemCurrentControlSetControlTerminal Server"
$rdpStatus = Get-ItemProperty -Path $rdpPath -Name "fDenyTSConnections"
if ($rdpStatus.fDenyTSConnections -eq 0) {
Write-Host "UYARI: RDP aktif durumda!" -ForegroundColor Red
} else {
Write-Host "RDP devre dışı" -ForegroundColor Green
}
Firewall Durumu ve Kuralları Denetimi
# Firewall profillerinin durumunu kontrol et
Get-NetFirewallProfile | Select-Object Name, Enabled, DefaultInboundAction, DefaultOutboundAction
# Gelen trafiğe izin veren kuralları listele
Get-NetFirewallRule |
Where-Object { $_.Direction -eq "Inbound" -and $_.Action -eq "Allow" -and $_.Enabled -eq "True" } |
Select-Object DisplayName, LocalPort, RemoteAddress, Profile |
Sort-Object LocalPort |
Format-Table -AutoSize
# Şüpheli, 3. parti tarafından eklenen firewall kurallarını bul
Get-NetFirewallRule |
Where-Object {
$_.Direction -eq "Inbound" -and
$_.Action -eq "Allow" -and
$_.Enabled -eq "True" -and
$_.Owner -ne "Microsoft.Windows.Defender" -and
$_.Owner -ne $null
} |
Select-Object DisplayName, Owner, LocalPort, RemoteAddress |
Format-Table -AutoSize
Kapsamlı Güvenlik Raporu Oluşturma
Şimdiye kadar öğrendiklerini bir araya getirip HTML rapor üreten bir script yazalım. Bu gerçek dünyada kullandığım bir yaklaşım.
# Kapsamlı güvenlik raporu oluştur
function New-SecurityAuditReport {
param(
[string]$OutputPath = "C:SecurityAuditReport_$(Get-Date -Format 'yyyyMMdd_HHmm').html"
)
# Çıktı dizinini oluştur
$outputDir = Split-Path $OutputPath
if (-not (Test-Path $outputDir)) {
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
}
$report = @()
$report += "<html><head><title>Güvenlik Denetim Raporu</title>"
$report += "<style>body{font-family:Arial;} table{border-collapse:collapse;width:100%} td,th{border:1px solid #ddd;padding:8px;} tr:nth-child(even){background:#f2f2f2} .warn{color:orange} .danger{color:red} .ok{color:green}</style></head><body>"
$report += "<h1>Güvenlik Denetim Raporu - $(Get-Date -Format 'dd/MM/yyyy HH:mm')</h1>"
$report += "<h2>Sistem: $env:COMPUTERNAME</h2>"
# Administrators grubu
$report += "<h3>Administrators Grubu Üyeleri</h3><ul>"
Get-LocalGroupMember -Group "Administrators" | ForEach-Object {
$report += "<li>$($_.Name) - $($_.ObjectClass)</li>"
}
$report += "</ul>"
# Aktif kullanıcılar
$report += "<h3>Aktif Kullanıcı Hesapları</h3><ul>"
Get-LocalUser | Where-Object { $_.Enabled -eq $true } | ForEach-Object {
$lastLogon = if ($_.LastLogon) { $_.LastLogon.ToString("dd/MM/yyyy") } else { "Hiç giriş yok" }
$report += "<li>$($_.Name) - Son giriş: $lastLogon</li>"
}
$report += "</ul>"
# Firewall durumu
$report += "<h3>Firewall Durumu</h3><ul>"
Get-NetFirewallProfile | ForEach-Object {
$status = if ($_.Enabled) { "<span class='ok'>Aktif</span>" } else { "<span class='danger'>DEVRE DIŞI</span>" }
$report += "<li>$($_.Name): $status</li>"
}
$report += "</ul>"
$report += "</body></html>"
$report | Out-File -FilePath $OutputPath -Encoding UTF8
Write-Host "Rapor oluşturuldu: $OutputPath" -ForegroundColor Green
return $OutputPath
}
# Raporu çalıştır
New-SecurityAuditReport
Otomatik Denetim için Zamanlanmış Görev Kurulumu
Bu scriptleri elle çalıştırmak yerine zamanlanmış görev olarak ayarlayabilirsin.
# Güvenlik denetim scriptini haftalık çalışacak şekilde ayarla
$taskAction = New-ScheduledTaskAction -Execute "PowerShell.exe" `
-Argument "-NonInteractive -ExecutionPolicy Bypass -File C:ScriptsSecurityAudit.ps1"
$taskTrigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At "06:00AM"
$taskSettings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 1) `
-RestartCount 3 `
-RestartInterval (New-TimeSpan -Minutes 10)
$taskPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask `
-TaskName "WeeklySecurityAudit" `
-TaskPath "SecurityScripts" `
-Action $taskAction `
-Trigger $taskTrigger `
-Settings $taskSettings `
-Principal $taskPrincipal `
-Description "Haftalık güvenlik denetim scripti"
Write-Host "Zamanlanmış görev oluşturuldu." -ForegroundColor Green
Gerçek Dünya Senaryosu: Şüpheli Aktivite Tespiti
Bir müşteride yaşadığım gerçek bir olayı paylaşayım. Sunucudan dışarıya olağandışı trafik gidiyordu. Şu basit sorgu durumu netleştirdi:
# Alışılmadık dış bağlantıları ve süreçleri ilişkilendir
$connections = Get-NetTCPConnection -State Established |
Where-Object {
$_.RemoteAddress -notlike "10.*" -and
$_.RemoteAddress -notlike "192.168.*" -and
$_.RemoteAddress -notlike "172.16.*" -and
$_.RemoteAddress -ne "127.0.0.1"
}
foreach ($conn in $connections) {
$process = Get-Process -Id $conn.OwningProcess -ErrorAction SilentlyContinue
$processPath = if ($process) { $process.Path } else { "Bilinmiyor" }
[PSCustomObject]@{
RemoteIP = $conn.RemoteAddress
RemotePort = $conn.RemotePort
LocalPort = $conn.LocalPort
ProcessName = if ($process) { $process.Name } else { "N/A" }
ProcessPath = $processPath
ProcessStartTime = if ($process) { $process.StartTime } else { "N/A" }
}
} | Format-Table -AutoSize
Bu sorgu sonucunda svchost.exe olarak görünen ama C:UsersPublicDownloads altında çalışan bir süreç ortaya çıktı. Meşru svchost.exe her zaman C:WindowsSystem32 altında bulunur. Bu şüpheli dosyayı izole edip incelediğimizde bir RAT (Remote Access Trojan) olduğunu tespit ettik.
Sonuç
PowerShell ile güvenlik denetimi, Windows ortamlarında en pratik ve erişilebilir yaklaşımlardan biri. Bu yazıda ele aldığımız konuları özetlemek gerekirse:
- Kullanıcı hesapları: Aktif olmayan, admin grubundaki beklenmedik ve şifresi hiç expire olmayan hesapları düzenli kontrol et
- Ağ bağlantıları: Sisteminizden dışarıya giden tüm bağlantıları ve bu bağlantıları kuran süreçleri izle
- Servisler ve zamanlanmış görevler: Sistem dışı dizinlerde çalışan her şeyi şüpheyle karşıla
- Event loglar: Başarısız giriş denemeleri ve hesap değişiklikleri sessiz alarmların başında gelir
- Registry autorun: Başlangıç noktaları kötü amaçlı yazılımların vazgeçilmez adresidir
- Firewall kuralları: Kim, ne zaman, hangi kuralı eklemiş takip etmezsen kapıları kim açmış bilemezsin
Bu scriptleri bir başlangıç noktası olarak kullan, kendi ortamına göre uyarla. Örneğin kurumsal ortamlarda domain controller’dan Active Directory sorguları eklemek, SIEM sistemlerine log göndermek ya da Slack veya Teams’e bildirim atmak için bu scripti genişletebilirsin.
En önemli nokta şu: Güvenlik denetimi tek seferlik bir iş değildir. Bu scriptleri zamanlanmış görev olarak çalıştır, raporları sakla ve önceki raporlarla karşılaştır. Bir güvenlik ihlalinin en net işareti çoğu zaman normalden sapma olduğu için tarihsel veri altın değer taşır.
