「在新電腦上執行指令碼時,被告知『這個系統上已停用指令碼執行,因此…』而無法執行」、「總之先加上 -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)的敘述相當直接。執行原則是控制 PowerShell 讀取組態檔、執行指令碼之條件的「安全功能(safety feature)」,並「不是限制使用者操作的安全系統」。因為即使是無法執行指令碼的使用者,只要把指令碼內容貼到命令列中,還是能執行同樣的處理。文件中明確寫著,執行原則的作用是設定基本規則,防止非蓄意違反該規則的情況。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
替代資料串流(Alternate Data Streams)是 NTFS 的功能。在 NTFS 中,一個檔案可以擁有多個資料串流,平常看到的「檔案內容」存放在一個沒有名稱的預設串流中。除此之外,還可以用 檔名:串流名稱 這種寫法附加具名串流,這就是它的機制。9 Zone.Identifier 正是這種具名串流之一,因此指令碼本文一個位元組都沒有改變,卻只有可否執行這一點會發生變化。內容相同的檔案在不同電腦上會出現能動與不能動的差異,原因就在這裡;反過來說,若透過 NTFS 以外的檔案系統(例如 USB 隨身碟的 FAT32)傳遞,整個串流會一併遺失,標記消失後反而能執行的情況也可能發生。
在 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 環境中對發布物進行簽署的運用,都需要程式碼簽章憑證。取得管道大致可以整理成 3 種。5
| 取得管道 | 受信任的範圍 | 判斷 |
|---|---|---|
自我簽署(New-SelfSignedCertificate) |
僅限自己的電腦。無法在其他電腦上執行 | 僅限測試・驗證用途,不用於發布57 |
| 由內部 CA(憑證機關)簽發 | 已設定為信任內部 CA 的組織內電腦 | AD 網域環境的首選。憑證的簽發・撤銷可由組織統一控管 |
| 由商業 CA 簽發(付費) | 一般 Windows 環境(已信任公開 CA) | 需將指令碼發布至公司外部時使用 |
官方文件的整理方式也相同,說明「由憑證機關簽發的憑證,在其他電腦上也會被信任」「自行建立的憑證雖然免費,但僅限自己的電腦使用,應限定於測試目的」。5 若目的是公司內部發布,由 Active Directory 憑證服務(AD CS)等內部 CA 簽發程式碼簽章憑證,是比較現實的做法。
若由內部 CA 簽發,接下來該做什麼
若話說到「內部 CA 比較現實」就結束,會無法邁出下一步,因此這裡具體寫出步驟。AD CS 是提供公開金鑰基礎架構(PKI)的 Windows Server 角色,除了憑證機關(CA)角色服務外,還由 Web 註冊、線上回應程式(OCSP)等角色服務所構成。10
- 確認是否已有內部 CA。首先由資訊系統負責人確認公司內部是否已有 AD CS 的企業 CA。若沒有,單純為了程式碼簽章而新建 CA,是一個相當重的決定。請比較一張憑證的購買費用,與建置 CA・備份・公開撤銷清單(CRL)・保護金鑰等運用上的工夫,拿來與購買商業 CA 進行權衡。
- 請 CA 準備程式碼簽章用的憑證範本。這是 CA 端的工作。複製預設的「程式碼簽章」範本,在複製出來的範本中設定有效期間・金鑰長度・可申請的安全性群組(限定為簽署負責人),再請對方在「憑證機關」管理主控台中,將它加入發行對象的範本清單。不直接編輯預設範本的原因,是修改會影響整個網域,而且難以復原。
-
由簽署負責人申請憑證。在用來簽署的電腦上,以
Get-CertificateCmdlet 指定範本名稱來提出申請。發行後會直接存入個人存放區(這個 Cmdlet 只能將憑證存放到My存放區)。11# 向公司內部的企業 CA,指定範本名稱提出申請(Windows 整合驗證) # 'CodeSigning' 這部分,請換成 CA 管理員公開為發行對象的範本名稱 $result = Get-Certificate -Template 'CodeSigning' -Url ldap: ` -CertStoreLocation Cert:\CurrentUser\My $result.Status # Issued 表示已發行,Pending 表示等待核准中 - 將信任配布到各電腦。透過群組原則(電腦設定 > 原則 > Windows 設定 > 安全性設定 > 公開金鑰原則),把內部 CA 的根憑證發布到「信任的根憑證授權單位」存放區,把用於簽署的發行者憑證發布到「信任的發行者」存放區。官方指南中有說明在公開金鑰原則底下,對各個存放區按右鍵匯入憑證的步驟。12 做到這一步,就能從運用中消除 AllSigned 環境下會出現的「是否要執行來自此不受信任發行者的軟體?」提示。15
CA 端的工作(步驟 2)通常無法單靠資訊系統部門獨力完成,因此先確定好步驟 1 的確認結果與步驟 4 的配布方針,再去找 CA 管理員商量,溝通會比較順利。
未加入網域的環境(工作群組)該怎麼做
在本文設定的目標讀者──中小企業之中,也有一開始就沒有 AD 網域、或只有部分電腦未加入網域的情況。在無法使用 GPO 也無法使用內部 CA 的前提下,可以依照以下順序,逐步累積出現實可行的做法。
- 逐台電腦設定執行原則,並確認是否一致。將
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser(若要對所有使用者生效,則以系統管理員權限設定 LocalMachine)寫入電腦初始配置(kitting)手冊。3 由於沒有 GPO 強制執行,每台電腦仍有可能被隨意變更,因此要搭配在盤點時機確認Get-ExecutionPolicy -List結果的運用方式。 - 將指令碼的正本統一於一處,並限制寫入權限。將共用資料夾中的某一處指定為正本存放位置,寫入權限只開放給管理員。由於執行原則只能防止「不小心」的情況,防止竄改是 NTFS 存取權限的工作。
- 若還要求做到簽署,就用費用來比較是要發放自我簽署憑證,還是購買商業 CA。官方文件指出「用自我簽署憑證簽章的指令碼,無法在其他電腦上執行」「不適合用於想要共用的指令碼」。5 不過這是對預設狀態的說明,意思是因為其他電腦不信任那張憑證所以才無法執行。反過來說,只要把信任發布出去,就能執行。下一節會分開整理說明。
- 在操作手冊中寫明 Mark of the Web 的處理方式。透過郵件或外部共用管道收到的
.ps1,有時會帶有標記,因此要把「先審閱內容,再執行Unblock-File」明文寫成操作步驟(第 4 章)。4
若要在工作群組中使用自我簽署憑證
若停在「自我簽署只能在簽署當下的電腦上使用」這句話,就等於強迫只有 10 台、20 台電腦的公司也得購買商業 CA。實際上,只要把憑證的公開部分發布到各台電腦上,AllSigned 就能通過。差別只在於不是用 GPO,而是改用手動作業・安裝程式・MDM(如 Intune)等方式來發布。
要發布的僅限公開部分,私密金鑰不會離開用來簽署的那台電腦。
# 【在簽署用電腦上執行 1 次】只匯出公開部分(-Cert 不包含私密金鑰)
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert |
Where-Object Subject -eq 'CN=KomuraSoft Code Signing (Test)'
Export-Certificate -Cert $cert -FilePath .\KomuraSoftCodeSigning.cer
# 【在每台電腦上執行 1 次・需要系統管理員權限】同時放入根與發行者兩個存放區。
# 若不放入根存放區,簽章驗證本身就無法通過(因為是自我簽署,所以自己就是根);
# 若不放入發行者存放區,AllSigned 的提示會持續出現
Import-Certificate -FilePath .\KomuraSoftCodeSigning.cer `
-CertStoreLocation Cert:\LocalMachine\Root
Import-Certificate -FilePath .\KomuraSoftCodeSigning.cer `
-CertStoreLocation Cert:\LocalMachine\TrustedPublisher
在此基礎上,請先了解自己要承擔的成本。與商業 CA 的差異就展現在這裡。
| 議題 | 發放自我簽署憑證 | 購買商業 CA |
|---|---|---|
| 初期費用 | 0 元。但需要對所有電腦進行發布作業 | 憑證購買費用(年費) |
| 私密金鑰的保護 | 簽署用電腦本身就是整條防線。若這把金鑰外洩,就能對所有已發布信任的電腦簽署惡意程式碼。需事先決定金鑰的存放位置與禁止攜出的規則 | 同樣重要,但有 HSM 或雲端簽署服務可以選擇 |
| 撤銷 | 沒有手段。一旦外洩,只能逐台從存放區中刪除 | 可用 CRL/OCSP 撤銷 |
| 過期 | 每次更新都要重新發布到所有電腦(若已附加時間戳記,既有的簽章即使過期後仍然有效5) | 電腦端無需額外作業 |
| 新增電腦 | 必須納入初始配置流程中 | 不需要 |
| 加入「信任的根」的影響 | 電腦會信任這一張憑證。由於不是 CA,無法簽發下層憑證,但用這把金鑰簽署的任何東西都會通過 | 沒有變化 |
若電腦數量不多、初始配置流程有受到管理、又能保護好簽署用電腦,那麼發放自我簽署憑證的架構就已足夠。反之,若電腦數量持續增加、初始配置依賴特定人員、或簽署用電腦是任何人都能接觸到的 ── 只要符合其中任何一項,把撤銷與更新的麻煩外包給商業 CA 的判斷,反而會更划算。
憑證存放區與 Cert: 磁碟機
在進入簽署程式碼之前,先簡單說明一下 Cert:。Cert: 是 PowerShell 憑證提供者所提供的虛擬磁碟機,可以像檔案系統一樣用路徑瀏覽憑證存放區。13 其結構是 Cert:\<存放區位置>\<存放區名稱> 這種兩層架構,存放區位置分為 CurrentUser(供目前登入使用者使用)與 LocalMachine(供整台電腦使用)兩種,存放區名稱則有 My(個人)、Root(信任的根憑證授權單位)、TrustedPublisher(信任的發行者)等。每一張憑證都以指紋(Thumbprint)識別,可以用 Get-ChildItem 列出清單。13
也就是說,Cert:\CurrentUser\My 是「目前使用者的個人存放區」,接下來會出現的 Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert,意思就是「把該處所有用於程式碼簽章的憑證都列出來」。-CodeSigningCert 是憑證提供者專屬的參數,用來篩選出擴充金鑰使用方式(EKU)中含有程式碼簽章的憑證。13
首先用測試用的自我簽署憑證確認整體流程。
# 建立測試用的程式碼簽章憑證(自我簽署 ── 只有這台電腦會信任)
$params = @{
Subject = 'CN=KomuraSoft Code Signing (Test)'
Type = 'CodeSigningCert'
CertStoreLocation = 'Cert:\CurrentUser\My'
HashAlgorithm = 'sha256'
}
$cert = New-SelfSignedCertificate @params
New-SelfSignedCertificate 是用來建立測試用自我簽署憑證的 Cmdlet,指定 -Type CodeSigningCert 就會加入程式碼簽章用的擴充項目。有效期間預設為 1 年。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 請指定支援 RFC 3161 的時間戳記服務。優先使用憑證發行者(購買來源)提供的指引
# 範例:DigiCert 公開的時間戳記伺服器 http://timestamp.digicert.com
Set-AuthenticodeSignature -FilePath .\Invoke-NightlyBatch.ps1 -Certificate $cert `
-HashAlgorithm SHA256 -TimestampServer 'http://timestamp.digicert.com'
# 簽章狀態可以用 Get-AuthenticodeSignature 確認(Valid/NotSigned/HashMismatch 等)
# 若 TimeStamperCertificate 不是空的,就代表已附加時間戳記
Get-AuthenticodeSignature -FilePath .\Invoke-NightlyBatch.ps1 |
Format-List -Property Status, SignerCertificate, TimeStamperCertificate
- 簽章會以
# SIG #註解區塊的形式,嵌入在檔案末尾。若已存在簽章,會被取代。也就是說,簽署之後只要指令碼被改動哪怕一個字元,簽章就會失效,這正是竄改偵測發揮作用的方式。 - 指定
-TimestampServer,即使憑證過期,指令碼也不會因此失敗。簽章的有效性,原則上是「在簽章憑證有效期間內」或是「只要時間戳記伺服器能驗證簽署當下憑證仍有效」,由於多數程式碼簽章憑證的有效期間只有 1 年,時間戳記正是長期運用的命脈。65 URL 不能填虛構的值,必須指定實際存在、支援 RFC 3161 的服務。第一優先是憑證發行者(購買來源的 CA)所提供的 URL,例如 DigiCert 公開了http://timestamp.digicert.com。14 另外,由於 AD CS 的角色服務中並不包含時間戳記回應用的服務10,因此即使是用內部 CA 簽發的憑證進行簽署,時間戳記的取得來源通常也會採用外部服務的架構。這一點在評估導入內部 CA 時容易被忽略,請務必連同網路是否能連到該 URL(代理伺服器・防火牆的允許設定)一併確認。簽署後只要查看Get-AuthenticodeSignature的TimeStamperCertificate不是空的,就能確認實際上是否已附加時間戳記。 - 在 Windows PowerShell 5.1 或 PowerShell 7.2 之前的版本中簽署的指令碼,需要以 ASCII 或 UTF8NoBOM 格式儲存。PowerShell 7.2 以後支援以任意編碼儲存已簽署的指令碼。5 在仍保留 5.1 的現場,含有日文註解的指令碼容易因編碼問題造成簽章驗證失敗,這點需要特別留意。5.1 與 7 的差異全貌,請參閱「Windows PowerShell 5.1 與 PowerShell 7 的差異與遷移」。
在 AllSigned 環境中,即使已經簽署,只要發行者尚未被分類為信任或拒絕,執行時就會出現「是否要執行來自此不受信任發行者的軟體?」的提示。選擇「一律執行」後,之後對該發行者就不會再詢問。15 若在內部 CA 運用中,一併把發行者憑證發布到各電腦的「信任的發行者」存放區,就能從運用中消除這個確認步驟。
6. 實務的標準做法(判斷表) ── 從「用 Bypass 蓋住問題」到簽署運用
公司內部指令碼的發布・執行控管,標準做法是依照下面這張判斷表來思考。
| 議題 | 選項 | 判斷依據 |
|---|---|---|
| 環境端的原則 | 維持 Restricted / RemoteSigned / AllSigned | 若要推動自動化,RemoteSigned 是底線。已有簽署體制則用 AllSigned1 |
| 設定的發布 | 各自使用 Set-ExecutionPolicy / 群組原則 | 網域環境下應優先選用 GPO。優先順序高於所有範圍,也能封鎖擅自變更1 |
| 發布物的信任 | 未簽署 + 放在共用資料夾 / 程式碼簽章 | 未簽署的運用方式無法偵測竄改。從定期執行的運用指令碼開始優先簽署 |
| 憑證 | 自我簽署 / 內部 CA / 商業 CA | 公司內部發布用內部 CA。自我簽署僅限測試,對外發布用商業 CA5 |
| 下載物的處理 | 放寬原則 / 審閱後執行 Unblock-File | 不動原則,只放行已確認過的檔案4 |
| 定期工作的啟動 | 長期慣用 -ExecutionPolicy Bypass / 整備環境端原則 + 簽署 |
長期慣用 Bypass 等於放棄安全裝置。在 GPO 管理下本來就不會生效1 |
補充說明最後一行。「用 ExecutionPolicy Bypass 蓋住問題」這種運用方式的問題,並不是打開了一個安全漏洞本身(畢竟執行原則原本就不是邊界)。問題在於以下 3 點。
- 放棄安全裝置 ── 這會永久拆除防止不小心執行到別的指令碼、或未察覺指令碼已遭竄改而誤執行等事故的最後一道防線。
- 與統一控管脫節 ── 一旦開始用 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 的錯誤處理與重新執行設計 ── 從 try/catch 失效的陷阱到 exit code、重試的實務定石
- Windows PowerShell 5.1 與 PowerShell 7 的差異 ── 公司內部腳本遷移實務指南
- 那個批次檔,該遷移到 PowerShell 嗎? ── cmd/bat 資產盤點與遷移判斷
相關諮詢領域
合同會社小村軟體承接公司內部 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 ↩26
-
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 ↩5
-
Microsoft Learn, Unblock-File。關於 Unblock-File 會移除代表網際網路區域數值 3 的 Zone.Identifier 替代資料串流、與檔案總管內容中「解除封鎖」按鈕的操作相同、使用前應確認檔案與取得來源的安全性,以及可用 Get-Item -Stream 偵測帶有 Zone.Identifier 的檔案的說明。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, about_Signing。關於簽署對象的檔案類型、憑證機關簽發憑證與自我簽署憑證的差異(自我簽署僅限測試用途,無法在其他電腦上執行)、簽章以 # SIG # 註解區塊的形式加入、PowerShell 7.2 之前需要以 ASCII/UTF8NoBOM 格式儲存、時間戳記伺服器可讓簽章在憑證過期後依然有效,以及不受信任發行者提示的說明。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15
-
Microsoft Learn, Set-AuthenticodeSignature。關於加入 Authenticode 簽章、取代既有簽章、-TimestampServer 參數可讓指令碼在憑證過期後仍不會失敗,以及使用 Cert: 磁碟機的 -CodeSigningCert 參數取得持有私密金鑰之程式碼簽章憑證的範例說明。 ↩ ↩2 ↩3
-
Microsoft Learn, New-SelfSignedCertificate。關於此為建立測試用自我簽署憑證的 Cmdlet、可透過 -Type 參數指定程式碼簽章憑證,以及有效期間預設為 1 年的說明。 ↩ ↩2 ↩3
-
Microsoft Learn, PowerShell security features。關於執行原則被定位為提升指令碼執行環境安全性的多項功能之一、屬於協助防止惡意指令碼執行的安全功能的說明。 ↩
-
Microsoft Learn, File Streams。關於 NTFS 檔案系統中寫入檔案的資料以串流形式保存、一個檔案可以擁有多個串流、預設資料串流沒有名稱,以及具名串流以「檔名:串流名稱:串流型別」格式指定的說明。 ↩
-
Microsoft Learn, Active Directory Certificate Services documentation。關於 AD CS 提供用於加密・數位憑證・簽章功能的公開金鑰基礎架構(PKI)、由憑證機關・Web 註冊・線上回應程式(OCSP)・網路裝置註冊服務・憑證註冊 Web 服務等角色服務所構成(不包含時間戳記回應服務)的說明。 ↩ ↩2
-
Microsoft Learn, Get-Certificate。關於此為向註冊伺服器送出憑證要求並安裝回應的 Cmdlet、以 -Template 指定憑證範本的名稱或 OID、未指定認證時會使用 Kerberos 驗證、-CertStoreLocation 只能指定 My 存放區,以及已發行時 Status 為 Issued、待核准時為 Pending 的說明。 ↩
-
Microsoft Learn, Configure trusted roots and disallowed certificates in Windows。關於透過群組原則(電腦設定 > 原則 > Windows 設定 > 安全性設定 > 公開金鑰原則)將憑證匯入「信任的根憑證授權單位」等存放區以進行發布的步驟說明。 ↩
-
Microsoft Learn, about_Certificate_Provider。關於 PowerShell 憑證提供者以
Cert:磁碟機提供對憑證存放區與憑證的存取、CurrentUser 與 LocalMachine 這兩個存放區位置及其下的各存放區、憑證以指紋識別、可用 Get-ChildItem 等指令像檔案系統一樣瀏覽,以及 -CodeSigningCert 參數用於取得擴充金鑰使用方式含有程式碼簽章之憑證的說明。 ↩ ↩2 ↩3 -
DigiCert, RFC 3161 compliant Time Stamp Authority server。關於 DigiCert 公開了符合 RFC 3161 的時間戳記伺服器
http://timestamp.digicert.com,可用於 Authenticode 簽章的時間戳記的說明。 ↩
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
PowerShell 中安全處理認證資訊 ── 把明文密碼逐出腳本
本文整理將 PowerShell 腳本中的明文密碼遷移至安全保存方式的做法,說明 SecureString 的實際樣貌與限制、Export-Clixml 透過 DPAPI 保存的機制,以及 SecretManagement/SecretStore 的適用場合。
用 winget + PowerShell 自動化 PC 配置 ── 讓操作手冊變得可執行
本文整理讓新進員工 PC 的環境建置可重現的方法,涵蓋以 winget 進行應用程式導入與 export/import、WinGet Configuration 的宣告式組態、以 PowerShell 補充的設定,以及無人執行時的注意事項。
PowerShell 的安全性強化 ── 日誌・AMSI・語言模式・JEA
本文整理在不禁止 PowerShell 的前提下安全使用它的實務做法。內容涵蓋啟用指令碼區塊記錄與逐字稿記錄、停用 AMSI 與舊版本、以語言模式進行限制,以及透過 JEA 委派權限。
PowerShell與REST API串接 ── Invoke-RestMethod的實務
本文整理從PowerShell呼叫公司內部API或SaaS REST API的實務作法。內容涵蓋認證標頭的傳遞方式、日文JSON亂碼的因應對策、4xx/5xx的錯誤處理、429的重試、分頁,以及Proxy與TLS的陷阱。
PowerShell 腳本太慢時該檢查的地方 ── 陣列・管線・比對的訣竅
整理 PowerShell 腳本變慢的常見原因。從實務角度解說陣列 += 造成 O(n^2) 的原因、管線與 foreach 的差異、比對的雜湊表化、檔案 I/O 的改善,以及正確的測量方式。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
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。