在企业IT环境中,脚本安全是保障系统稳定运行的重要防线。今天我们就来聊聊如何通过签名和验证机制,让你的PowerShell脚本既安全又可信。

一、为什么要给脚本签名?

想象一下,如果你的电脑能随便运行来历不明的脚本,就像随便吃陌生人给的糖果一样危险。脚本签名就像给糖果贴上质检标签,让我们知道它来自可信的源头,没有被篡改过。

在企业里,这特别重要。管理员写的脚本可能涉及重启服务、修改注册表等敏感操作。如果这些脚本被恶意修改,后果不堪设想。

二、PowerShell执行策略基础

PowerShell有个"门卫"叫执行策略,它决定了哪些脚本能运行。常见的有:

  • Restricted:默认设置,禁止所有脚本运行
  • AllSigned:只运行经过签名的脚本
  • RemoteSigned:本地脚本可运行,但下载的必须签名
  • Unrestricted:所有脚本都能运行(不推荐)

查看当前策略:

# 技术栈:PowerShell 5.1
Get-ExecutionPolicy -List

设置策略(需要管理员权限):

# 设置当前用户作用域的执行策略
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned -Force

三、创建自签名证书

正式环境建议使用企业CA证书,但测试时我们可以自己创建证书:

# 创建自签名证书(需要管理员权限)
$cert = New-SelfSignedCertificate `
    -Type CodeSigningCert `
    -Subject "CN=PowerShell Script Signing" `
    -KeyUsage DigitalSignature `
    -KeyAlgorithm RSA `
    -KeyLength 2048 `
    -CertStoreLocation "Cert:\CurrentUser\My"

# 将证书导出为PFX文件(设置密码保护)
$certPassword = ConvertTo-SecureString -String "YourPassword123!" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath "C:\certs\scriptSigning.pfx" -Password $certPassword

四、给脚本签名

有了证书后,就可以给脚本签名了:

# 获取证书
$cert = Get-ChildItem -Path Cert:\CurrentUser\My | 
        Where-Object { $_.Subject -eq "CN=PowerShell Script Signing" } |
        Select-Object -First 1

# 给脚本签名
Set-AuthenticodeSignature -FilePath "C:\scripts\Update-Service.ps1" -Certificate $cert

签名后的脚本末尾会多出一段数字签名块,看起来像这样:

# SIG # Begin signature block
# MIIJnwYJKoZIhvcNAQcCoIIJkDCCCYwCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# ...

五、验证脚本签名

运行前验证签名很重要:

# 验证脚本签名
$sig = Get-AuthenticodeSignature -FilePath "C:\scripts\Update-Service.ps1"

# 检查签名状态
$sig.Status

可能的返回值:

  • Valid:签名有效
  • UnknownError:未知错误
  • NotSigned:未签名
  • HashMismatch:哈希不匹配(内容被修改)

六、企业级部署方案

在实际企业环境中,建议这样做:

  1. 使用企业CA颁发代码签名证书
  2. 通过组策略分发受信任的根证书
  3. 设置AllSigned执行策略
  4. 建立脚本发布审核流程

自动化签名示例:

# 批量签名目录下所有ps1文件
Get-ChildItem -Path "C:\scripts\" -Filter *.ps1 | ForEach-Object {
    Set-AuthenticodeSignature -FilePath $_.FullName -Certificate $cert
}

七、常见问题解决

Q:签名后修改脚本为什么还能运行? A:因为执行策略只检查是否有签名,不强制验证内容。需要定期用Get-AuthenticodeSignature检查。

Q:证书过期怎么办? A:需要续订证书并重新签名所有脚本。建议证书有效期设置足够长(如5年)。

Q:如何撤销已签名的脚本? A:将证书加入证书吊销列表(CRL),但PowerShell默认不检查CRL。

八、进阶技巧:时间戳签名

为了防止证书过期后脚本失效,可以添加时间戳:

# 带时间戳的签名
Set-AuthenticodeSignature -FilePath "C:\scripts\Update-Service.ps1" `
    -Certificate $cert `
    -TimestampServer "http://timestamp.digicert.com"

九、其他安全加固措施

除了签名,还可以:

  1. 限制脚本运行权限:
# 创建仅能运行特定脚本的受限端点
New-PSSessionConfigurationFile -Path "C:\PSSessions\LimitedScripts.pssc" `
    -ScriptsToProcess "C:\scripts\AllowedScripts.ps1" `
    -SessionType RestrictedRemoteServer
  1. 记录脚本执行日志:
# 在脚本开头添加日志记录
Start-Transcript -Path "C:\logs\script_$(Get-Date -Format 'yyyyMMdd').log"

十、总结与最佳实践

经过上面的介绍,我们可以得出以下最佳实践:

  1. 生产环境务必使用AllSigned策略
  2. 企业应使用CA证书而非自签名
  3. 重要脚本添加时间戳
  4. 定期检查脚本签名状态
  5. 配合其他安全措施如日志、权限控制

记住,脚本安全不是一劳永逸的,需要持续维护和检查。就像给门上锁一样,签名是最基础但重要的一环。

最后分享一个检查所有脚本签名状态的实用函数:

function Test-ScriptSignatures {
    param(
        [string]$Path = "C:\scripts"
    )
    
    Get-ChildItem -Path $Path -Filter *.ps1 -Recurse | ForEach-Object {
        $sig = Get-AuthenticodeSignature -FilePath $_.FullName
        [PSCustomObject]@{
            Script = $_.Name
            Status = $sig.Status
            Signer = $sig.SignerCertificate.Subject
            Date = $sig.TimeStamperCertificate.NotAfter
            Path = $_.FullName
        }
    } | Format-Table -AutoSize
}

# 使用示例
Test-ScriptSignatures -Path "C:\scripts"

希望这篇文章能帮助你构建更安全的PowerShell脚本环境。安全无小事,从每一个脚本做起!