powershell-security
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePowerShell Security Best Practices (2025)
PowerShell安全最佳实践(2025)
Modern security practices for PowerShell scripts and automation, including credential management, SecretManagement module, and hardening techniques.
适用于PowerShell脚本与自动化的现代化安全实践,包括凭证管理、SecretManagement模块及加固技术。
SecretManagement Module (Recommended 2025 Standard)
SecretManagement模块(2025推荐标准)
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.
Microsoft.PowerShell.SecretManagement是PowerShell中用于安全存储凭证的官方解决方案。
为何使用SecretManagement:
- 绝不在脚本中存储明文凭证
- 跨平台的机密存储
- 支持多种密钥库提供商
- 与Azure Key Vault、1Password、KeePass等集成
Installation
安装
powershell
undefinedpowershell
undefinedInstall SecretManagement module
Install SecretManagement module
Install-Module -Name Microsoft.PowerShell.SecretManagement -Scope CurrentUser
Install-Module -Name Microsoft.PowerShell.SecretManagement -Scope CurrentUser
Install vault provider (choose one or more)
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
undefinedInstall-Module -Name Microsoft.PowerShell.SecretStore # Local encrypted vault
Install-Module -Name Az.KeyVault # Azure Key Vault
Install-Module -Name SecretManagement.KeePass # KeePass integration
undefinedBasic Usage
基础用法
powershell
undefinedpowershell
undefinedRegister a vault
Register a vault
Register-SecretVault -Name LocalVault -ModuleName Microsoft.PowerShell.SecretStore
Register-SecretVault -Name LocalVault -ModuleName Microsoft.PowerShell.SecretStore
Store a secret
Store a secret
$password = Read-Host -AsSecureString -Prompt "Enter password"
Set-Secret -Name "DatabasePassword" -Secret $password -Vault LocalVault
$password = Read-Host -AsSecureString -Prompt "Enter password"
Set-Secret -Name "DatabasePassword" -Secret $password -Vault LocalVault
Retrieve a secret
Retrieve a secret
$dbPassword = Get-Secret -Name "DatabasePassword" -Vault LocalVault -AsPlainText
$dbPassword = Get-Secret -Name "DatabasePassword" -Vault LocalVault -AsPlainText
Or as SecureString
Or as SecureString
$dbPasswordSecure = Get-Secret -Name "DatabasePassword" -Vault LocalVault
$dbPasswordSecure = Get-Secret -Name "DatabasePassword" -Vault LocalVault
List secrets
List secrets
Get-SecretInfo
Get-SecretInfo
Remove a secret
Remove a secret
Remove-Secret -Name "DatabasePassword" -Vault LocalVault
undefinedRemove-Secret -Name "DatabasePassword" -Vault LocalVault
undefinedAzure Key Vault Integration
Azure Key Vault集成
powershell
undefinedpowershell
undefinedInstall and import Az.KeyVault
Install and import Az.KeyVault
Install-Module -Name Az.KeyVault -Scope CurrentUser
Import-Module Az.KeyVault
Install-Module -Name Az.KeyVault -Scope CurrentUser
Import-Module Az.KeyVault
Authenticate to Azure
Authenticate to Azure
Connect-AzAccount
Connect-AzAccount
Register Azure Key Vault as secret vault
Register Azure Key Vault as secret vault
Register-SecretVault -Name AzureKV
-VaultParameters @{
AZKVaultName = 'MyKeyVault'
SubscriptionId = 'your-subscription-id'
}
-ModuleName Az.KeyVaultRegister-SecretVault -Name AzureKV
-VaultParameters @{
AZKVaultName = 'MyKeyVault'
SubscriptionId = 'your-subscription-id'
}
-ModuleName Az.KeyVaultStore secret in Azure Key Vault
Store secret in Azure Key Vault
Set-Secret -Name "ApiKey" -Secret "your-api-key" -Vault AzureKV
Set-Secret -Name "ApiKey" -Secret "your-api-key" -Vault AzureKV
Retrieve from Azure Key Vault
Retrieve from Azure Key Vault
$apiKey = Get-Secret -Name "ApiKey" -Vault AzureKV -AsPlainText
undefined$apiKey = Get-Secret -Name "ApiKey" -Vault AzureKV -AsPlainText
undefinedAutomation Scripts with SecretManagement
结合SecretManagement的自动化脚本
powershell
<#
.SYNOPSIS
Secure automation script using SecretManagement
.DESCRIPTION
Demonstrates secure credential handling without hardcoded secrets
#>
#Requires -Modules Microsoft.PowerShell.SecretManagement
[CmdletBinding()]
param()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
Retrieve credentials from vault
$dbConnectionString = Get-Secret -Name "SQLConnectionString" -AsPlainText
$apiToken = Get-Secret -Name "APIToken" -AsPlainText
$dbConnectionString = Get-Secret -Name "SQLConnectionString" -AsPlainText
$apiToken = Get-Secret -Name "APIToken" -AsPlainText
Use credentials securely
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() }
}
undefinedtry {
# 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() }
}
undefinedCredential Management Best Practices
凭证管理最佳实践
Never Hardcode Credentials
绝不硬编码凭证
powershell
undefinedpowershell
undefined❌ WRONG - Hardcoded credentials
❌ WRONG - Hardcoded credentials
$password = "MyPassword123"
$username = "admin"
$password = "MyPassword123"
$username = "admin"
❌ WRONG - Plaintext in script
❌ WRONG - Plaintext in script
$cred = New-Object System.Management.Automation.PSCredential("admin", "password")
$cred = New-Object System.Management.Automation.PSCredential("admin", "password")
✅ CORRECT - SecretManagement
✅ CORRECT - SecretManagement
$password = Get-Secret -Name "AdminPassword" -AsPlainText
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential("admin", $securePassword)
$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)
✅ CORRECT - Interactive prompt (for manual runs)
$cred = Get-Credential -Message "Enter admin credentials"
$cred = Get-Credential -Message "Enter admin credentials"
✅ CORRECT - Managed Identity (Azure automation)
✅ CORRECT - Managed Identity (Azure automation)
Connect-AzAccount -Identity
undefinedConnect-AzAccount -Identity
undefinedService Principal Authentication (Azure)
服务主体认证(Azure)
powershell
undefinedpowershell
undefinedStore service principal credentials in vault
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"
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
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
undefined$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
undefinedJust Enough Administration (JEA)
Just Enough Administration (JEA)
What is JEA?
什么是JEA?
Just Enough Administration restricts PowerShell remoting sessions to specific cmdlets and parameters.
Just Enough Administration(最小权限管理)可限制PowerShell远程会话仅能使用特定cmdlet和参数。
Use Cases
使用场景
- Delegate admin tasks without full admin rights
- Compliance requirements (SOC 2, HIPAA, PCI-DSS)
- Production environment hardening
- Audit trail for privileged operations
- 在不授予完整管理员权限的前提下委派管理任务
- 满足合规要求(SOC 2、HIPAA、PCI-DSS)
- 生产环境加固
- 特权操作的审计追踪
Creating a JEA Endpoint
创建JEA端点
powershell
undefinedpowershell
undefined1. Create role capability file
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'
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
2. Create session configuration file
New-PSSessionConfigurationFile -Path "C:\JEA\RestartServices.pssc"
-RoleDefinitions @{
'DOMAIN\ServiceAdmins' = @{ RoleCapabilities = 'RestartServices' }
} `
-LanguageMode NoLanguage
-SessionType RestrictedRemoteServerNew-PSSessionConfigurationFile -Path "C:\JEA\RestartServices.pssc"
-RoleDefinitions @{
'DOMAIN\ServiceAdmins' = @{ RoleCapabilities = 'RestartServices' }
} `
-LanguageMode NoLanguage
-SessionType RestrictedRemoteServer3. Register JEA endpoint
3. Register JEA endpoint
Register-PSSessionConfiguration -Name RestartServices
-Force
-Path "C:\JEA\RestartServices.pssc"Register-PSSessionConfiguration -Name RestartServices
-Force
-Path "C:\JEA\RestartServices.pssc"4. Connect to JEA endpoint (as delegated user)
4. Connect to JEA endpoint (as delegated user)
Enter-PSSession -ComputerName Server01 -ConfigurationName RestartServices
Enter-PSSession -ComputerName Server01 -ConfigurationName RestartServices
User can ONLY run allowed commands
User can ONLY run allowed commands
Restart-Service -Name Spooler # ✅ Allowed
Restart-Service -Name DNS # ❌ Denied (not in ValidateSet)
Get-Process # ❌ Denied (not visible)
undefinedRestart-Service -Name Spooler # ✅ Allowed
Restart-Service -Name DNS # ❌ Denied (not in ValidateSet)
Get-Process # ❌ Denied (not visible)
undefinedJEA Audit Logging
JEA审计日志
powershell
undefinedpowershell
undefinedEnable transcription and logging
Enable transcription and logging
New-PSSessionConfigurationFile -Path "C:\JEA\AuditedSession.pssc"
-TranscriptDirectory "C:\JEA\Transcripts" `
-RunAsVirtualAccount
-SessionType RestrictedRemoteServerNew-PSSessionConfigurationFile -Path "C:\JEA\AuditedSession.pssc"
-TranscriptDirectory "C:\JEA\Transcripts" `
-RunAsVirtualAccount
-SessionType RestrictedRemoteServerAll JEA sessions are transcribed to C:\JEA\Transcripts
All JEA sessions are transcribed to C:\JEA\Transcripts
Review audit logs
Review audit logs
Get-ChildItem "C:\JEA\Transcripts" | Get-Content
undefinedGet-ChildItem "C:\JEA\Transcripts" | Get-Content
undefinedWindows Defender Application Control (WDAC)
Windows Defender Application Control (WDAC)
PowerShell Script Control
PowerShell脚本管控
WDAC replaces AppLocker for controlling which PowerShell scripts can execute.
powershell
undefinedWDAC取代AppLocker,用于管控可执行的PowerShell脚本。
powershell
undefinedCreate WDAC policy for signed scripts only
Create WDAC policy for signed scripts only
New-CIPolicy -FilePath "C:\WDAC\PowerShellPolicy.xml"
-Level FilePublisher
-UserPEs
-ScanPath "C:\Scripts" -Fallback HashNew-CIPolicy -FilePath "C:\WDAC\PowerShellPolicy.xml"
-Level FilePublisher
-UserPEs
-ScanPath "C:\Scripts" -Fallback HashAllow only signed scripts
Allow only signed scripts
Set-RuleOption -FilePath "C:\WDAC\PowerShellPolicy.xml" `
-Option 3 # Required WHQL
Set-RuleOption -FilePath "C:\WDAC\PowerShellPolicy.xml" `
-Option 3 # Required WHQL
Convert to binary policy
Convert to binary policy
ConvertFrom-CIPolicy -XmlFilePath "C:\WDAC\PowerShellPolicy.xml" `
-BinaryFilePath "C:\Windows\System32\CodeIntegrity\SIPolicy.p7b"
ConvertFrom-CIPolicy -XmlFilePath "C:\WDAC\PowerShellPolicy.xml" `
-BinaryFilePath "C:\Windows\System32\CodeIntegrity\SIPolicy.p7b"
Reboot to apply policy
Reboot to apply policy
Restart-Computer
undefinedRestart-Computer
undefinedCode Signing
脚本签名
Why Sign Scripts?
为何要签名脚本?
- Verify script integrity
- Meet organizational security policies
- Enable WDAC enforcement
- Prevent tampering
- 验证脚本完整性
- 满足组织安全策略
- 启用WDAC强制管控
- 防止篡改
Signing a Script
签名脚本
powershell
undefinedpowershell
undefinedGet code signing certificate
Get code signing certificate
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert
Sign script
Sign script
Set-AuthenticodeSignature -FilePath "C:\Scripts\MyScript.ps1" -Certificate $cert
Set-AuthenticodeSignature -FilePath "C:\Scripts\MyScript.ps1" -Certificate $cert
Verify signature
Verify signature
$signature = Get-AuthenticodeSignature -FilePath "C:\Scripts\MyScript.ps1"
$signature.Status # Should be "Valid"
undefined$signature = Get-AuthenticodeSignature -FilePath "C:\Scripts\MyScript.ps1"
$signature.Status # Should be "Valid"
undefinedExecution Policy
执行策略
powershell
undefinedpowershell
undefinedCheck current execution policy
Check current execution policy
Get-ExecutionPolicy
Get-ExecutionPolicy
Set execution policy (requires admin)
Set execution policy (requires admin)
Set-ExecutionPolicy RemoteSigned -Scope LocalMachine
Set-ExecutionPolicy RemoteSigned -Scope LocalMachine
Bypass for single script (testing only)
Bypass for single script (testing only)
PowerShell.exe -ExecutionPolicy Bypass -File "script.ps1"
undefinedPowerShell.exe -ExecutionPolicy Bypass -File "script.ps1"
undefinedConstrained Language Mode
受限语言模式
What is Constrained Language Mode?
什么是受限语言模式?
Restricts PowerShell language features to prevent malicious code execution.
powershell
undefined限制PowerShell语言特性,防止恶意代码执行。
powershell
undefinedCheck current language mode
Check current language mode
$ExecutionContext.SessionState.LanguageMode
$ExecutionContext.SessionState.LanguageMode
Output: FullLanguage (admin) or ConstrainedLanguage (standard user)
Output: FullLanguage (admin) or ConstrainedLanguage (standard user)
Set system-wide constrained language mode
Set system-wide constrained language mode
Via Environment Variable or Group Policy
Via Environment Variable or Group Policy
Set: __PSLockdownPolicy = 4
Set: __PSLockdownPolicy = 4
Test constrained mode behavior
Test constrained mode behavior
FullLanguage allows:
FullLanguage allows:
[System.Net.WebClient]::new() # ✅ Allowed
[System.Net.WebClient]::new() # ✅ Allowed
ConstrainedLanguage blocks:
ConstrainedLanguage blocks:
[System.Net.WebClient]::new() # ❌ Blocked
Add-Type -TypeDefinition "..." # ❌ Blocked
undefined[System.Net.WebClient]::new() # ❌ Blocked
Add-Type -TypeDefinition "..." # ❌ Blocked
undefinedScript Block Logging
脚本块日志
Enable Logging
启用日志
powershell
undefinedpowershell
undefinedEnable via Group Policy or Registry
Enable via Group Policy or Registry
HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging
HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging
New-ItemProperty -Path "HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" `
-Name "EnableScriptBlockLogging" -Value 1 -PropertyType DWord
New-ItemProperty -Path "HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" `
-Name "EnableScriptBlockLogging" -Value 1 -PropertyType DWord
Log location: Windows Event Log
Log location: Windows Event Log
Event Viewer > Applications and Services Logs > Microsoft > Windows > PowerShell > Operational
Event Viewer > Applications and Services Logs > Microsoft > Windows > PowerShell > Operational
undefinedundefinedReview Logs
查看日志
powershell
undefinedpowershell
undefinedQuery script block logs
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
undefinedGet-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" |
Where-Object { $_.Id -eq 4104 } | # Script Block Logging event
Select-Object TimeCreated, Message |
Out-GridView
undefinedInput Validation
输入验证
Prevent Injection Attacks
防止注入攻击
powershell
undefinedpowershell
undefined❌ WRONG - No validation
❌ WRONG - No validation
function Get-UserData {
param($Username)
Invoke-Sqlcmd -Query "SELECT * FROM Users WHERE Username = '$Username'"
}
function Get-UserData {
param($Username)
Invoke-Sqlcmd -Query "SELECT * FROM Users WHERE Username = '$Username'"
}
Vulnerable to SQL injection
Vulnerable to SQL injection
✅ CORRECT - Parameterized queries
✅ 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}
}
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
✅ CORRECT - ValidateSet for known values
function Restart-AppService {
param(
[ValidateSet('Web', 'API', 'Worker')]
[string]$ServiceName
)
Restart-Service -Name "App${ServiceName}Service"
}
undefinedfunction Restart-AppService {
param(
[ValidateSet('Web', 'API', 'Worker')]
[string]$ServiceName
)
Restart-Service -Name "App${ServiceName}Service"
}
undefinedSecurity Checklist
安全检查清单
Script Development
脚本开发
- Never hardcode credentials (use SecretManagement)
- Use parameterized queries for SQL operations
- Validate all user input with ,
[ValidatePattern], etc.[ValidateSet] - Enable
Set-StrictMode -Version Latest - Use for error handling
try/catch - Avoid with user input
Invoke-Expression - Sign production scripts
- Enable Script Block Logging
- 绝不硬编码凭证(使用SecretManagement)
- 对SQL操作使用参数化查询
- 使用、
[ValidatePattern]等验证所有用户输入[ValidateSet] - 启用
Set-StrictMode -Version Latest - 使用进行错误处理
try/catch - 避免将用户输入传入
Invoke-Expression - 为生产环境脚本签名
- 启用脚本块日志
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
- 使用托管标识或服务主体(绝不使用密码)
- 将机密存储在SecretManagement或Azure Key Vault中
- 为委派管理任务实现JEA
- 为所有特权操作启用审计日志
- 遵循最小权限原则
- 定期轮换凭证
- 监控失败的认证尝试
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
- 实施WDAC策略以管控脚本
- 为非管理员用户启用受限语言模式
- 启用PowerShell日志(脚本块+转录)
- 通过执行策略要求脚本签名
- 定期进行安全审计
- 保持PowerShell为最新版本(7.5+)
- 为远程管理使用JEA