PowerShell Execution Policy ile Güvenli Yapılandırma Rehberi
Windows ortamlarında PowerShell yönetimi, özellikle kurumsal ağlarda ciddi bir güvenlik meselesi haline geldi. Execution Policy, birçok sysadmin’in “varsayılan ayarları bırakayım, zaten çalışıyor” diyerek geçiştirdiği, ama aslında bir saldırganın sisteme sızmak için ilk hedef aldığı noktalardan biri. Bu yazıda Execution Policy’yi sadece teorik olarak değil, gerçek dünya senaryolarıyla, pratik yapılandırmalarla ve sıkça yapılan hatalarla birlikte ele alacağız.
Execution Policy Nedir ve Ne Değildir?
Önce şunu netleştirelim: Execution Policy bir güvenlik duvarı değil, bir koruma katmanı. Microsoft’un kendi dökümantasyonunda da belirttiği gibi, bu mekanizma kötü niyetli kullanıcıları değil, dikkatsiz kullanıcıları korumak için tasarlandı. Yani birisi size “Execution Policy’yi Unrestricted yaparsanız güvenlisiniz” diyorsa, o kişiyle güvenlik tartışması yapmamanız gerekiyor.
Execution Policy, PowerShell’in hangi betikleri çalıştırabileceğini belirleyen bir politika mekanizması. Yanlış yapılandırıldığında, bir saldırganın sisteminizde istediği PowerShell betiğini çalıştırmasının önünde hiçbir engel kalmıyor. Doğru yapılandırıldığında ise kurumsal ortamda betik yönetimini merkezi ve denetlenebilir hale getiriyor.
Policy Seviyeleri ve Gerçek Anlamları
PowerShell’de altı farklı execution policy seviyesi bulunuyor. Bunları salt teorik olarak değil, pratikte ne anlama geldiklerini açıklayarak inceleyelim.
Restricted: Hiçbir betik çalıştırılamaz. Sadece interaktif komutlar çalışır. Windows istemcilerinde varsayılan ayar bu. Güvenli ama kullanışsız.
AllSigned: Tüm betikler, güvenilir bir sertifika otoritesi tarafından imzalanmış olmalı. Kurumsal ortamlar için ideal ama sertifika altyapısı gerektiriyor.
RemoteSigned: Yerel betikler imzasız çalışabilir, internet’ten indirilen betikler imzalı olmalı. Sunucularda en yaygın kullanılan ayar bu.
Unrestricted: Her betik çalışır, internet’ten indirilenler için sadece uyarı verir. Kullanmayın.
Bypass: Hiçbir kısıtlama yok, hiçbir uyarı yok. Sadece belirli otomasyon pipeline’larında ve çok iyi anlıyorsanız kullanın.
Undefined: Bu scope için policy tanımlanmamış, bir üst scope’un ayarı geçerli.
Şimdi mevcut durumu kontrol edelim:
# Tum scope'lardaki execution policy'leri goster
Get-ExecutionPolicy -List
# Sadece gecerli policy'yi goster
Get-ExecutionPolicy
# Verbose modda detayli bilgi al
Get-ExecutionPolicy -Verbose
Bu komutu çalıştırdığınızda şöyle bir çıktı görürsünüz:
# Ornek cikti:
# Scope ExecutionPolicy
# ----- ---------------
# MachinePolicy Undefined
# UserPolicy Undefined
# Process Undefined
# CurrentUser RemoteSigned
# LocalMachine Restricted
Burada önemli bir nokta var: Scope’lar arasında öncelik sırası bulunuyor. MachinePolicy en yüksek önceliğe sahip, sonra UserPolicy, Process, CurrentUser ve LocalMachine geliyor. Group Policy üzerinden yapılan ayarlar MachinePolicy veya UserPolicy scope’unda görünür ve bunlar lokal ayarların üzerine yazar. Bu yüzden birisi “Set-ExecutionPolicy komutu çalışmıyor” diyorsa, büyük ihtimalle GPO bir şeyi override ediyor.
Scope Bazlı Yapılandırma
Kurumsal ortamda Execution Policy’yi doğru scope’ta yapılandırmak kritik. Şu senaryoya bakalım: Bir domain’de 200 Windows sunucunuz var. Her birine tek tek giripde policy değiştirmek yerine merkezi yönetim gerekiyor.
# LocalMachine scope'unda policy ayarla (Admin gerektirir)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force
# Sadece mevcut kullanici icin ayarla
Set-ExecutionPolicy -ExecutionPolicy AllSigned -Scope CurrentUser
# Sadece mevcut PowerShell session'i icin ayarla (en gecici)
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
Process scope’u özellikle dikkat gerektiriyor. Bir CI/CD pipeline’ında veya bir otomasyon görevinde -Scope Process kullanarak geçici olarak policy değiştirebilirsiniz, session kapandığında ayar sıfırlanır. Bu yöntem, kalıcı bir güvenlik açığı oluşturmadan belirli işleri halletmenizi sağlar.
Group Policy ile Merkezi Yönetim
Gerçek kurumsal senaryoda execution policy’yi Group Policy üzerinden yönetmek en doğru yaklaşım. Bu sayede politikanın lokal olarak değiştirilmesinin önüne geçebilirsiniz.
GPO yolu: Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell > Turn on Script Execution
Bu ayarı yapılandırdığınızda MachinePolicy scope’unda görünür ve kullanıcıların ya da lokal adminlerin bu ayarı değiştirmesi engellenir. Bir saldırgan lokal admin yetkisi kazansa bile GPO’yu bypass etmesi çok daha zor.
Registry üzerinden kontrol etmek isterseniz:
# GPO tarafindan ayarlanan policy'yi kontrol et
Get-ItemProperty -Path "HKLM:SOFTWAREPoliciesMicrosoftWindowsPowerShell" -Name "ExecutionPolicy" -ErrorAction SilentlyContinue
# LocalMachine scope'unun registry kaydini kontrol et
Get-ItemProperty -Path "HKLM:SOFTWAREMicrosoftPowerShell1ShellIdsMicrosoft.PowerShell" -Name "ExecutionPolicy"
# Tum ilgili registry degerlerini listele
Get-ChildItem -Path "HKLM:SOFTWAREPoliciesMicrosoftWindowsPowerShell" -ErrorAction SilentlyContinue
Execution Policy Bypass Yöntemleri ve Savunma
Bu kısım kritik. Saldırganların Execution Policy’yi bypass etmek için kullandığı yöntemleri bilmeden savunma stratejisi geliştiremezsiniz. Bu yöntemlerin farkında olmanız, neden sadece Execution Policy’ye güvenmemeniz gerektiğini de anlatıyor.
Yaygın bypass teknikleri:
powershell.exe -ExecutionPolicy Bypass -File script.ps1parametresiyle doğrudan çalıştırma- Betiği string olarak pipe etme:
Get-Content script.ps1 | PowerShell.exe -NoProfile - - Encode edilmiş komut kullanma:
-EncodedCommandparametresi - PowerShell betiklerini
.ps1yerine farklı uzantıyla yeniden adlandırma - Alternatif PowerShell host’ları kullanma (pwsh.exe, powershell_ise.exe)
Bu bypass yöntemlerine karşı savunma:
# PowerShell script block logging'i aktif et (hangi kodun calistigini kayit altina al)
$registryPath = "HKLM:SOFTWAREPoliciesMicrosoftWindowsPowerShellScriptBlockLogging"
If (!(Test-Path $registryPath)) {
New-Item -Path $registryPath -Force
}
Set-ItemProperty -Path $registryPath -Name "EnableScriptBlockLogging" -Value 1
# Module logging aktif et
$mlPath = "HKLM:SOFTWAREPoliciesMicrosoftWindowsPowerShellModuleLogging"
If (!(Test-Path $mlPath)) {
New-Item -Path $mlPath -Force
}
Set-ItemProperty -Path $mlPath -Name "EnableModuleLogging" -Value 1
Script Block Logging aktif edildiğinde, -ExecutionPolicy Bypass kullansa bile çalıştırılan tüm PowerShell kodları Windows Event Log’a (Event ID 4104) kaydedilir. Bu, SIEM sistemleriniz için mükemmel bir veri kaynağı.
AllSigned Policy ile Sertifika Tabanlı Güvenlik
Yüksek güvenlik gerektiren ortamlarda AllSigned politikası ideal. Bunun için önce kod imzalama sertifikası gerekiyor. Eğer kurumsal CA altyapınız varsa, buradan sertifika alabilirsiniz. Test ortamı için self-signed sertifika oluşturabilirsiniz ama production’da bu yöntemi kullanmayın.
# Test icin self-signed kod imzalama sertifikasi olustur
$cert = New-SelfSignedCertificate -Type CodeSigningCert `
-Subject "CN=TestCodeSigning" `
-CertStoreLocation "Cert:CurrentUserMy" `
-KeyUsage DigitalSignature `
-KeyAlgorithm RSA `
-KeyLength 2048
# Sertifikay? Trusted Root ve Trusted Publisher'a ekle
$rootStore = [System.Security.Cryptography.X509Certificates.X509Store]::new("Root","LocalMachine")
$rootStore.Open("ReadWrite")
$rootStore.Add($cert)
$rootStore.Close()
$publisherStore = [System.Security.Cryptography.X509Certificates.X509Store]::new("TrustedPublisher","LocalMachine")
$publisherStore.Open("ReadWrite")
$publisherStore.Add($cert)
$publisherStore.Close()
# Betigi imzala
Set-AuthenticodeSignature -FilePath "C:Scriptsbackup.ps1" -Certificate $cert
# Imzayi dogrula
Get-AuthenticodeSignature -FilePath "C:Scriptsbackup.ps1"
Kurumsal ortamda sertifika yönetimini otomatikleştirmek için Active Directory Certificate Services (AD CS) kullanabilirsiniz. Kod imzalama template’i oluşturup yetkili sysadmin’lere bu sertifikayı otomatik olarak dağıtabilirsiniz.
Constrained Language Mode ile Derinlemesine Savunma
Execution Policy tek başına yeterli değil. Constrained Language Mode (CLM) ile PowerShell’in yeteneklerini kısıtlamak, defense in depth stratejisinin önemli bir parçası.
# Mevcut language mode'u kontrol et
$ExecutionContext.SessionState.LanguageMode
# Language mode'u degistir (genellikle AppLocker veya WDAC ile yapilir)
# Manuel test icin:
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
# CLM aktifken kısıtlı olan bazi ornekler:
# - Add-Type kullanimi engellenir
# - COM nesneleri kullanilamaz
# - .NET type'larina dogrudan erisim kisitlanir
# - Invoke-Expression kullanimi engellenir
# Full Language Mode'da calistigin dogrula
if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage") {
Write-Host "Full Language Mode aktif" -ForegroundColor Yellow
} else {
Write-Host "Constrained Language Mode aktif" -ForegroundColor Green
}
CLM’yi production’da uygulamak için Windows Defender Application Control (WDAC) veya AppLocker kullanmanız gerekiyor. Bu araçlar, yalnızca izin verilen uygulamaların ve betiklerin çalışmasına izin verirken CLM’yi de otomatik olarak devreye alıyor.
Gerçek Dünya Senaryosu: Kurumsal Ortamda Güvenli Betik Yönetimi
Bir finans şirketinde 150 Windows Server 2019 sunucusu olduğunu düşünelim. Sysadmin ekibi düzenli olarak PowerShell betikleriyle yedekleme, raporlama ve patch yönetimi yapıyor. Ama SOC ekibi, bazı sunucularda yetkisiz PowerShell aktivitesi gördüğünü bildiriyor.
Bu senaryoda uygulayacağımız yapılandırma:
# Tum sunucularda uygulanacak baseline kontrol scripti
# Bu betigi GPO'nun Startup Script olarak dagitin
function Set-PowerShellSecurityBaseline {
param(
[string]$ComputerName = $env:COMPUTERNAME
)
Write-Host "[$ComputerName] PowerShell guvenlik baseline uygulanıyor..." -ForegroundColor Cyan
# 1. Execution Policy - RemoteSigned (AllSigned daha iyi ama gecis sureci gerektirir)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force
Write-Host " [OK] Execution Policy: RemoteSigned" -ForegroundColor Green
# 2. Script Block Logging
$sbPath = "HKLM:SOFTWAREPoliciesMicrosoftWindowsPowerShellScriptBlockLogging"
New-Item -Path $sbPath -Force | Out-Null
Set-ItemProperty -Path $sbPath -Name "EnableScriptBlockLogging" -Value 1
Set-ItemProperty -Path $sbPath -Name "EnableScriptBlockInvocationLogging" -Value 1
Write-Host " [OK] Script Block Logging aktif" -ForegroundColor Green
# 3. Module Logging
$modPath = "HKLM:SOFTWAREPoliciesMicrosoftWindowsPowerShellModuleLogging"
New-Item -Path $modPath -Force | Out-Null
Set-ItemProperty -Path $modPath -Name "EnableModuleLogging" -Value 1
# Tum moduller icin loglama
$modNamesPath = "$modPathModuleNames"
New-Item -Path $modNamesPath -Force | Out-Null
Set-ItemProperty -Path $modNamesPath -Name "*" -Value "*"
Write-Host " [OK] Module Logging aktif" -ForegroundColor Green
# 4. Transcription logging
$transcriptPath = "HKLM:SOFTWAREPoliciesMicrosoftWindowsPowerShellTranscription"
New-Item -Path $transcriptPath -Force | Out-Null
Set-ItemProperty -Path $transcriptPath -Name "EnableTranscripting" -Value 1
Set-ItemProperty -Path $transcriptPath -Name "OutputDirectory" -Value "C:PSTranscripts"
Set-ItemProperty -Path $transcriptPath -Name "EnableInvocationHeader" -Value 1
# Transcript klasorunu olustur ve izinleri ayarla
if (!(Test-Path "C:PSTranscripts")) {
New-Item -ItemType Directory -Path "C:PSTranscripts" | Out-Null
# Herkes yazabilir ama sadece adminler okuyabilir
$acl = Get-Acl "C:PSTranscripts"
$acl.SetAccessRuleProtection($true, $false)
$adminRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
"Administrators", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
)
$systemRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
"SYSTEM", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
)
$acl.AddAccessRule($adminRule)
$acl.AddAccessRule($systemRule)
Set-Acl -Path "C:PSTranscripts" -AclObject $acl
}
Write-Host " [OK] Transcription Logging aktif" -ForegroundColor Green
Write-Host "[$ComputerName] Baseline tamamlandi." -ForegroundColor Cyan
}
Set-PowerShellSecurityBaseline
Event Log Monitoring ile Anomali Tespiti
Logging’i aktif etmek yetmiyor, bu logları analiz etmeniz de gerekiyor. PowerShell aktivitesini izlemek için kullanabileceğiniz temel Event ID’leri:
- 4103: Module logging – hangi modüllerin kullanıldığı
- 4104: Script Block Logging – çalıştırılan kod blokları
- 4105/4106: Transcription başlangıç ve bitiş
- 400/403: PowerShell engine durumu
# Son 24 saatte suphelı PowerShell aktivitelerini ara
$startTime = (Get-Date).AddHours(-24)
# Execution Policy bypass girisimleri
Get-WinEvent -FilterHashtable @{
LogName = 'Microsoft-Windows-PowerShell/Operational'
Id = 4104
StartTime = $startTime
} -ErrorAction SilentlyContinue | Where-Object {
$_.Message -match "bypass|encodedcommand|iex|invoke-expression|downloadstring"
} | Select-Object TimeCreated, Id, Message | Format-List
# Encoded command kullanim tespiti
Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4688
StartTime = $startTime
} -ErrorAction SilentlyContinue | Where-Object {
$_.Message -match "powershell" -and $_.Message -match "-enc|-EncodedCommand"
} | Select-Object TimeCreated, @{N='CommandLine';E={$_.Properties[8].Value}}
Yanlış Yapılandırma Kontrol Scripti
Ortamınızdaki tüm sunucuları tarayarak yanlış yapılandırmaları tespit eden bir araç:
# Uzak sunucularda execution policy uyumsuzlugunu tara
$servers = Get-Content "C:serverlist.txt"
$results = @()
foreach ($server in $servers) {
try {
$policy = Invoke-Command -ComputerName $server -ScriptBlock {
$ep = Get-ExecutionPolicy -List
$logging = Get-ItemProperty -Path "HKLM:SOFTWAREPoliciesMicrosoftWindowsPowerShellScriptBlockLogging" `
-ErrorAction SilentlyContinue
[PSCustomObject]@{
LocalMachinePolicy = ($ep | Where-Object Scope -eq 'LocalMachine').ExecutionPolicy
MachineGPOPolicy = ($ep | Where-Object Scope -eq 'MachinePolicy').ExecutionPolicy
ScriptBlockLogging = if ($logging.EnableScriptBlockLogging -eq 1) { "Aktif" } else { "Pasif" }
PSVersion = $PSVersionTable.PSVersion.ToString()
}
} -ErrorAction Stop
$results += [PSCustomObject]@{
Sunucu = $server
LocalMachinePolicy = $policy.LocalMachinePolicy
GPOPolicy = $policy.MachineGPOPolicy
SBLogging = $policy.ScriptBlockLogging
PSVersion = $policy.PSVersion
Durum = if ($policy.LocalMachinePolicy -in @("Unrestricted","Bypass") -or
$policy.ScriptBlockLogging -eq "Pasif") { "UYUMSUZ" } else { "OK" }
}
} catch {
$results += [PSCustomObject]@{
Sunucu = $server
Durum = "ERISIM HATASI: $_"
}
}
}
# Sonuclari goruntule
$results | Sort-Object Durum | Format-Table -AutoSize
# Uyumsuz sunuculari ayri kaydet
$results | Where-Object Durum -eq "UYUMSUZ" | Export-Csv -Path "C:uyumsuz_sunucular.csv" -NoTypeInformation -Encoding UTF8
Eski PowerShell Versiyonları Sorunu
Bu kısmı atlamayın. PowerShell v2 hala birçok sistemde etkin durumda ve bu versiyon Script Block Logging desteklemiyor. Yani bir saldırgan powershell -Version 2 -ExecutionPolicy Bypass komutuyla tüm logging mekanizmanızı atlatabilir.
# PowerShell v2'nin aktif olup olmadigini kontrol et
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root
# PowerShell v2'yi devre disi birak (Windows 10/2016+)
Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root -NoRestart
Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2 -NoRestart
# Toplu olarak devre disi birak (DISM ile)
DISM /Online /Disable-Feature /FeatureName:MicrosoftWindowsPowerShellV2Root /NoRestart
Bu adımı uygulamadan diğer tüm ayarlarınız eksik kalıyor. PowerShell v2’yi devre dışı bırakmak, modern logging altyapınızın bypass edilmesini engelleyen kritik bir adım.
Sonuç
PowerShell Execution Policy güvenli yapılandırması, tek bir komutla biten bir iş değil. RemoteSigned veya AllSigned policy, Script Block Logging, Module Logging, Transcription, PowerShell v2’nin devre dışı bırakılması ve bunların hepsinin GPO ile merkezi yönetimi, sağlam bir PowerShell güvenlik altyapısının temel taşları.
Unutmayın: Execution Policy bir kale duvarı değil, bir erken uyarı sistemi. Asıl güç, logging ve monitoring altyapısından geliyor. Bir saldırgan Execution Policy’yi bypass etse bile, doğru yapılandırılmış logging ile bu aktiviteyi tespit edebilirsiniz. Ortamınızda bu adımları uyguladıktan sonra düzenli olarak o uyumsuzluk tarama scriptini çalıştırın, yeni eklenen sunucuların baseline dışında kalmadığından emin olun. Ve her değişikliği change management sürecinize dahil edin, çünkü “geçici olarak Bypass yaptım” diyerek açık bırakılan sistemler en kötü güvenlik olaylarının başlangıç noktası oluyor.
