Initial commit
This commit is contained in:
408
skills/powershell-security.md
Normal file
408
skills/powershell-security.md
Normal file
@@ -0,0 +1,408 @@
|
||||
---
|
||||
name: powershell-security
|
||||
description: Modern PowerShell security practices including SecretManagement, JEA, WDAC, and credential protection
|
||||
---
|
||||
|
||||
# PowerShell Security Best Practices (2025)
|
||||
|
||||
Modern security practices for PowerShell scripts and automation, including credential management, SecretManagement module, and hardening techniques.
|
||||
|
||||
## SecretManagement Module (Recommended 2025 Standard)
|
||||
|
||||
### Overview
|
||||
|
||||
**Microsoft.PowerShell.SecretManagement** is the official solution for secure credential storage in PowerShell.
|
||||
|
||||
**Why use SecretManagement:**
|
||||
- Never store plaintext credentials in scripts
|
||||
- Cross-platform secret storage
|
||||
- Multiple vault provider support
|
||||
- Integration with Azure Key Vault, 1Password, KeePass, etc.
|
||||
|
||||
### Installation
|
||||
|
||||
```powershell
|
||||
# Install SecretManagement module
|
||||
Install-Module -Name Microsoft.PowerShell.SecretManagement -Scope CurrentUser
|
||||
|
||||
# Install vault provider (choose one or more)
|
||||
Install-Module -Name Microsoft.PowerShell.SecretStore # Local encrypted vault
|
||||
Install-Module -Name Az.KeyVault # Azure Key Vault
|
||||
Install-Module -Name SecretManagement.KeePass # KeePass integration
|
||||
```
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```powershell
|
||||
# Register a vault
|
||||
Register-SecretVault -Name LocalVault -ModuleName Microsoft.PowerShell.SecretStore
|
||||
|
||||
# Store a secret
|
||||
$password = Read-Host -AsSecureString -Prompt "Enter password"
|
||||
Set-Secret -Name "DatabasePassword" -Secret $password -Vault LocalVault
|
||||
|
||||
# Retrieve a secret
|
||||
$dbPassword = Get-Secret -Name "DatabasePassword" -Vault LocalVault -AsPlainText
|
||||
# Or as SecureString
|
||||
$dbPasswordSecure = Get-Secret -Name "DatabasePassword" -Vault LocalVault
|
||||
|
||||
# List secrets
|
||||
Get-SecretInfo
|
||||
|
||||
# Remove a secret
|
||||
Remove-Secret -Name "DatabasePassword" -Vault LocalVault
|
||||
```
|
||||
|
||||
### Azure Key Vault Integration
|
||||
|
||||
```powershell
|
||||
# Install and import Az.KeyVault
|
||||
Install-Module -Name Az.KeyVault -Scope CurrentUser
|
||||
Import-Module Az.KeyVault
|
||||
|
||||
# Authenticate to Azure
|
||||
Connect-AzAccount
|
||||
|
||||
# Register Azure Key Vault as secret vault
|
||||
Register-SecretVault -Name AzureKV `
|
||||
-ModuleName Az.KeyVault `
|
||||
-VaultParameters @{
|
||||
AZKVaultName = 'MyKeyVault'
|
||||
SubscriptionId = 'your-subscription-id'
|
||||
}
|
||||
|
||||
# Store secret in Azure Key Vault
|
||||
Set-Secret -Name "ApiKey" -Secret "your-api-key" -Vault AzureKV
|
||||
|
||||
# Retrieve from Azure Key Vault
|
||||
$apiKey = Get-Secret -Name "ApiKey" -Vault AzureKV -AsPlainText
|
||||
```
|
||||
|
||||
### Automation Scripts with SecretManagement
|
||||
|
||||
```powershell
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Secure automation script using SecretManagement
|
||||
|
||||
.DESCRIPTION
|
||||
Demonstrates secure credential handling without hardcoded secrets
|
||||
#>
|
||||
|
||||
#Requires -Modules Microsoft.PowerShell.SecretManagement
|
||||
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
# Retrieve credentials from vault
|
||||
$dbConnectionString = Get-Secret -Name "SQLConnectionString" -AsPlainText
|
||||
$apiToken = Get-Secret -Name "APIToken" -AsPlainText
|
||||
|
||||
# Use credentials securely
|
||||
try {
|
||||
# Database operation
|
||||
$connection = New-Object System.Data.SqlClient.SqlConnection($dbConnectionString)
|
||||
$connection.Open()
|
||||
|
||||
# API call with token
|
||||
$headers = @{ Authorization = "Bearer $apiToken" }
|
||||
$response = Invoke-RestMethod -Uri "https://api.example.com/data" -Headers $headers
|
||||
|
||||
# Process results
|
||||
Write-Host "Operation completed successfully"
|
||||
}
|
||||
catch {
|
||||
Write-Error "Operation failed: $_"
|
||||
}
|
||||
finally {
|
||||
if ($connection) { $connection.Close() }
|
||||
}
|
||||
```
|
||||
|
||||
## Credential Management Best Practices
|
||||
|
||||
### Never Hardcode Credentials
|
||||
|
||||
```powershell
|
||||
# ❌ WRONG - Hardcoded credentials
|
||||
$password = "MyPassword123"
|
||||
$username = "admin"
|
||||
|
||||
# ❌ WRONG - Plaintext in script
|
||||
$cred = New-Object System.Management.Automation.PSCredential("admin", "password")
|
||||
|
||||
# ✅ CORRECT - SecretManagement
|
||||
$password = Get-Secret -Name "AdminPassword" -AsPlainText
|
||||
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
|
||||
$cred = New-Object System.Management.Automation.PSCredential("admin", $securePassword)
|
||||
|
||||
# ✅ CORRECT - Interactive prompt (for manual runs)
|
||||
$cred = Get-Credential -Message "Enter admin credentials"
|
||||
|
||||
# ✅ CORRECT - Managed Identity (Azure automation)
|
||||
Connect-AzAccount -Identity
|
||||
```
|
||||
|
||||
### Service Principal Authentication (Azure)
|
||||
|
||||
```powershell
|
||||
# Store service principal credentials in vault
|
||||
Set-Secret -Name "AzureAppId" -Secret "app-id-guid"
|
||||
Set-Secret -Name "AzureAppSecret" -Secret "app-secret-value"
|
||||
Set-Secret -Name "AzureTenantId" -Secret "tenant-id-guid"
|
||||
|
||||
# Retrieve and authenticate
|
||||
$appId = Get-Secret -Name "AzureAppId" -AsPlainText
|
||||
$appSecret = Get-Secret -Name "AzureAppSecret" -AsPlainText
|
||||
$tenantId = Get-Secret -Name "AzureTenantId" -AsPlainText
|
||||
|
||||
$secureSecret = ConvertTo-SecureString $appSecret -AsPlainText -Force
|
||||
$credential = New-Object System.Management.Automation.PSCredential($appId, $secureSecret)
|
||||
|
||||
Connect-AzAccount -ServicePrincipal -Credential $credential -Tenant $tenantId
|
||||
```
|
||||
|
||||
## Just Enough Administration (JEA)
|
||||
|
||||
### What is JEA?
|
||||
|
||||
**Just Enough Administration** restricts PowerShell remoting sessions to specific cmdlets and parameters.
|
||||
|
||||
### Use Cases
|
||||
|
||||
- Delegate admin tasks without full admin rights
|
||||
- Compliance requirements (SOC 2, HIPAA, PCI-DSS)
|
||||
- Production environment hardening
|
||||
- Audit trail for privileged operations
|
||||
|
||||
### Creating a JEA Endpoint
|
||||
|
||||
```powershell
|
||||
# 1. Create role capability file
|
||||
New-PSRoleCapabilityFile -Path "C:\JEA\RestartServices.psrc" `
|
||||
-VisibleCmdlets @{
|
||||
Name = 'Restart-Service'
|
||||
Parameters = @{
|
||||
Name = 'Name'
|
||||
ValidateSet = 'Spooler', 'W32Time', 'WinRM'
|
||||
}
|
||||
}, 'Get-Service'
|
||||
|
||||
# 2. Create session configuration file
|
||||
New-PSSessionConfigurationFile -Path "C:\JEA\RestartServices.pssc" `
|
||||
-SessionType RestrictedRemoteServer `
|
||||
-RoleDefinitions @{
|
||||
'DOMAIN\ServiceAdmins' = @{ RoleCapabilities = 'RestartServices' }
|
||||
} `
|
||||
-LanguageMode NoLanguage
|
||||
|
||||
# 3. Register JEA endpoint
|
||||
Register-PSSessionConfiguration -Name RestartServices `
|
||||
-Path "C:\JEA\RestartServices.pssc" `
|
||||
-Force
|
||||
|
||||
# 4. Connect to JEA endpoint (as delegated user)
|
||||
Enter-PSSession -ComputerName Server01 -ConfigurationName RestartServices
|
||||
|
||||
# User can ONLY run allowed commands
|
||||
Restart-Service -Name Spooler # ✅ Allowed
|
||||
Restart-Service -Name DNS # ❌ Denied (not in ValidateSet)
|
||||
Get-Process # ❌ Denied (not visible)
|
||||
```
|
||||
|
||||
### JEA Audit Logging
|
||||
|
||||
```powershell
|
||||
# Enable transcription and logging
|
||||
New-PSSessionConfigurationFile -Path "C:\JEA\AuditedSession.pssc" `
|
||||
-SessionType RestrictedRemoteServer `
|
||||
-TranscriptDirectory "C:\JEA\Transcripts" `
|
||||
-RunAsVirtualAccount
|
||||
|
||||
# All JEA sessions are transcribed to C:\JEA\Transcripts
|
||||
# Review audit logs
|
||||
Get-ChildItem "C:\JEA\Transcripts" | Get-Content
|
||||
```
|
||||
|
||||
## Windows Defender Application Control (WDAC)
|
||||
|
||||
### PowerShell Script Control
|
||||
|
||||
**WDAC** replaces AppLocker for controlling which PowerShell scripts can execute.
|
||||
|
||||
```powershell
|
||||
# Create WDAC policy for signed scripts only
|
||||
New-CIPolicy -FilePath "C:\WDAC\PowerShellPolicy.xml" `
|
||||
-ScanPath "C:\Scripts" `
|
||||
-Level FilePublisher `
|
||||
-Fallback Hash `
|
||||
-UserPEs
|
||||
|
||||
# Allow only signed scripts
|
||||
Set-RuleOption -FilePath "C:\WDAC\PowerShellPolicy.xml" `
|
||||
-Option 3 # Required WHQL
|
||||
|
||||
# Convert to binary policy
|
||||
ConvertFrom-CIPolicy -XmlFilePath "C:\WDAC\PowerShellPolicy.xml" `
|
||||
-BinaryFilePath "C:\Windows\System32\CodeIntegrity\SIPolicy.p7b"
|
||||
|
||||
# Reboot to apply policy
|
||||
Restart-Computer
|
||||
```
|
||||
|
||||
## Code Signing
|
||||
|
||||
### Why Sign Scripts?
|
||||
|
||||
- Verify script integrity
|
||||
- Meet organizational security policies
|
||||
- Enable WDAC enforcement
|
||||
- Prevent tampering
|
||||
|
||||
### Signing a Script
|
||||
|
||||
```powershell
|
||||
# Get code signing certificate
|
||||
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert
|
||||
|
||||
# Sign script
|
||||
Set-AuthenticodeSignature -FilePath "C:\Scripts\MyScript.ps1" -Certificate $cert
|
||||
|
||||
# Verify signature
|
||||
$signature = Get-AuthenticodeSignature -FilePath "C:\Scripts\MyScript.ps1"
|
||||
$signature.Status # Should be "Valid"
|
||||
```
|
||||
|
||||
### Execution Policy
|
||||
|
||||
```powershell
|
||||
# Check current execution policy
|
||||
Get-ExecutionPolicy
|
||||
|
||||
# Set execution policy (requires admin)
|
||||
Set-ExecutionPolicy RemoteSigned -Scope LocalMachine
|
||||
|
||||
# Bypass for single script (testing only)
|
||||
PowerShell.exe -ExecutionPolicy Bypass -File "script.ps1"
|
||||
```
|
||||
|
||||
## Constrained Language Mode
|
||||
|
||||
### What is Constrained Language Mode?
|
||||
|
||||
Restricts PowerShell language features to prevent malicious code execution.
|
||||
|
||||
```powershell
|
||||
# Check current language mode
|
||||
$ExecutionContext.SessionState.LanguageMode
|
||||
# Output: FullLanguage (admin) or ConstrainedLanguage (standard user)
|
||||
|
||||
# Set system-wide constrained language mode
|
||||
# Via Environment Variable or Group Policy
|
||||
# Set: __PSLockdownPolicy = 4
|
||||
|
||||
# Test constrained mode behavior
|
||||
# FullLanguage allows:
|
||||
[System.Net.WebClient]::new() # ✅ Allowed
|
||||
|
||||
# ConstrainedLanguage blocks:
|
||||
[System.Net.WebClient]::new() # ❌ Blocked
|
||||
Add-Type -TypeDefinition "..." # ❌ Blocked
|
||||
```
|
||||
|
||||
## Script Block Logging
|
||||
|
||||
### Enable Logging
|
||||
|
||||
```powershell
|
||||
# Enable via Group Policy or Registry
|
||||
# HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging
|
||||
New-ItemProperty -Path "HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" `
|
||||
-Name "EnableScriptBlockLogging" -Value 1 -PropertyType DWord
|
||||
|
||||
# Log location: Windows Event Log
|
||||
# Event Viewer > Applications and Services Logs > Microsoft > Windows > PowerShell > Operational
|
||||
```
|
||||
|
||||
### Review Logs
|
||||
|
||||
```powershell
|
||||
# Query script block logs
|
||||
Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" |
|
||||
Where-Object { $_.Id -eq 4104 } | # Script Block Logging event
|
||||
Select-Object TimeCreated, Message |
|
||||
Out-GridView
|
||||
```
|
||||
|
||||
## Input Validation
|
||||
|
||||
### Prevent Injection Attacks
|
||||
|
||||
```powershell
|
||||
# ❌ WRONG - No validation
|
||||
function Get-UserData {
|
||||
param($Username)
|
||||
Invoke-Sqlcmd -Query "SELECT * FROM Users WHERE Username = '$Username'"
|
||||
}
|
||||
# Vulnerable to SQL injection
|
||||
|
||||
# ✅ CORRECT - Parameterized queries
|
||||
function Get-UserData {
|
||||
param(
|
||||
[ValidatePattern('^[a-zA-Z0-9_-]+$')]
|
||||
[string]$Username
|
||||
)
|
||||
Invoke-Sqlcmd -Query "SELECT * FROM Users WHERE Username = @Username" `
|
||||
-Variable @{Username=$Username}
|
||||
}
|
||||
|
||||
# ✅ CORRECT - ValidateSet for known values
|
||||
function Restart-AppService {
|
||||
param(
|
||||
[ValidateSet('Web', 'API', 'Worker')]
|
||||
[string]$ServiceName
|
||||
)
|
||||
Restart-Service -Name "App${ServiceName}Service"
|
||||
}
|
||||
```
|
||||
|
||||
## Security Checklist
|
||||
|
||||
### Script Development
|
||||
|
||||
- [ ] Never hardcode credentials (use SecretManagement)
|
||||
- [ ] Use parameterized queries for SQL operations
|
||||
- [ ] Validate all user input with `[ValidatePattern]`, `[ValidateSet]`, etc.
|
||||
- [ ] Enable `Set-StrictMode -Version Latest`
|
||||
- [ ] Use `try/catch` for error handling
|
||||
- [ ] Avoid `Invoke-Expression` with user input
|
||||
- [ ] Sign production scripts
|
||||
- [ ] Enable Script Block Logging
|
||||
|
||||
### Automation
|
||||
|
||||
- [ ] Use Managed Identity or Service Principal (never passwords)
|
||||
- [ ] Store secrets in SecretManagement or Azure Key Vault
|
||||
- [ ] Implement JEA for delegated admin tasks
|
||||
- [ ] Enable audit logging for all privileged operations
|
||||
- [ ] Use least privilege principle
|
||||
- [ ] Rotate credentials regularly
|
||||
- [ ] Monitor failed authentication attempts
|
||||
|
||||
### Production Environments
|
||||
|
||||
- [ ] Implement WDAC policies for script control
|
||||
- [ ] Use Constrained Language Mode for non-admin users
|
||||
- [ ] Enable PowerShell logging (Script Block + Transcription)
|
||||
- [ ] Require signed scripts (via execution policy)
|
||||
- [ ] Regular security audits
|
||||
- [ ] Keep PowerShell updated (7.5+)
|
||||
- [ ] Use JEA for remote administration
|
||||
|
||||
## Resources
|
||||
|
||||
- [SecretManagement Documentation](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.secretmanagement)
|
||||
- [JEA Documentation](https://learn.microsoft.com/en-us/powershell/scripting/security/remoting/jea/overview)
|
||||
- [WDAC Documentation](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control)
|
||||
- [PowerShell Security Best Practices](https://learn.microsoft.com/en-us/powershell/scripting/security/securing-powershell)
|
||||
- [Azure Key Vault](https://learn.microsoft.com/en-us/azure/key-vault/)
|
||||
Reference in New Issue
Block a user