Windows Sistemde Dosya Bütünlüğü İzleme
Bir sistem yöneticisi olarak en çok korktuğun şeylerden biri, sisteminizde bir şeylerin değiştiğini ama bunu zamanında fark edemediğinizi sonradan anlamaktır. Dosya bütünlüğü izleme (File Integrity Monitoring – FIM) tam da bu sorunu çözmek için var. Windows ortamlarında bu konuya yeterince önem verilmediğini sık sık görüyorum ve bu yazıda hem teorik altyapıyı hem de sahada kullanabileceğiniz pratik araçları ve teknikleri ele alacağım.
Dosya Bütünlüğü İzleme Neden Bu Kadar Kritik?
Düşünün, bir saldırgan sisteminize sızdı. İlk yapacağı şeylerden biri kalıcılık sağlamak ve izlerini örtmektir. Bunun için ya sistem dosyalarını değiştirecek, ya arka kapı (backdoor) ekleyecek ya da konfigürasyon dosyalarını manipüle edecektir. Eğer bu değişiklikleri gerçek zamanlı veya düzenli aralıklarla takip etmiyorsanız, saldırı haftalar hatta aylar sonra fark edilebilir.
PCI DSS, HIPAA, SOX gibi uyumluluk standartları da FIM’i zorunlu kılmaktadır. Dolayısıyla bu konu hem güvenlik hem de yasal uyumluluk açısından kritiktir.
Windows üzerinde FIM için birkaç farklı yaklaşım var:
- Yerel Windows araçları (Windows yerleşik özellikler)
- PowerShell tabanlı özel çözümler
- Üçüncü parti araçlar (OSSEC, Wazuh, Tripwire)
- SIEM entegrasyonu
Bu yazıda önce temel kavramları ele alacak, ardından gerçek sahada kullanabileceğiniz kod örnekleri ile ilerliyeceğiz.
Windows Audit Policy ile Temel İzleme
Windows’un kendi içinde güçlü bir denetim mekanizması var. Öncelikle Audit Object Access politikasını etkinleştirmeniz gerekiyor.
Grup İlkesi üzerinden yapabilirsiniz ama komut satırından yapmak çok daha hızlı ve script edilebilir:
# Audit Policy'yi etkinleştir
auditpol /set /subcategory:"File System" /success:enable /failure:enable
# Mevcut durumu kontrol et
auditpol /get /subcategory:"File System"
# Object Access genel durumunu gör
auditpol /get /category:"Object Access"
Bu komutu çalıştırdıktan sonra, izlemek istediğiniz klasör veya dosyaların ACL’lerine (Access Control List) denetim girdisi eklemeniz gerekiyor. Bunu PowerShell ile şöyle yapabilirsiniz:
# Belirli bir dizine audit kuralı ekle
$acl = Get-Acl "C:CriticalFiles"
$auditRule = New-Object System.Security.AccessControl.FileSystemAuditRule(
"Everyone",
"Write,Delete,ChangePermissions",
"ContainerInherit,ObjectInherit",
"None",
"Success,Failure"
)
$acl.AddAuditRule($auditRule)
Set-Acl "C:CriticalFiles" $acl
Write-Host "Audit rule added successfully"
Bu ayarlardan sonra Windows Event Log’da 4663 (dosyaya erişim), 4656 (dosya handle açıldı), 4670 (izinler değiştirildi) gibi event ID’leri görmeye başlarsınız.
PowerShell ile Hash Bazlı Bütünlük Kontrolü
Hash karşılaştırması, FIM’in temel taşıdır. Bir dosyanın içeriği değiştiğinde hash değeri de değişir. Bu yöntemi PowerShell ile otomatize etmek oldukça kolaydır.
Önce bir baseline (referans nokta) oluşturuyoruz:
# Kritik sistem dosyalarinin baseline hash degerlerini olustur
$targetPath = "C:WindowsSystem32"
$outputFile = "C:FIMbaseline_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
$excludeExtensions = @('.log', '.tmp', '.etl')
# Klasoru olustur
New-Item -ItemType Directory -Path "C:FIM" -Force | Out-Null
$results = Get-ChildItem -Path $targetPath -File -Recurse -ErrorAction SilentlyContinue |
Where-Object { $excludeExtensions -notcontains $_.Extension } |
ForEach-Object {
try {
$hash = Get-FileHash -Path $_.FullName -Algorithm SHA256
[PSCustomObject]@{
FilePath = $_.FullName
Hash = $hash.Hash
LastModified = $_.LastWriteTime
Size = $_.Length
CreatedDate = $_.CreationTime
}
} catch {
Write-Warning "Could not hash: $($_.FullName)"
}
}
$results | Export-Csv -Path $outputFile -NoTypeInformation -Encoding UTF8
Write-Host "Baseline created: $outputFile with $($results.Count) files"
Şimdi bu baseline ile güncel durumu karşılaştıran bir kontrol scripti yazalım:
# Baseline ile mevcut durumu karsilastir
param(
[string]$BaselineFile = "C:FIMbaseline.csv",
[string]$ScanPath = "C:WindowsSystem32",
[string]$ReportPath = "C:FIMReports"
)
New-Item -ItemType Directory -Path $ReportPath -Force | Out-Null
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$reportFile = "$ReportPathfim_report_$timestamp.txt"
# Baseline yukle
$baseline = Import-Csv -Path $BaselineFile
$baselineHash = @{}
foreach ($entry in $baseline) {
$baselineHash[$entry.FilePath] = $entry
}
$changes = @()
$newFiles = @()
$deletedFiles = @()
# Mevcut dosyalari tara
$currentFiles = Get-ChildItem -Path $ScanPath -File -Recurse -ErrorAction SilentlyContinue
foreach ($file in $currentFiles) {
$currentHash = (Get-FileHash -Path $file.FullName -Algorithm SHA256 -ErrorAction SilentlyContinue).Hash
if ($baselineHash.ContainsKey($file.FullName)) {
if ($baselineHash[$file.FullName].Hash -ne $currentHash) {
$changes += [PSCustomObject]@{
Type = "MODIFIED"
FilePath = $file.FullName
OldHash = $baselineHash[$file.FullName].Hash
NewHash = $currentHash
Time = Get-Date
}
}
} else {
$newFiles += $file.FullName
}
}
# Silinen dosyalari bul
foreach ($key in $baselineHash.Keys) {
if (-not (Test-Path $key)) {
$deletedFiles += $key
}
}
# Rapor olustur
$report = @"
FIM Report - $timestamp
=======================
Modified Files: $($changes.Count)
New Files: $($newFiles.Count)
Deleted Files: $($deletedFiles.Count)
MODIFIED FILES:
$($changes | ForEach-Object { "- $($_.FilePath)`n Old: $($_.OldHash)`n New: $($_.NewHash)" } | Out-String)
NEW FILES:
$($newFiles | ForEach-Object { "- $_" } | Out-String)
DELETED FILES:
$($deletedFiles | ForEach-Object { "- $_" } | Out-String)
"@
$report | Out-File -FilePath $reportFile -Encoding UTF8
Write-Host "Report saved: $reportFile"
# Degisiklik varsa Event Log'a yaz
if ($changes.Count -gt 0 -or $newFiles.Count -gt 0 -or $deletedFiles.Count -gt 0) {
Write-EventLog -LogName Application -Source "FIM-Monitor" -EventId 9001 -EntryType Warning -Message "FIM Alert: Changes detected! Modified:$($changes.Count) New:$($newFiles.Count) Deleted:$($deletedFiles.Count)"
}
Windows Event Log İzleme ile Gerçek Zamanlı Tespit
Hash bazlı kontrol periyodik olarak çalışır. Ama gerçek zamanlı izleme için Windows Event Log’u takip etmek daha etkilidir. Aşağıdaki script, belirli event ID’lerini dinleyerek anlık bildirim yapabilir:
# Gercek zamanli Event Log izleme
$watchEvents = @(4663, 4670, 4656, 4657, 4659)
$criticalPaths = @("C:WindowsSystem32", "C:WindowsSysWOW64", "C:Program Files")
# Event filtresi olustur
$filterXML = @"
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">
*[System[(EventID=4663 or EventID=4670 or EventID=4656)]]
and
*[EventData[Data[@Name='ObjectType']='File']]
</Select>
</Query>
</QueryList>
"@
Write-Host "Starting real-time FIM monitoring... Press Ctrl+C to stop"
Write-Host "Watching for events: $($watchEvents -join ', ')"
# Surekli izleme dongusu
while ($true) {
$events = Get-WinEvent -FilterXml $filterXML -MaxEvents 10 -ErrorAction SilentlyContinue
foreach ($event in $events) {
$eventXML = [xml]$event.ToXml()
$objectName = ($eventXML.Event.EventData.Data | Where-Object { $_.Name -eq 'ObjectName' }).'#text'
foreach ($critPath in $criticalPaths) {
if ($objectName -like "$critPath*") {
$logEntry = "[ALERT] $(Get-Date) | EventID: $($event.Id) | User: $($event.UserId) | File: $objectName"
Write-Host $logEntry -ForegroundColor Red
Add-Content -Path "C:FIMrealtime_alerts.log" -Value $logEntry
}
}
}
Start-Sleep -Seconds 30
}
Wazuh ile Kurumsal Seviyede FIM
Sahada büyük ortamlar için açık kaynaklı Wazuh platformunu şiddetle tavsiye ediyorum. OSSEC’in üzerine geliştirilmiş, SIEM özellikli, merkezi yönetilebilir bir çözüm. Windows agent kurulumu sonrasında ossec.conf dosyasına FIM kuralları ekleyebilirsiniz.
Wazuh agent üzerinde FIM konfigürasyonu şu şekilde yapılır:
# Wazuh ossec.conf icindeki FIM bolumu ornegi
# C:Program Files (x86)ossec-agentossec.conf dosyasina eklenecek
<syscheck>
<!-- FIM etkinlestir -->
<disabled>no</disabled>
<!-- Tarama araligi (saniye) -->
<frequency>3600</frequency>
<!-- Baslangicta tarama yap -->
<scan_on_start>yes</scan_on_start>
<!-- Gercek zamanli izleme -->
<directories realtime="yes" report_changes="yes" check_all="yes">
C:WindowsSystem32
</directories>
<directories realtime="yes" check_all="yes">
C:WindowsSysWOW64
</directories>
<directories realtime="yes" check_all="yes">
C:Program Files
</directories>
<!-- Hizli degisen dosyalari hariç tut -->
<ignore>C:WindowsSystem32winevt</ignore>
<ignore>C:WindowsSystem32wbemLogs</ignore>
<ignore type="sregex">.log$|.tmp$|.etl$</ignore>
<!-- Registry izleme -->
<windows_registry>HKEY_LOCAL_MACHINESoftwareMicrosoftWindowsCurrentVersionRun</windows_registry>
<windows_registry>HKEY_LOCAL_MACHINESystemCurrentControlSetServices</windows_registry>
</syscheck>
Wazuh agent’i yeniden başlatmak için:
# Wazuh agent servisini yeniden baslat
net stop WazuhSvc
net start WazuhSvc
# Veya PowerShell ile
Restart-Service -Name WazuhSvc -Force
Get-Service WazuhSvc | Select-Object Name, Status, StartType
Registry Bütünlük İzleme
Windows sistemlerde sadece dosyaları değil, registry’yi de izlemek kritik. Özellikle startup anahtarları, servis kayıtları ve güvenlik politikası ayarları değişirse alarm vermelisiniz.
# Kritik registry anahtarlarini izle ve baseline al
$criticalKeys = @(
"HKLM:SOFTWAREMicrosoftWindowsCurrentVersionRun",
"HKLM:SOFTWAREMicrosoftWindowsCurrentVersionRunOnce",
"HKLM:SYSTEMCurrentControlSetServices",
"HKLM:SOFTWAREMicrosoftWindows NTCurrentVersionWinlogon",
"HKCU:SOFTWAREMicrosoftWindowsCurrentVersionRun"
)
$registryBaseline = @()
foreach ($key in $criticalKeys) {
try {
$values = Get-ItemProperty -Path $key -ErrorAction SilentlyContinue
if ($values) {
$values.PSObject.Properties | Where-Object { $_.Name -notlike "PS*" } | ForEach-Object {
$registryBaseline += [PSCustomObject]@{
KeyPath = $key
ValueName = $_.Name
ValueData = $_.Value
CheckTime = Get-Date
Hash = (($key + $_.Name + $_.Value) | Get-Hash -Algorithm SHA256 -AsString)
}
}
}
} catch {
Write-Warning "Cannot read: $key"
}
}
# Sonuclari kaydet
$registryBaseline | Export-Csv -Path "C:FIMregistry_baseline.csv" -NoTypeInformation
Write-Host "Registry baseline saved: $($registryBaseline.Count) entries"
# Degisiklikleri kontrol et
$currentRegistry = @()
foreach ($key in $criticalKeys) {
$values = Get-ItemProperty -Path $key -ErrorAction SilentlyContinue
if ($values) {
$values.PSObject.Properties | Where-Object { $_.Name -notlike "PS*" } | ForEach-Object {
$currentRegistry += [PSCustomObject]@{
KeyPath = $key
ValueName = $_.Name
ValueData = $_.Value
}
}
}
}
# Yeni veya degismis registry girisleri bul
$baseline = Import-Csv "C:FIMregistry_baseline.csv"
$baselineKeys = $baseline | Group-Object -Property KeyPath, ValueName
foreach ($current in $currentRegistry) {
$found = $baseline | Where-Object { $_.KeyPath -eq $current.KeyPath -and $_.ValueName -eq $current.ValueName }
if ($null -eq $found) {
Write-Host "[NEW REGISTRY ENTRY] $($current.KeyPath)$($current.ValueName) = $($current.ValueData)" -ForegroundColor Yellow
} elseif ($found.ValueData -ne $current.ValueData) {
Write-Host "[MODIFIED REGISTRY] $($current.KeyPath)$($current.ValueName)" -ForegroundColor Red
Write-Host " Old: $($found.ValueData)"
Write-Host " New: $($current.ValueData)"
}
}
Scheduled Task ile FIM Otomasyonu
Tüm bu scriptleri el ile çalıştırmak pratik değil. Windows Task Scheduler ile düzenli çalıştırılacak şekilde ayarlamalısınız:
# FIM scriptini Scheduled Task olarak kaydet
$taskName = "FIM-DailyCheck"
$scriptPath = "C:FIMScriptsfim_check.ps1"
$logPath = "C:FIMLogs"
New-Item -ItemType Directory -Path $logPath -Force | Out-Null
# Task Action olustur
$action = New-ScheduledTaskAction `
-Execute "PowerShell.exe" `
-Argument "-NonInteractive -ExecutionPolicy Bypass -File `"$scriptPath`" >> `"$logPathfim_$(Get-Date -Format 'yyyyMMdd').log`" 2>&1"
# Her gun saat 03:00'da calistir
$trigger = New-ScheduledTaskTrigger -Daily -At "03:00"
# SYSTEM hesabi ile calistir
$principal = New-ScheduledTaskPrincipal `
-UserId "SYSTEM" `
-LogonType ServiceAccount `
-RunLevel Highest
# Task ayarlari
$settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 2) `
-RestartCount 3 `
-RestartInterval (New-TimeSpan -Minutes 5) `
-StartWhenAvailable
# Task'i kaydet
Register-ScheduledTask `
-TaskName $taskName `
-TaskPath "Security" `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Settings $settings `
-Description "Daily File Integrity Monitoring Check" `
-Force
Write-Host "Scheduled task created: Security$taskName"
Get-ScheduledTask -TaskName $taskName | Select-Object TaskName, State, TaskPath
Gerçek Dünya Senaryosu: Web Sunucusunda FIM
Birkaç yıl önce bir müşterimde tam bu senaryoyu yaşadık. IIS tabanlı bir web sunucusunda haftalarca süren, fark edilemeyen bir webshell yerleşimi vardı. O günden bu yana, tüm web sunucularımda şu şekilde özel bir FIM kuralı çalıştırıyorum:
Web kök dizinlerini izlemek için kritik path listesi:
- C:inetpubwwwroot: Ana web içeriği
- C:inetpubwwwroot.aspx, .php, *.asp: Script dosyaları
- C:WindowsSystem32inetsrv: IIS binary dosyaları
- C:WindowsSystem32inetsrvconfig: IIS konfigürasyonları
Bu senaryo için özelleştirilmiş bir monitoring scripti:
# Web sunucusu FIM - Webshell tespiti odakli
param(
[string]$WebRoot = "C:inetpubwwwroot",
[string]$BaselinePath = "C:FIMweb_baseline.csv",
[string]$AlertEmail = "[email protected]"
)
# Tehlikeli uzantilar
$dangerousExtensions = @('.asp', '.aspx', '.php', '.php5', '.phtml', '.shtml', '.cfm', '.cgi', '.pl')
$suspiciousPatterns = @('cmd.exe', 'powershell', 'eval(', 'base64_decode', 'shell_exec', 'passthru')
$alerts = @()
# Yeni eklenen script dosyalarini bul
if (Test-Path $BaselinePath) {
$baseline = Import-Csv $BaselinePath
$baselineFiles = $baseline | Select-Object -ExpandProperty FilePath
$currentScripts = Get-ChildItem -Path $WebRoot -Recurse -File |
Where-Object { $dangerousExtensions -contains $_.Extension.ToLower() }
foreach ($script in $currentScripts) {
# Baseline'da olmayan yeni script dosyasi
if ($script.FullName -notin $baselineFiles) {
$alerts += "[CRITICAL] New script file detected: $($script.FullName)"
Write-Host "[CRITICAL] New script file: $($script.FullName)" -ForegroundColor Red
}
# Dosya icerigi suphelik pattern iceriyor mu?
try {
$content = Get-Content -Path $script.FullName -Raw -ErrorAction SilentlyContinue
foreach ($pattern in $suspiciousPatterns) {
if ($content -match [regex]::Escape($pattern)) {
$alerts += "[SUSPICIOUS] Pattern '$pattern' found in: $($script.FullName)"
Write-Host "[SUSPICIOUS] $pattern found in $($script.FullName)" -ForegroundColor Magenta
}
}
} catch { }
# Son 24 saatte degistirilen dosyalar
if ($script.LastWriteTime -gt (Get-Date).AddHours(-24)) {
$hashCurrent = (Get-FileHash $script.FullName -Algorithm SHA256).Hash
$baselineEntry = $baseline | Where-Object { $_.FilePath -eq $script.FullName }
if ($baselineEntry -and $baselineEntry.Hash -ne $hashCurrent) {
$alerts += "[WARNING] Modified in last 24h: $($script.FullName)"
}
}
}
}
# Alert ozeti
if ($alerts.Count -gt 0) {
$alertMessage = "FIM Web Server Alert - $(Get-Date)`n`n" + ($alerts -join "`n")
Write-Host "`n$alertMessage" -ForegroundColor Red
# Event Log'a yaz
Write-EventLog -LogName Application -Source "FIM-WebMonitor" -EventId 9002 `
-EntryType Error -Message $alertMessage
# Log dosyasina kaydet
Add-Content -Path "C:FIMweb_alerts.log" -Value $alertMessage
Write-Host "`nTotal alerts: $($alerts.Count)"
} else {
Write-Host "No issues detected. Web root is clean." -ForegroundColor Green
}
İyi Pratikler ve Dikkat Edilmesi Gerekenler
Windows FIM’i doğru kurmak kadar doğru işletmek de önemli. Sahada gördüğüm en sık hatalar şunlar:
- Fazla geniş kapsam: Her dosyayı izlemeye çalışmak, hem performansı düşürür hem de alarm yorgunluğuna yol açar. Kritik path’leri önceliklendirin.
- Baseline’ı koruyamamak: Meşru değişikliklerden sonra baseline’ı güncellemezseniz, sürekli yanlış alarm alırsınız. Patch Tuesday’den sonra mutlaka baseline’ı yenileyin.
- Log’ları saklamama: FIM log’larını en az 90 gün, PCI uyumluluğu için 1 yıl saklamanız gerekir. Log rotasyonu ve arşivleme planı yapın.
- Sadece dosya sistemi izlemek: Registry, servisler, zamanlanmış görevler ve ağ paylaşımları da izlemeniz gereken kritik noktalardır.
- Test etmemek: FIM sisteminizin gerçekten çalışıp çalışmadığını düzenli aralıklarla simüle edilmiş değişikliklerle test edin.
- Alert yorgunluğu: Çok fazla alarm üretirseniz, gerçek olaylar gürültüde kaybolur. Alert eşiklerini ve önceliklendirmeyi iyi yapın.
- Şifreli raporlama: FIM raporları hassas sistem bilgisi içerir. Bunları şifreli kanallar üzerinden iletip saklamalısınız.
Sonuç
Windows sistemlerde dosya bütünlüğü izleme, savunma katmanlarının en temellerinden biri. Küçük ortamlar için PowerShell tabanlı özel scriptler yeterli olabilir, ancak büyük ve kurumsal yapılar için Wazuh, OSSEC veya ticari bir çözüm tercih etmenizi öneririm. Önemli olan, hangi aracı kullandığınızdan çok ne izlediğinizi ve alarmları nasıl değerlendirdiğinizi bilmektir.
Başlangıç için şu adımları öneririm: Audit Policy’yi etkinleştir, kritik dizinler için baseline oluştur, periyodik kontrol scriptini Task Scheduler’a ekle ve alarm mekanizmasını kur. Bu dört adım bile sizi çoğu saldırıya karşı önemli ölçüde daha görünür kılacak.
FIM bir kez kurulup unutulacak bir şey değil. Düzenli olarak gözden geçirilmesi, baseline’larının güncellenmesi ve alarm eşiklerinin iyileştirilmesi gereken, canlı bir güvenlik sürecidir. Sisteminizde bir şeyler değiştiğinde, bunu saldırgan değil siz fark etmelisiniz.
