跳到主要内容

脚本安全

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

作用域优先级(从高到低):

  1. MachinePolicy - 组策略
  2. UserPolicy - 用户组策略
  3. Process - 当前进程
  4. CurrentUser - 当前用户
  5. 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 或更严格
  • 生产脚本经过数字签名
  • 凭据不硬编码在脚本中
  • 启用脚本块日志记录
  • 使用最小权限运行脚本
  • 验证所有外部输入
  • 敏感数据使用安全字符串
  • 启用转录日志用于审计
  • 定期审查脚本权限

下一步

完成安全学习后,可以查看 知识速查表,快速查阅常用命令和语法。