脚本安全
PowerShell 作为强大的自动化工具,安全性至关重要。本章介绍 PowerShell 的安全机制,包括执行策略、脚本签名、凭据管理和安全最佳实践。
执行策略
执行策略概述
执行策略控制 PowerShell 脚本的运行条件,防止恶意脚本执行。
查看执行策略
Get-ExecutionPolicy
Get-ExecutionPolicy -List
执行策略类型
| 策略 | 说明 |
|---|---|
Restricted | 不允许运行任何脚本(Windows 客户端默认) |
AllSigned | 所有脚本必须经过签名 |
RemoteSigned | 本地脚本可运行,远程脚本需签名(Windows 服务器默认) |
Unrestricted | 允许所有脚本,远程脚本会提示 |
Bypass | 不阻止任何脚本,无警告 |
Undefined | 未设置,继承父作用域策略 |
设置执行策略
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine
作用域优先级(从高到低):
MachinePolicy- 组策略UserPolicy- 用户组策略Process- 当前进程CurrentUser- 当前用户LocalMachine- 本地计算机
临时绕过执行策略
powershell.exe -ExecutionPolicy Bypass -File script.ps1
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
脚本签名
代码签名证书
脚本签名需要代码签名证书。可以使用自签名证书测试:
$cert = New-SelfSignedCertificate `
-Subject "PowerShell Code Signing" `
-Type CodeSigningCert `
-CertStoreLocation "Cert:\CurrentUser\My"
企业环境应使用受信任的 CA 颁发的证书。
签名脚本
使用 Set-AuthenticodeSignature 签名:
$cert = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1
Set-AuthenticodeSignature -FilePath "C:\Scripts\script.ps1" -Certificate $cert
验证签名
Get-AuthenticodeSignature -FilePath "C:\Scripts\script.ps1"
签名状态:
| 状态 | 说明 |
|---|---|
Valid | 签名有效 |
NotSigned | 未签名 |
HashMismatch | 脚本被修改 |
UnknownError | 签名验证失败 |
时间戳服务器
添加时间戳确保签名长期有效:
Set-AuthenticodeSignature `
-FilePath "script.ps1" `
-Certificate $cert `
-TimestampServer "http://timestamp.digicert.com"
凭据管理
Get-Credential
安全获取凭据:
$cred = Get-Credential
$cred = Get-Credential -UserName "DOMAIN\admin" -Message "请输入密码"
安全存储凭据
Export-Clixml / Import-Clixml:
$cred | Export-Clixml -Path "C:\Secure\cred.xml"
$cred = Import-Clixml -Path "C:\Secure\cred.xml"
注意:此方法使用 Windows DPAPI 加密,只能在同一台计算机解密。
SecretManagement 模块(推荐):
Install-Module -Name Microsoft.PowerShell.SecretManagement
Install-Module -Name Microsoft.PowerShell.SecretStore
Register-SecretVault -Name "LocalStore" -ModuleName Microsoft.PowerShell.SecretStore
Set-Secret -Name "AdminCred" -Secret $cred
Get-Secret -Name "AdminCred"
避免硬编码密码
错误做法:
$password = "MyPassword123"
正确做法:
$password = Read-Host -AsSecureString
$cred = New-Object System.Management.Automation.PSCredential("admin", $password)
或从安全存储获取:
$cred = Get-Secret -Name "AdminCred"
安全字符串
转换为安全字符串
$plainPassword = "MyPassword"
$securePassword = ConvertTo-SecureString -String $plainPassword -AsPlainText -Force
从安全字符串转换
$plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword)
)
创建凭据对象
$username = "DOMAIN\admin"
$password = Read-Host -AsSecureString
$cred = New-Object System.Management.Automation.PSCredential($username, $password)
审计和日志
脚本块日志
启用脚本块日志记录:
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" -Name "EnableScriptBlockLogging" -Value 1
查看日志:
Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" |
Where-Object { $_.Id -eq 4104 }
模块日志
启用模块日志:
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging" -Name "EnableModuleLogging" -Value 1
转录日志
启用转录:
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription" -Name "EnableTranscripting" -Value 1
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription" -Name "OutputDirectory" -Value "C:\Transcripts"
手动启动转录:
Start-Transcript -Path "C:\Transcripts\session.log"
Stop-Transcript
受限语言模式
语言模式类型
| 模式 | 说明 |
|---|---|
FullLanguage | 完整功能(默认) |
ConstrainedLanguage | 限制危险操作 |
RestrictedLanguage | 仅允许基本命令 |
NoLanguage | 不允许脚本 |
设置语言模式
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
查看当前模式
$ExecutionContext.SessionState.LanguageMode
Application Control
AppLocker
AppLocker 可以限制哪些脚本可以运行:
Get-AppLockerPolicy -Effective
Get-AppLockerFileInformation -Path "C:\Scripts\script.ps1"
Windows Defender Application Control (WDAC)
更严格的控制机制,需要数字签名才能运行脚本。
安全最佳实践
1. 最小权限原则
使用最小必要权限运行脚本:
if ([Security.Principal.WindowsIdentity]::GetCurrent().Groups -contains [Security.Principal.WindowsBuiltInRole]::Administrator) {
Write-Warning "请勿以管理员身份运行此脚本"
exit
}
需要管理员权限时检查:
function Test-Administrator {
$user = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($user)
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
if (-not (Test-Administrator)) {
Write-Error "此脚本需要管理员权限"
exit 1
}
2. 输入验证
验证所有外部输入:
param(
[ValidatePattern("^[a-zA-Z0-9_-]+$")]
[string]$Username,
[ValidateRange(1, 65535)]
[int]$Port,
[ValidateSet("Development", "Staging", "Production")]
[string]$Environment
)
3. 避免注入攻击
不要拼接用户输入到命令中:
$name = Read-Host "输入进程名"
Get-Process -Name $name -ErrorAction SilentlyContinue
4. 安全的错误处理
不要暴露敏感信息:
try {
Connect-Database -Credential $cred
}
catch {
Write-Error "数据库连接失败,请联系管理员"
Write-Log -Message "数据库连接错误: $($_.Exception.Message)" -Level Error
}
5. 使用 TLS 1.2+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
6. 清理敏感数据
$password = Read-Host -AsSecureString
try {
}
finally {
$password.Dispose()
}
安全检查清单
- 执行策略设置为
RemoteSigned或更严格 - 生产脚本经过数字签名
- 凭据不硬编码在脚本中
- 启用脚本块日志记录
- 使用最小权限运行脚本
- 验证所有外部输入
- 敏感数据使用安全字符串
- 启用转录日志用于审计
- 定期审查脚本权限
下一步
完成安全学习后,可以查看 知识速查表,快速查阅常用命令和语法。