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.ps1 parametresiyle doğrudan çalıştırma
  • Betiği string olarak pipe etme: Get-Content script.ps1 | PowerShell.exe -NoProfile -
  • Encode edilmiş komut kullanma: -EncodedCommand parametresi
  • PowerShell betiklerini .ps1 yerine 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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir