powershell-security

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

PowerShell 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
undefined
powershell
undefined

Install 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
undefined
Install-Module -Name Microsoft.PowerShell.SecretStore # Local encrypted vault Install-Module -Name Az.KeyVault # Azure Key Vault Install-Module -Name SecretManagement.KeePass # KeePass integration
undefined

Basic Usage

基础用法

powershell
undefined
powershell
undefined

Register 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
undefined
Remove-Secret -Name "DatabasePassword" -Vault LocalVault
undefined

Azure Key Vault Integration

Azure Key Vault集成

powershell
undefined
powershell
undefined

Install 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
    -ModuleName Az.KeyVault
-VaultParameters @{ AZKVaultName = 'MyKeyVault' SubscriptionId = 'your-subscription-id' }
Register-SecretVault -Name AzureKV
    -ModuleName Az.KeyVault
-VaultParameters @{ AZKVaultName = 'MyKeyVault' SubscriptionId = 'your-subscription-id' }

Store 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
undefined

Automation 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() } }
undefined
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() } }
undefined

Credential Management Best Practices

凭证管理最佳实践

Never Hardcode Credentials

绝不硬编码凭证

powershell
undefined
powershell
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
undefined
Connect-AzAccount -Identity
undefined

Service Principal Authentication (Azure)

服务主体认证(Azure)

powershell
undefined
powershell
undefined

Store 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
undefined

Just 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
undefined
powershell
undefined

1. 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"
    -SessionType RestrictedRemoteServer
-RoleDefinitions @{ 'DOMAIN\ServiceAdmins' = @{ RoleCapabilities = 'RestartServices' } } ` -LanguageMode NoLanguage
New-PSSessionConfigurationFile -Path "C:\JEA\RestartServices.pssc"
    -SessionType RestrictedRemoteServer
-RoleDefinitions @{ 'DOMAIN\ServiceAdmins' = @{ RoleCapabilities = 'RestartServices' } } ` -LanguageMode NoLanguage

3. Register JEA endpoint

3. Register JEA endpoint

Register-PSSessionConfiguration -Name RestartServices
    -Path "C:\JEA\RestartServices.pssc"
-Force
Register-PSSessionConfiguration -Name RestartServices
    -Path "C:\JEA\RestartServices.pssc"
-Force

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)
undefined
Restart-Service -Name Spooler # ✅ Allowed Restart-Service -Name DNS # ❌ Denied (not in ValidateSet) Get-Process # ❌ Denied (not visible)
undefined

JEA Audit Logging

JEA审计日志

powershell
undefined
powershell
undefined

Enable transcription and logging

Enable transcription and logging

New-PSSessionConfigurationFile -Path "C:\JEA\AuditedSession.pssc"
    -SessionType RestrictedRemoteServer
-TranscriptDirectory "C:\JEA\Transcripts" ` -RunAsVirtualAccount
New-PSSessionConfigurationFile -Path "C:\JEA\AuditedSession.pssc"
    -SessionType RestrictedRemoteServer
-TranscriptDirectory "C:\JEA\Transcripts" ` -RunAsVirtualAccount

All 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
undefined
Get-ChildItem "C:\JEA\Transcripts" | Get-Content
undefined

Windows Defender Application Control (WDAC)

Windows Defender Application Control (WDAC)

PowerShell Script Control

PowerShell脚本管控

WDAC replaces AppLocker for controlling which PowerShell scripts can execute.
powershell
undefined
WDAC取代AppLocker,用于管控可执行的PowerShell脚本。
powershell
undefined

Create WDAC policy for signed scripts only

Create WDAC policy for signed scripts only

New-CIPolicy -FilePath "C:\WDAC\PowerShellPolicy.xml"
    -ScanPath "C:\Scripts"
-Level FilePublisher
    -Fallback Hash
-UserPEs
New-CIPolicy -FilePath "C:\WDAC\PowerShellPolicy.xml"
    -ScanPath "C:\Scripts"
-Level FilePublisher
    -Fallback Hash
-UserPEs

Allow 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
undefined
Restart-Computer
undefined

Code Signing

脚本签名

Why Sign Scripts?

为何要签名脚本?

  • Verify script integrity
  • Meet organizational security policies
  • Enable WDAC enforcement
  • Prevent tampering
  • 验证脚本完整性
  • 满足组织安全策略
  • 启用WDAC强制管控
  • 防止篡改

Signing a Script

签名脚本

powershell
undefined
powershell
undefined

Get 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"
undefined

Execution Policy

执行策略

powershell
undefined
powershell
undefined

Check 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"
undefined
PowerShell.exe -ExecutionPolicy Bypass -File "script.ps1"
undefined

Constrained Language Mode

受限语言模式

What is Constrained Language Mode?

什么是受限语言模式?

Restricts PowerShell language features to prevent malicious code execution.
powershell
undefined
限制PowerShell语言特性,防止恶意代码执行。
powershell
undefined

Check 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
undefined

Script Block Logging

脚本块日志

Enable Logging

启用日志

powershell
undefined
powershell
undefined

Enable 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

undefined
undefined

Review Logs

查看日志

powershell
undefined
powershell
undefined

Query 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
undefined
Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" | Where-Object { $_.Id -eq 4104 } | # Script Block Logging event Select-Object TimeCreated, Message | Out-GridView
undefined

Input Validation

输入验证

Prevent Injection Attacks

防止注入攻击

powershell
undefined
powershell
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" }
undefined
function Restart-AppService { param( [ValidateSet('Web', 'API', 'Worker')] [string]$ServiceName ) Restart-Service -Name "App${ServiceName}Service" }
undefined

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
  • 绝不硬编码凭证(使用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

Resources

参考资源