关于 powershell-security
介绍凭据管理、脚本加固和管理控制的 PowerShell 安全技能,面向编写脚本的人员和管理员,涵盖 SecretManagement、JEA、WDAC、签名与日志记录。
主要功能
- 01SecretManagement 与保管库提供程序指引
- 02凭据处理与 Azure 身份验证
- 03JEA 角色限制和审计日志
- 04WDAC 与受限语言控制
- 05脚本签名和执行策略
- 06输入验证与安全检查清单
适用场景
- 01移除脚本中的明文凭据
- 02限制委派的 PowerShell 管理权限
- 03审计特权自动化会话
- 04加固用于生产环境的脚本
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ josiahsiegel/ claude-plugin-marketplace --skill powershell-security安装后在 Claude Code 中输入/powershell-security确认已加载。
详细介绍
PowerShell 安全最佳实践(2025)
PowerShell 脚本和自动化的现代安全实践,包括凭据管理、SecretManagement 模块和强化技术。
SecretManagement 模块(2025 年推荐标准)
概述
Microsoft.PowerShell.SecretManagement 是 PowerShell 中安全存储凭据的官方解决方案。
使用 SecretManagement 的原因:
- 切勿在脚本中存储明文凭据
- 跨平台密钥存储
- 支持多种保险库提供程序
- 与 Azure Key Vault、1Password、KeePass 等集成
安装
# 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
基本用法
# 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 集成
# 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
使用 SecretManagement 的自动化脚本
<#
.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() }
}
凭据管理最佳实践
切勿硬编码凭据
# ❌ 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
服务主体身份验证(Azure)
# 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
最小权限管理(JEA)
什么是 JEA?
最小权限管理会将 PowerShell 远程处理会话限制为特定的 cmdlet 和参数。
使用场景
- 委派管理任务,无需授予完整管理员权限
- 满足合规性要求(SOC 2、HIPAA、PCI-DSS)
- 强化生产环境安全
- 为特权操作保留审计跟踪
创建 JEA 端点
# 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 审计日志记录
# 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 应用程序控制(WDAC)
PowerShell 脚本控制
WDAC 取代 AppLocker,用于控制哪些 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
代码签名
为什么要对脚本进行签名?
- 验证脚本完整性
- 满足组织安全策略
- 启用 WDAC 强制实施
- 防止篡改
对脚本进行签名
# 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"
执行策略
# 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"
受限语言模式
什么是受限语言模式?
限制 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
脚本块日志记录
启用日志记录
# 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
查看日志
# 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
输入验证
防止注入攻击
# ❌ 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"
}
安全检查清单
脚本开发
- 切勿硬编码凭据(使用 SecretManagement)
- 对 SQL 操作使用参数化查询
- 使用
[ValidatePattern]、[ValidateSet]等验证所有用户输入 - 启用
Set-StrictMode -Version Latest - 使用
try/catch处理错误 - 避免将用户输入与
Invoke-Expression一起使用 - 对生产脚本进行签名
- 启用脚本块日志记录
自动化
- 使用托管身份或服务主体(切勿使用密码)
- 将密钥存储在 SecretManagement 或 Azure Key Vault 中
- 为委派的管理任务实施 JEA
- 为所有特权操作启用审计日志记录
- 遵循最小权限原则
- 定期轮换凭据
- 监控身份验证失败尝试
生产环境
- 实施 WDAC 策略以控制脚本
- 为非管理员用户使用受限语言模式
- 启用 PowerShell 日志记录(脚本块 + 转录)
- 要求脚本经过签名(通过执行策略)
- 定期进行安全审计
- 保持 PowerShell 为最新版本(7.5+)
- 使用 JEA 进行远程管理
资源
评论
支持 Markdown