「在新电脑上运行脚本时,提示『无法加载文件,因为在此系统上禁止运行脚本……』,脚本根本跑不起来」「先加上 -ExecutionPolicy Bypass 试了一下,结果就能跑了,于是所有任务都这么写」「放在共享文件夹里的 .ps1,偏偏只在某个人的电脑上出现签名错误」── PowerShell 的执行策略,是推进公司内部自动化时几乎必然会撞上的一堵墙。而在很多现场,大家在没有真正理解其机制的情况下,不断用「Bypass 蒙混过关」的方式来应对,问题就这样一层层堆积起来。
麻烦的地方在于,执行策略「到底是用来保护什么的功能」很容易被误解。如果把它当作安全功能而设置得过于严格,业务就会停摆;反过来,如果觉得「反正也没用」而把所有地方都设成 Bypass,就连防止无意事故的最后一道安全装置也会被一并拆除。只要理解了其机制,这两种情况其实都可以避免。
本文面向中小企业的信息系统负责人,以及正在用 PowerShell 将公司内部日常工作自动化的读者,围绕执行策略的真实定位与作用域的优先顺序、与 Zone.Identifier(即所谓的 Mark of the Web)之间的关系,以及基于脚本签名的分发运维方式,结合官方文档进行系统梳理。
1. 先说结论
- 执行策略不是安全边界,而是一种安全装置。官方文档明确指出「执行策略并不是限制用户操作的安全系统」,「只要把脚本内容输入到命令行中,就可以轻易绕过」。它的目的是制定基本规则,防止无意执行(即无意间引发的事故)。1
- 执行策略只影响脚本的执行,交互式命令的执行始终不受限制。Windows PowerShell 5.1 在客户端操作系统上的默认值是 Restricted(不允许执行脚本),在 Windows Server 上的默认值是 RemoteSigned。PowerShell 7 的默认值则是 RemoteSigned。21
- 策略拥有 5 个作用域,优先顺序为 MachinePolicy > UserPolicy > Process > CurrentUser > LocalMachine。MachinePolicy/UserPolicy 仅供组策略使用,无法通过
Set-ExecutionPolicy更改,也无法被命令行指定所覆盖。13 - RemoteSigned 只对『来自互联网』的脚本要求签名。来源的判定依据是文件所附带的 Zone.Identifier 备用数据流(Mark of the Web);在确认内容之后,用
Unblock-File将其移除,即可在不改变策略的情况下执行脚本。14 - AllSigned 要求包括本地创建的脚本在内的所有脚本都必须有受信任发布者的签名。签名通过
Set-AuthenticodeSignature添加,会以# SIG #注释块的形式嵌入到文件末尾。15 - 签名时务必附加时间戳(-TimestampServer)。有了时间戳,即使签名证书的有效期已过,脚本依然可以继续有效。由于大多数代码签名证书的有效期只有一年,省略这一步就等于埋下了每年都会引爆的定时炸弹。65
- 自签名证书仅限测试用途。用自签名证书签署的脚本无法在其他计算机上运行。用于组织内分发时,应使用由证书颁发机构(公司内部 CA 或商业 CA)签发的代码签名证书。57
- 如果要在组织层面进行统一管控,可以通过组策略中的「启用脚本执行」设置来集中管理。该设置会优先于 PowerShell 一侧的所有作用域设置。1
2. 执行策略的真实定位 ── 它是「安全装置」,而非「安全边界」
首先确认一下前提。官方文档(about_Execution_Policies)的表述非常直白。执行策略是一种「安全功能(safety feature)」,用于控制 PowerShell 加载配置文件与执行脚本的条件,它「并不是限制用户操作的安全系统」。这是因为,即便是无法执行脚本的用户,只要把脚本内容粘贴到命令行中,同样可以完成相同的操作。文档中明确写明,执行策略的作用是设定基本规则,防止用户无意中违反这些规则。1 入门文档中也反复强调,执行策略「不是安全边界,无法阻止有意执行脚本的用户」。2
理解了这一点定位之后,运维方针也就自然清晰了。「把执行策略设得严格一些就能防住攻击」和「反正能绕过所以毫无意义」这两种想法都是错误的。针对攻击的防护,应该由其他层面(应用程序控制、权限最小化、审计日志)来承担;执行策略的职责是防止事故。8
下面整理一下主要策略之间的差异。1
| 策略 | 脚本执行 | 签名要求 | 定位 |
|---|---|---|---|
| Restricted | 不允许(仅可执行单条命令) | ── | Windows PowerShell 5.1 在客户端操作系统上的默认值2 |
| AllSigned | 允许 | 要求所有脚本与配置文件均签名。对尚未分类的发布者,会在执行前提示确认 | 适合已具备签名运维能力的组织 |
| RemoteSigned | 允许 | 仅对来自互联网的脚本要求签名。本地创建的脚本不需要 | 实务中的标准配置,也是 PowerShell 7 的默认值1 |
| Unrestricted | 允许 | 无(内网区域之外会提示警告) | 非 Windows 平台的默认值(无法更改)1 |
| Bypass | 允许 | 无。既无警告,也无提示 | 供以 PowerShell 为基础、自带独立安全模型的应用程序内嵌使用1 |
容易被忽视的一点是,Restricted 阻止的并不仅仅是业务脚本,还包括配置文件(.ps1)、模块(.psm1)以及格式配置文件(.ps1xml)的加载。1「新电脑上配置文件加载不出来」这一常见问题,追根溯源往往就是执行策略在作怪。
3. 作用域与优先顺序 ── 「明明改过了却没变化」的真相
执行策略并不是单一的一个值,而是可以分别在 5 个作用域中设置,其中优先级最高的那个才是最终生效值。1
| 作用域 | 设置方式 | 保存位置 | 优先顺序 |
|---|---|---|---|
| MachinePolicy | 组策略(计算机配置) | GPO | 1(最高优先级) |
| UserPolicy | 组策略(用户配置) | GPO | 2 |
| Process | -ExecutionPolicy 启动参数/-Scope Process |
环境变量 $Env:PSExecutionPolicyPreference(会话结束后消失) |
3 |
| CurrentUser | Set-ExecutionPolicy -Scope CurrentUser |
用户配置 | 4 |
| LocalMachine | Set-ExecutionPolicy(默认作用域,需要管理员权限) |
所有用户共用的配置 | 5 |
排查「明明改过了却没有生效」这类问题时,正确的做法是查看完整列表,而不是只看最终生效值。
# 不要只看最终生效的策略,务必确认各个作用域分别是什么状态
Get-ExecutionPolicy -List
# 示例:即便 LocalMachine 设置为 AllSigned,CurrentUser 的 RemoteSigned 依然会胜出
# Scope ExecutionPolicy
# ----- ---------------
# MachinePolicy Undefined
# UserPolicy Undefined
# Process Undefined
# CurrentUser RemoteSigned
# LocalMachine AllSigned
# 只想更改自己所在环境时,使用无需管理员权限的 CurrentUser 作用域较为方便
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
由于 CurrentUser 的优先级高于 LocalMachine,上面示例中最终生效的策略是 RemoteSigned。1 Set-ExecutionPolicy 默认写入 LocalMachine 作用域,因此需要管理员权限;但如果指定 CurrentUser 作用域,普通用户也可以自行更改。3
而组织级管理的真正主角是组策略。「启用脚本执行(Turn on Script Execution)」这项策略,会优先于 PowerShell 一侧设置的所有作用域。禁用它相当于 Restricted,启用后则可以从「允许所有脚本(Unrestricted)」「允许本地脚本和签名的远程脚本(RemoteSigned)」「仅允许签名的脚本(AllSigned)」中选择,该设置位于管理模板中的 Windows 组件\Windows PowerShell 下。计算机配置的优先级高于用户配置。1 在由 GPO 管理的环境中,Set-ExecutionPolicy 虽然会保存设置,但并不会真正生效,并且会显示说明冲突原因的提示信息。3
这里有一个重要的推论。如果 GPO 正在管理执行策略,那么写在任务或快捷方式中的 -ExecutionPolicy Bypass 是不起作用的。文档明确指出,Process 作用域的指定虽然可以胜过 LocalMachine/CurrentUser 的配置,但无法胜过组策略。1 反过来说,「写上 Bypass 就能解决问题」这种情况,恰恰只出现在组织根本没有对执行策略进行管理的环境中。
4. Zone.Identifier(Mark of the Web)与 Unblock-File
RemoteSigned 所说的「远程(来自互联网)」,判定依据并不是文件存放的位置,而是文件上的标记。浏览器等程序在下载文件时,会为其附加一个备用数据流,将其标记为「来自互联网的文件」。1 这个数据流就是 Zone.Identifier,其中保存着表示互联网区域的值 3,这就是所谓的 Mark of the Web。4
在 RemoteSigned 环境下,如果尝试执行带有该标记的未签名脚本,就会被「未进行数字签名」这样的错误提示所阻止。应对方法分为两步。
# 1) 首先确认哪些文件被标记为阻止状态(这只是一个只读的安全操作)
Get-Item -Path C:\Tools\*.ps1 -Stream Zone.Identifier -ErrorAction SilentlyContinue
# 2) 只对审查过内容、确认安全的文件解除阻止
# (Unblock-File 只是移除 Zone.Identifier 数据流,执行策略本身不会改变)
Unblock-File -Path C:\Tools\Get-InventoryReport.ps1
Unblock-File 是用来移除 Zone.Identifier 备用数据流的 cmdlet,其效果等同于在资源管理器属性中勾选「解除锁定(Unblock)」按钮。它的关键之处在于,可以在不放松执行策略的前提下,只让已确认过的文件通过。4 官方文档也把「使用前先确认文件本身及其来源,并核实其安全性」列为必要步骤。43
反方向的陷阱同样值得了解。并非所有的获取途径都会附加 Mark of the Web。文档中明确写明,通过 curl.exe、Invoke-WebRequest、Invoke-RestMethod 下载的文件,有时并不会被附加互联网区域的标记。1 也就是说,RemoteSigned 并不能保证「所有下载的危险文件都会被拦下」——而这恰恰正是它作为安全装置、而非安全边界的原因所在。此外还有一条提示:在不区分 UNC 路径与互联网路径的系统配置下,位于共享文件夹中的脚本有时也会被 RemoteSigned 拒绝执行。1 如果遇到「放在共享文件夹里的 .ps1,只在部分电脑上无法运行」的情况,请优先怀疑区域设置以及 Zone.Identifier 是否存在。
顺带一提,可执行文件(.exe)一侧出现类似症状的 SmartScreen 机制,我们在《Windows 提示『Windows 已保护你的电脑』的原因》一文中有专门讲解。
5. 脚本签名的实务操作 ── Set-AuthenticodeSignature 与时间戳
无论是要转向 AllSigned 运维,还是在 RemoteSigned 环境中为分发的脚本签名,都需要一张代码签名证书。获取途径大致可以分为三种。5
| 获取途径 | 信任范围 | 判断建议 |
|---|---|---|
自签名(New-SelfSignedCertificate) |
仅限自己的计算机,其他设备无法执行 | 仅用于测试与验证,不要用于分发57 |
| 由公司内部 CA(证书颁发机构)签发 | 组织内配置为信任该内部 CA 的设备 | AD 域环境下的首选方案,证书的签发与吊销由组织统一管控 |
| 由商业 CA 签发(付费) | 一般的 Windows 设备(公共 CA 已默认受信任) | 需要向组织外部分发脚本时使用 |
官方文档的说法与此一致:「由证书颁发机构签发的证书,在其他计算机上同样受信任」,而「自行创建的证书虽然免费,但仅限于自己的计算机使用,应限定于测试目的」。5 如果目的是公司内部分发,现实的做法是通过 Active Directory 证书服务等公司内部 CA 来签发代码签名证书。
先用测试用的自签名证书来确认整体流程。
# 创建测试用的代码签名证书(自签名 ── 仅在本机受信任)
$params = @{
Subject = 'CN=KomuraSoft Code Signing (Test)'
Type = 'CodeSigningCert'
CertStoreLocation = 'Cert:\CurrentUser\My'
HashAlgorithm = 'sha256'
}
$cert = New-SelfSignedCertificate @params
New-SelfSignedCertificate 是用于创建测试用自签名证书的 cmdlet,指定 -Type CodeSigningCert 即可加入代码签名所需的扩展。默认有效期为一年。7 如果要用自签名证书来测试 AllSigned 环境,需要先将其注册到该设备的受信任根证书颁发机构存储区中。5
正式签名只需要这一行代码。
# 从证书存储中获取代码签名证书并进行签名
# -CodeSigningCert 只是把范围缩小到「可用于代码签名的证书」,
# 因此还需进一步限定为有效期内且拥有私钥的证书,
# 在存在多个匹配项的环境中,应通过 Subject 或 Thumbprint 进一步锁定
$cert = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert |
Where-Object { $_.HasPrivateKey -and $_.NotAfter -gt (Get-Date) -and
$_.Subject -eq 'CN=KomuraSoft Code Signing (Test)' } |
Sort-Object NotAfter -Descending |
Select-Object -First 1 # 因续期等原因导致同一 Subject 存在多个证书时,只取有效期最靠后的那一个
# 时间戳是必须的。即使证书过期,也能让签名继续保持有效
# URL 应替换为证书签发方(公司内部 CA/购买渠道)所提供的时间戳服务地址
Set-AuthenticodeSignature -FilePath .\Invoke-NightlyBatch.ps1 -Certificate $cert `
-HashAlgorithm SHA256 -TimestampServer 'http://timestamp.example.com'
# 可以用 Get-AuthenticodeSignature 确认签名状态(Valid/NotSigned/HashMismatch 等)
Get-AuthenticodeSignature -FilePath .\Invoke-NightlyBatch.ps1
- 签名会以
# SIG #注释块的形式嵌入到文件末尾。如果已存在签名,会被替换掉。也就是说,签名之后只要脚本内容哪怕改动一个字符,签名就会失效——这正是它能够起到篡改检测作用的原因。 - 指定
-TimestampServer之后,即使证书的有效期已过,脚本也不会因此执行失败。签名的有效性取决于「签名证书本身是否仍然有效」,或者「时间戳服务器是否能够证明签名是在证书有效期内完成的」这两者之一;由于大多数代码签名证书的有效期只有一年,时间戳正是长期运维得以维系的关键所在。65 具体使用哪个时间戳服务的 URL,请遵循证书签发方(CA)提供的说明。 - 在 Windows PowerShell 5.1 以及 PowerShell 7.2 之前的版本中,用于签名的脚本必须以 ASCII 或 UTF8NoBOM 格式保存。PowerShell 7.2 及之后的版本支持任意编码的已签名脚本。5 在仍在使用 5.1 的现场,含有中文注释的脚本很容易因为编码问题而导致签名验证失败,这一点需要格外留意。5.1 与 7 之间的差异,以及相应的迁移方式,我们会在其他文章中详细展开。
在 AllSigned 环境下,如果脚本虽然已签名,但其发布者尚未被归类为受信任或已拒绝,执行时会弹出提示:「是否要运行来自这个不受信任的发布者的软件?」选择「始终运行」之后,之后再遇到同一发布者时就不会再次询问。15 在公司内部 CA 运维模式下,如果同时把发布者证书分发到各设备的「受信任的发布者」存储区,就可以从运维流程中彻底消除这个确认提示。
6. 实务判断表 ── 从「用 Bypass 蒙混过关」到规范的签名运维
公司内部脚本的分发与执行管控,可以按照下面这张判断表来考虑。
| 议题 | 可选方案 | 判断参考 |
|---|---|---|
| 环境侧的策略 | 维持 Restricted/RemoteSigned/AllSigned | 若要推进自动化,RemoteSigned 是底线;已具备签名体制则可选 AllSigned1 |
| 设置的分发方式 | 各自执行 Set-ExecutionPolicy/组策略 | 在域环境中组策略是唯一选择,它优先于所有作用域,也能防止随意更改1 |
| 对分发文件的信任 | 不签名 + 放在共享目录中/代码签名 | 不签名的运维方式无法检测篡改,应从按计划定期执行的运维脚本开始逐步转向签名 |
| 证书 | 自签名/公司内部 CA/商业 CA | 公司内部分发使用内部 CA;自签名仅限测试,对外分发使用商业 CA5 |
| 对下载文件的处理 | 放宽策略/审查后使用 Unblock-File | 不要触碰策略本身,只让已确认过的文件通过4 |
| 定时任务的启动方式 | 习惯性使用 -ExecutionPolicy Bypass/完善环境策略 + 签名 |
习惯性使用 Bypass 等于放弃了安全装置,而且在 GPO 管理下原本就不会生效1 |
针对最后一行再补充说明一下。「用 ExecutionPolicy Bypass 蒙混过关」这种运维方式的问题,并不在于它本身打开了一个安全漏洞(因为执行策略原本就不是一道边界)。真正的问题在于以下三点。
- 放弃了安全装置 ── 用来防止「不小心执行了别的脚本」「没有察觉脚本已被篡改却仍然执行」这类事故的最后一道防线,会被永久性地拆除。
- 与管控体系脱节 ── 一旦组织开始通过 GPO 管理执行策略,Bypass 指定就会立即失效,那些原本靠它蒙混过关的任务会同时全部失败。1「之所以能跑起来」的原因,原来只是一个未被纳入管理的漏洞,这本身就是一笔技术债。
- 干扰原因排查 ── 一旦 Bypass 散落在各处,就无法再从环境的最终生效策略去推断行为,导致针对「为什么只有这台设备会失败」这类设备间差异的排查变得异常困难。
Bypass 本身,是为了让基于 PowerShell 构建、并自带独立安全模型进行管控的应用程序使用而设置的选项。1 请明确认识到,它并不适合作为人工编写的运维脚本的常规启动选项来使用。关于如何在任务计划程序中构建安全、可靠的定时执行方案,我们在《任务计划程序的任务无法执行、以 0x1 结束 ── 原因排查与安全运维方案设计》一文中有详细说明。
7. 总结
- 执行策略不是安全边界,而是一种安全装置。针对攻击的防护应交由其他层面负责,执行策略只承担「防止无意事故」这一职责。
- 策略拥有 5 个作用域,优先顺序为 MachinePolicy > UserPolicy > Process > CurrentUser > LocalMachine。排查问题应从
Get-ExecutionPolicy -List开始。 - RemoteSigned 关注的是 Zone.Identifier(Mark of the Web)。对已确认过的文件,用
Unblock-File在不放松策略的前提下放行即可。 - 签名通过
Set-AuthenticodeSignature进行,务必指定-TimestampServer。用于公司内部分发的证书应由内部 CA 签发,自签名仅限测试使用。 - 组织层面的管控应通过组策略中的「启用脚本执行」来统一实现。该设置的优先级高于所有作用域以及
-ExecutionPolicy指定。 - 习惯性地使用
-ExecutionPolicy Bypass等于放弃了安全装置,而且在 GPO 管理下也不会生效。正确的做法是通过完善环境策略并推行签名运维,让「蒙混过关」变得没有必要。
相关文章
- PowerShell 命令基础 ── 先要掌握的操作与安全的使用方法
- Windows 提示『Windows 已保护你的电脑』的原因
- 任务计划程序的任务无法执行、以 0x1 结束 ── 原因排查与安全运维方案设计
- PowerShell 脚本的错误处理与重试(Retry)设计
- Windows PowerShell 5.1 与 PowerShell 7 的差异与迁移
- 从批处理文件迁移到 PowerShell 的判断方法
相关咨询领域
合同会社小村软件(合同会社小村ソフト)承接公司内部 PowerShell 脚本的执行策略与签名运维设计、基于组策略的部署方案评估,以及诸如「脚本只在特定设备上无法运行」这类环境差异的原因排查。我们也可以协助将现有的批处理与脚本资产迁移到更安全的运维方式上。
参考链接
-
Microsoft Learn, about_Execution_Policies。关于执行策略是一种安全功能、而非限制用户的安全系统,各项策略(AllSigned/Bypass/RemoteSigned/Restricted/Unrestricted 等)的定义,5 个作用域及其优先顺序,Process 作用域保存于 $Env:PSExecutionPolicyPreference 且无法胜过 GPO,组策略「Turn on Script Execution」优先于所有作用域,下载文件会被附加备用数据流、而 curl.exe 等工具有时不会附加该标记,以及关于 UNC 路径的注意事项。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19 ↩20 ↩21 ↩22 ↩23 ↩24 ↩25
-
Microsoft Learn, Chapter 1 - Getting started with PowerShell。关于执行策略不是安全边界,Windows 10/11 的默认值为 Restricted、Windows Server 2016/2019/2022 的默认值为 RemoteSigned,以及执行策略只影响脚本、交互式命令始终可以执行。 ↩ ↩2 ↩3
-
Microsoft Learn, Set-ExecutionPolicy。关于默认作用域为 LocalMachine 且需要管理员权限,MachinePolicy/UserPolicy 作用域无法更改,无法覆盖组策略(冲突时会显示提示信息),以及 Unblock-File 在不更改执行策略的情况下解除脚本阻止的示例。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Unblock-File。关于 Unblock-File 会移除表示互联网区域、值为 3 的 Zone.Identifier 备用数据流,其效果与资源管理器属性中的「解除锁定」按钮相同,使用前应确认文件及其来源的安全性,以及用 Get-Item -Stream 检测带有 Zone.Identifier 的文件。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, about_Signing。关于可签名的文件类型,证书颁发机构签发的证书与自签名证书之间的区别(自签名仅限测试用途,无法在其他计算机上运行),签名以 # SIG # 注释块形式附加,PowerShell 7.2 之前的版本需要以 ASCII/UTF8NoBOM 保存,时间戳服务器使签名在证书过期后依然保持有效,以及关于不受信任发布者的提示。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12
-
Microsoft Learn, Set-AuthenticodeSignature。关于添加 Authenticode 签名、替换现有签名、-TimestampServer 参数使脚本在证书过期后依然不会执行失败,以及通过 Cert: 驱动器的 -CodeSigningCert 参数获取带私钥的代码签名证书的示例。 ↩ ↩2 ↩3
-
Microsoft Learn, New-SelfSignedCertificate。关于这是用于创建测试用自签名证书的 cmdlet,可通过 -Type 参数指定代码签名证书,以及默认有效期为一年。 ↩ ↩2 ↩3
-
Microsoft Learn, PowerShell security features。关于执行策略被定位为提升脚本运行环境安全性的多种功能之一,是一种有助于防止恶意脚本执行的安全功能。 ↩
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
PowerShell 的错误处理与重试设计 ── 从 try/catch 失效的陷阱到 exit code、重试的实务定式
本文从实务角度整理 PowerShell 终止错误与非终止错误的区别、try/catch 失效陷阱与 -ErrorAction Stop 的应对定式、用 $LASTEXITCODE 判断成败、exit code 设计,直至指数退避重试的完整流程。
PowerShell 脚本进阶 ── 安全地实现日志排查、归档与报表自动化
本文整理了用 PowerShell 脚本安全推进日志排查、CSV 报表、旧日志归档、留存记录,直至任务计划程序执行的实务步骤。
在 C#(CSharp)中执行 PowerShell 并以对象形式接收结果
本文从实务角度整理如何从 C# 启动 PowerShell,并以 PSObject 而非字符串来接收结果,涵盖 PowerShell SDK、AddCommand、AddParameter、BaseObject、Properties 直至错误处理。
用 Pester 完善 PowerShell 测试 ── 让运维脚本不易损坏的实务方法
本文整理用 Pester v5 测试 PowerShell 脚本的实务步骤,涵盖日期处理、文件操作、删除处理、Mock,一直到 CI 执行,帮助安全地打好测试基础。
PowerShell 实用命令合集 —— 积累日常工作中常用的小工具
本文整理 PowerShell 日常工作中常用的实用命令,说明 Measure-Object、Group-Object、Select-String、Compare-Object、Tee-Object、Start-Transcript 等命令的使用场景和时机。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
常见问题
汇总了咨询这一主题时常见的问题。
- PowerShell 的执行策略是一种安全功能吗?
- 官方文档明确指出:「执行策略并不是限制用户操作的安全系统」。因为只要把脚本内容直接粘贴到命令行中执行,就可以轻易绕过这一限制。执行策略的目的是制定基本规则,防止「无意中执行了不打算执行的脚本」这类事故,它是一种安全装置,绝不能把它当作用来阻止攻击者的安全边界来设计。针对攻击的防护应该在其他层面(应用程序控制、权限最小化等)进行。
- RemoteSigned 和 AllSigned 有什么区别?
- RemoteSigned 只对来自互联网(带有 Mark of the Web 标记)的脚本要求受信任发布者的签名,本地创建的脚本可以不签名直接运行。AllSigned 则要求包括本地创建在内的所有脚本和配置文件都必须签名,对于尚未分类的发布者,会在执行前进行确认提示。如果公司内部能够建立签名运维体系(代码签名证书的分发与签名流程),AllSigned 是现实的选择;如果还没有这样的体制,RemoteSigned 则更为现实。
- 下载的脚本提示『未进行数字签名』而无法执行,是什么原因?
- 这是因为通过浏览器等方式下载的文件会被附加一个名为 Zone.Identifier 的备用数据流,从而被标记为『来自互联网的文件』。如果执行策略为 RemoteSigned,带有该标记的未签名脚本就会被阻止执行。在确认内容确实安全之后,可以使用 Unblock-File cmdlet,或者在资源管理器的属性中勾选「解除锁定」,来移除 Zone.Identifier 标记,这样就能在不改变执行策略的前提下运行该脚本。
- 公司内部分发的 PowerShell 脚本,现实中应该怎样运维?
- 大致有两种选择。第一种是 RemoteSigned + 文件服务器分发,无需搭建签名体系即可开始,但根据分发路径的不同,有时会被附加 Mark of the Web 而导致执行受阻,而且也无法检测脚本是否被篡改。第二种是 AllSigned + 签名运维:使用代码签名证书(现实中以公司内部 CA 签发为主)通过 Set-AuthenticodeSignature 进行签名,并通过组策略统一管理执行策略。这种方式在获得执行策略管控与篡改检测能力的同时,也需要承担证书分发与更新的运维成本。
- 可以在任务计划程序中一直使用 -ExecutionPolicy Bypass 吗?
- 虽然能运行,但并不推荐这样做。首先,在通过组策略管理执行策略的环境中,命令行中指定的 ExecutionPolicy 无法胜过组策略,因此这种指定原本就不会生效。其次,习惯性地使用 Bypass,相当于自己动手拆除了用来防止无意执行的安全装置,久而久之会导致组织制定的策略与现场实际运行状况渐行渐远。如果想把它作为长期运维方式,正确的做法是通过组策略或管理员执行 Set-ExecutionPolicy 来妥善设置环境侧的策略,脚本一侧则通过签名来证明其可信。
- 脚本签名为什么需要时间戳(-TimestampServer)?
- 签名的有效性原则上受限于签名证书的有效期,但如果附加了时间戳,时间戳服务器就会证明「该签名是在证书仍然有效时完成的」,因此即使证书过期之后,脚本依然可以继续使用。由于大多数代码签名证书的有效期都在一年左右,如果不使用时间戳来运维,就会背上一个每年都可能引爆的风险——「某一天,公司内部所有已签名的脚本会突然集体无法运行」。签名时请务必指定 -TimestampServer。
作者简介
本文作者的个人简介页面。
Go Komura
小村软件有限公司 代表
以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。