Windows ortamlarında PowerShell scriptleri çalıştırmaya çalışırken “cannot be loaded because running scripts is disabled on this system” hatasıyla karşılaşmayan sysadmin neredeyse yoktur. Bu hata ilk bakışta sinir bozucu görünse de aslında Windows’un yerleşik güvenlik mekanizmalarından biri olan Execution Policy’nin tam olarak çalıştığının kanıtıdır. Ancak çoğu yönetici bu politikayı ya tamamen devre dışı bırakıyor ya da ne yaptığını tam anlamadan manipüle ediyor. Bu yazıda PowerShell Execution Policy’nin gerçekte ne işe yaradığını, sınırlarını, doğru yapılandırma yöntemlerini ve kurumsal ortamlarda nasıl yönetilmesi gerektiğini ele alacağız.
Execution Policy Nedir ve Ne Değildir?
Önce şunu netleştirelim: Execution Policy bir güvenlik duvarı değildir. Microsoft’un resmi dokümantasyonunda da açıkça belirtildiği gibi bu mekanizma, kullanıcıların yanlışlıkla zararlı scriptleri çalıştırmasını engellemek için tasarlanmış bir güvenlik katmanıdır. Kötü niyetli ve bilinçli bir saldırgan için bu korumayı aşmak son derece kolaydır.
Execution Policy’yi bir iş akışı kontrol mekanizması olarak düşünmek daha doğru. Sistem yöneticisi olmayan kullanıcıların rastgele indirdikleri scriptleri çalıştırmasını engeller, üretim ortamlarında imzasız scriptlerin çalışmasının önüne geçer ve politika tabanlı script yönetimini mümkün kılar.
Policy Seviyeleri ve Anlamları
PowerShell’de altı farklı execution policy seviyesi bulunur:
- Restricted: Hiçbir script çalıştırılamaz. Sadece interaktif komutlar kabul edilir. Windows istemcilerinde varsayılan değerdir.
- AllSigned: Hem yerel hem de uzak scriptlerin güvenilir bir sertifika yetkilisi tarafından imzalanması zorunludur. En güvenli seçenektir.
- RemoteSigned: Yerel scriptler imzasız çalışabilir, internetten veya ağdan indirilen scriptlerin imzalı olması gerekir. Windows Server’ın varsayılan değeridir.
- Unrestricted: İmzasız scriptler çalışır, uzak scriptler için sadece uyarı gösterilir. Tehlikeli bir ayardır.
- Bypass: Hiçbir engel yoktur, uyarı da yoktur. Otomasyon senaryoları için kullanılır ama dikkatli olunmalıdır.
- Undefined: O scope için policy tanımlanmamış demektir, üst scope’tan miras alınır.
Scope Kavramı: Asıl Karmaşıklık Burada
Pek çok sysadmin execution policy’yi tek bir global ayar olarak düşünür, oysa birden fazla scope üst üste uygulanır ve öncelik sırası kritiktir:
- MachinePolicy: Group Policy üzerinden tüm makine için uygulanan ayar, en yüksek önceliğe sahiptir
- UserPolicy: Group Policy üzerinden kullanıcıya uygulanan ayar
- Process: Sadece mevcut PowerShell oturumu için geçerlidir, oturum kapandığında sıfırlanır
- CurrentUser: Mevcut kullanıcının registry ayarları
- LocalMachine: Tüm kullanıcılar için geçerli makine geneli ayar, en düşük önceliğe sahiptir
Mevcut durumu görmek için:
# Tüm scope'lardaki policy değerlerini görüntüle
Get-ExecutionPolicy -List
# Aktif (efektif) policy'yi görüntüle
Get-ExecutionPolicy
Çıktı şöyle görünür:
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser RemoteSigned
LocalMachine Undefined
Execution Policy Değiştirme
Geçici Değişiklik (Process Scope)
Sadece mevcut oturum için policy değiştirmek istiyorsanız Process scope’unu kullanın. Bu yöntem üretim ortamlarında tek seferlik görevler için idealdir:
# Mevcut oturum için geçici olarak RemoteSigned yap
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
# Doğrula
Get-ExecutionPolicy -Scope Process
Bu ayar PowerShell penceresi kapandığında otomatik olarak sıfırlanır. Kalıcı bir değişiklik yapmadan script çalıştırmanız gerektiğinde bunu tercih edin.
Kalıcı Değişiklik (LocalMachine)
Tüm kullanıcılar için kalıcı ayar yapmak administrator yetkisi gerektirir:
# Tüm kullanıcılar için RemoteSigned ayarla
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force
# Belirli kullanıcı için
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Komut Satırı ile PowerShell Başlatma
Bazen policy değiştirmeden tek bir scripti çalıştırmanız gerekir. Bunun için PowerShell’i parametrelerle çalıştırabilirsiniz:
# Script'i bypass policy ile çalıştır
powershell.exe -ExecutionPolicy Bypass -File "C:Scriptsbackup.ps1"
# Encoded command ile çalıştır (automation senaryoları için)
$encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes('Write-Host "Merhaba"'))
powershell.exe -EncodedCommand $encoded
Önemli Not: -ExecutionPolicy Bypass parametresini kullanmak registry’deki ayarı değiştirmez, sadece o process için geçerlidir. Bu nedenle task scheduler veya servisler üzerinden script çalıştırırken bu yöntem sıkça kullanılır ancak ne yaptığınızın farkında olun.
Script İmzalama: Gerçek Güvenlik Buradan Başlar
Execution Policy’nin AllSigned modunu kullanmak istiyorsanız scriptlerinizi imzalamanız gerekir. Bu kurumsal ortamlar için en güvenli yaklaşımdır.
Self-Signed Sertifika Oluşturma (Test Ortamı)
# Test için self-signed sertifika oluştur
$cert = New-SelfSignedCertificate `
-Subject "CN=PowerShell Script Signing" `
-CertStoreLocation "Cert:LocalMachineMy" `
-KeyUsage DigitalSignature `
-Type CodeSigningCert `
-NotAfter (Get-Date).AddYears(3)
# Sertifikayı güvenilir köke ekle (self-signed için gerekli)
$rootStore = New-Object System.Security.Cryptography.X509Certificates.X509Store(
[System.Security.Cryptography.X509Certificates.StoreName]::Root,
[System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine
)
$rootStore.Open("ReadWrite")
$rootStore.Add($cert)
$rootStore.Close()
# Sertifikayı TrustedPublisher store'a da ekle
$publisherStore = New-Object System.Security.Cryptography.X509Certificates.X509Store(
[System.Security.Cryptography.X509Certificates.StoreName]::TrustedPublisher,
[System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine
)
$publisherStore.Open("ReadWrite")
$publisherStore.Add($cert)
$publisherStore.Close()
Write-Host "Sertifika thumbprint: $($cert.Thumbprint)"
Script İmzalama
# Sertifikayı al
$cert = Get-ChildItem Cert:LocalMachineMy |
Where-Object { $_.Subject -like "*PowerShell Script Signing*" }
# Script'i imzala
Set-AuthenticodeSignature -FilePath "C:ScriptsmyScript.ps1" -Certificate $cert
# İmza durumunu kontrol et
$sig = Get-AuthenticodeSignature -FilePath "C:ScriptsmyScript.ps1"
Write-Host "İmza durumu: $($sig.Status)"
Write-Host "İmzalayan: $($sig.SignerCertificate.Subject)"
İmzalı bir scriptin sonunda şöyle bir blok görürsünüz:
# SIG # Begin signature block
# Bu blok script içeriğinin hash'ini ve sertifika bilgilerini içerir
# Script'te herhangi bir değişiklik yapıldığında imza geçersiz hale gelir
# SIG # End signature block
Bu özellik kritiktir: İmzalı bir scriptin bir tek karakteri bile değiştirilse imza geçersiz hale gelir ve script çalışmayı reddeder. Bu sayede script’in orijinalliğinden emin olabilirsiniz.
Group Policy ile Kurumsal Yönetim
Büyük ortamlarda execution policy’yi her sunucuda tek tek ayarlamak hem zaman kaybı hem de tutarsızlık riski demektir. Group Policy bu sorunu merkezi olarak çözer.
GPO yolu: Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell
Buradaki “Turn on Script Execution” politikasını aktif edip istediğiniz seviyeyi seçebilirsiniz. GPO üzerinden yapılan ayarlar MachinePolicy scope’unda görünür ve kullanıcılar tarafından Set-ExecutionPolicy ile değiştirilemez.
Toplu kontrol için:
# Domain'deki tüm sunucularda execution policy kontrol et
$sunucular = Get-ADComputer -Filter {OperatingSystem -like "*Windows Server*"} |
Select-Object -ExpandProperty Name
foreach ($sunucu in $sunucular) {
try {
$policy = Invoke-Command -ComputerName $sunucu -ScriptBlock {
Get-ExecutionPolicy -List
} -ErrorAction Stop
Write-Host "`n=== $sunucu ===" -ForegroundColor Cyan
$policy | Format-Table
}
catch {
Write-Host "$sunucu - BAĞLANTI HATASI: $($_.Exception.Message)" -ForegroundColor Red
}
}
Gerçek Dünya Senaryoları
Senaryo 1: Scheduled Task ile Script Çalıştırma
Birçok sysadmin’in yaşadığı klasik durum: Script PowerShell konsolunda çalışıyor ama Scheduled Task olarak çalıştırdığınızda hata veriyor. Bunun nedeni genellikle Task Scheduler’ın SYSTEM hesabı altında çalışması ve o scope’ta farklı bir policy uygulanmasıdır.
# Task Scheduler için güvenli script çalıştırma wrapper'ı
$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-NonInteractive -NoProfile -ExecutionPolicy RemoteSigned -File C:Scriptsbackup.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At "02:00"
$principal = New-ScheduledTaskPrincipal `
-UserId "SYSTEM" `
-LogonType ServiceAccount `
-RunLevel Highest
Register-ScheduledTask `
-TaskName "Gece Yedekleme" `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Description "Sunucu gece yedekleme görevi"
Senaryo 2: CI/CD Pipeline’da Policy Yönetimi
Azure DevOps veya Jenkins pipeline’larında PowerShell scriptleri çalıştırırken execution policy sorunlarıyla karşılaşabilirsiniz:
# Pipeline başında policy ayarla ve script çalıştır
# Azure DevOps PowerShell task alternatifi olarak inline script
$ErrorActionPreference = "Stop"
# Mevcut policy'yi kaydet
$mevcutPolicy = Get-ExecutionPolicy -Scope Process
try {
# Pipeline için geçici policy ayarla
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
Write-Host "Pipeline başlatıldı. Execution Policy: $(Get-ExecutionPolicy)"
# Deploy scriptini çalıştır
& ".deployDeploy-Application.ps1" -Environment "Production" -Version "2.1.0"
Write-Host "Deploy başarıyla tamamlandı." -ForegroundColor Green
}
catch {
Write-Error "Pipeline hatası: $($_.Exception.Message)"
exit 1
}
finally {
# Policy'yi eski haline döndür (Process scope kapanınca zaten sıfırlanır)
Write-Host "Pipeline tamamlandı."
}
Senaryo 3: Internet’ten İndirilen Scriptlerin Güvenli Kullanımı
Windows, internet’ten indirilen dosyaları Zone.Identifier adlı bir NTFS alternate data stream ile işaretler. RemoteSigned policy’de bu işaretli dosyalar imzasız çalışamaz:
# Dosyanın zone bilgisini kontrol et
Get-Item "C:Downloadsscript.ps1" -Stream Zone.Identifier
# Zone.Identifier içeriğini oku
Get-Content "C:Downloadsscript.ps1" -Stream Zone.Identifier
# Çıktı: [ZoneTransfer] ZoneId=3 (3 = Internet zone)
# Dosyayı "Unblock" et (Zone.Identifier'ı kaldır)
Unblock-File -Path "C:Downloadsscript.ps1"
# Ya da toplu unblock
Get-ChildItem "C:Downloads*.ps1" | Unblock-File
# Alternatif: PowerShell Properties > General > Unblock seçeneği
Bu işlemi yapmadan önce scriptin içeriğini mutlaka incelediğinizden emin olun. Unblock-File yasal ama potansiyel olarak tehlikeli bir scripti çalıştırmak için kapıyı açar.
Execution Policy’yi Atlatma Yöntemleri (ve Neden Önemli)
Bu bölümü yazmaktaki amacım saldırı teknikleri öğretmek değil, neden Execution Policy’nin tek başına yeterli olmadığını anlatmak. Saldırganların ve kötü niyetli kişilerin bu mekanizmayı nasıl bypass ettiğini bilmek, savunma stratejinizi şekillendirmenize yardımcı olur.
# Yöntem 1: Pipe ile çalıştırma (policy bypass eder)
Get-Content script.ps1 | Invoke-Expression
# Yöntem 2: Encoded command
powershell -EncodedCommand [base64_encoded_command]
# Yöntem 3: DownloadString ile direkt çalıştırma
# (Bu tam olarak neden AppLocker/WDAC gerektiğini gösterir)
# IEX (New-Object Net.WebClient).DownloadString('http://...')
# Yöntem 4: Process scope override
powershell -ExecutionPolicy Bypass -Command "..."
Bu bypass yöntemleri neden önemli? Çünkü kurumsal güvenlik stratejiniz sadece Execution Policy’ye dayanmamalı. Bu mekanizma yanlışlıkla yapılan hataları engeller, bilinçli saldırıları değil.
Gerçek Güvenlik İçin Ek Katmanlar
AppLocker ve WDAC
AppLocker, hangi uygulamaların ve scriptlerin çalışabileceğini publisher, path veya hash bazlı kurallarla kontrol eder:
# AppLocker policy durumunu kontrol et
Get-AppLockerPolicy -Effective | Test-AppLockerPolicy -Path "C:Scriptsbackup.ps1" -User Everyone
# Mevcut AppLocker kurallarını görüntüle
Get-AppLockerPolicy -Effective | Select-Object -ExpandProperty RuleCollections
PowerShell Script Block Logging
Execution Policy’yi atlatmaya çalışan aktiviteleri tespit etmek için Script Block Logging’i aktif edin:
# Script Block Logging'i aktif et (Registry)
$path = "HKLM:SOFTWAREPoliciesMicrosoftWindowsPowerShellScriptBlockLogging"
if (-not (Test-Path $path)) {
New-Item -Path $path -Force | Out-Null
}
Set-ItemProperty -Path $path -Name "EnableScriptBlockLogging" -Value 1
Set-ItemProperty -Path $path -Name "EnableScriptBlockInvocationLogging" -Value 1
Write-Host "Script Block Logging aktif edildi." -ForegroundColor Green
Write-Host "Loglar: Event Viewer > Applications and Services Logs > Microsoft > Windows > PowerShell > Operational"
Bu ayar ile PowerShell’de çalıştırılan her script bloğu, obfuscation çözüldükten sonra Event ID 4104 ile loglanır. SIEM sistemlerinize bu event’i besleyerek anormal PowerShell aktivitelerini tespit edebilirsiniz.
Constrained Language Mode
PowerShell’in Constrained Language Mode’u, execution policy’den çok daha güçlü bir kısıtlama mekanizmasıdır ve AppLocker ile birlikte otomatik aktif olur:
# Mevcut language mode'u kontrol et
$ExecutionContext.SessionState.LanguageMode
# FullLanguage veya ConstrainedLanguage
# Constrained Language Mode'da şunlar kısıtlanır:
# - COM nesnelerine erişim
# - .NET tip dönüşümleri
# - Add-Type cmdlet'i
# - Bazı değişken türleri
Policy Yönetimi için Best Practice Önerileri
Yıllar içinde edindiğim deneyimlerden çıkardığım pratik öneriler:
- Geliştirme ortamı: RemoteSigned seviyesi makul bir başlangıç noktasıdır. Geliştiricilerin kendi scriptlerini imzalamadan çalıştırmasına izin verirken dışarıdan gelen scriptlerde güvenlik sağlar.
- Test ortamı: RemoteSigned yeterli. Ama production ortamını mümkün olduğunca simüle etmek için AllSigned’ı test etmeyi düşünün.
- Production ortamı: İdeal olan AllSigned ile tüm scriptleri kurumsal CA üzerinden imzalamaktır. Bu mümkün değilse en azından GPO üzerinden RemoteSigned zorunlu kılın ve LocalMachine scope’unda manuel değişiklikleri kısıtlayın.
- Asla Unrestricted kullanmayın: Production’da Unrestricted görmek gerçekten endişe verici bir durumdur.
- Bypass’ı sadece gerektiğinde ve belgeleyerek kullanın: Eğer bir process Bypass ile çalışıyorsa, bunu dokümante edin ve nedenini açıklayın.
- Script Block Logging’i her zaman aktif tutun: Execution policy’yi kim ne şekilde manipüle etti, hangi scriptler çalıştı, bunları görebilmek priceless.
- Düzenli policy audit yapın: Özellikle domain ortamlarında GPO çakışmaları beklenmedik sonuçlar doğurabilir. Aylık bazda execution policy durumunu raporlayın.
Sonuç
PowerShell Execution Policy, doğru anlaşıldığında kurumsal ortamlarda çok değerli bir güvenlik ve politika yönetim aracıdır. Yanlış anlaşıldığında ise ya gereksiz bir engel olarak görülüp tamamen devre dışı bırakılır ya da sağladığı yalancı güvenlik hissiyle tek savunma katmanı olarak kullanılır; her iki durumda da problemli bir senaryoya girmiş olursunuz.
En kritik nokta şudur: Execution Policy bir security boundary değil, bir convenience feature ve yanlışlıkla yapılan hatalara karşı bir kalkan. Gerçek script güvenliği için bunu AppLocker veya WDAC ile birleştirin, Script Block Logging’i aktif tutun, scriptlerinizi kod imzalama sertifikasıyla imzalayın ve tüm bunları GPO üzerinden merkezi olarak yönetin.
Prod ortamında bir sorun çıktığında “execution policy’yi bypass ettim, çalıştı” demek kısa vadede işi çözer ama uzun vadede güvenlik açıkları ve denetlenemez bir ortam doğurur. Biraz fazla zaman harcayıp scriptlerinizi imzalamak ve policy’yi doğru yapılandırmak, gece 3’te bir security incident ile uğraşmaktan çok daha az yorucu.