PowerShell 的執行原則與指令碼簽署 ── 從「用 Bypass 蓋住問題」的做法畢業的實務指南

· · PowerShell, Windows, 執行原則, 程式碼簽章, 資訊安全, 指令碼, 維運改善, 自動化

「在新電腦上執行指令碼時,被告知『這個系統上已停用指令碼執行,因此…』而無法執行」、「總之先加上 -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.exeInvoke-WebRequestInvoke-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

  1. 確認是否已有內部 CA。首先由資訊系統負責人確認公司內部是否已有 AD CS 的企業 CA。若沒有,單純為了程式碼簽章而新建 CA,是一個相當重的決定。請比較一張憑證的購買費用,與建置 CA・備份・公開撤銷清單(CRL)・保護金鑰等運用上的工夫,拿來與購買商業 CA 進行權衡。
  2. 請 CA 準備程式碼簽章用的憑證範本。這是 CA 端的工作。複製預設的「程式碼簽章」範本,在複製出來的範本中設定有效期間・金鑰長度・可申請的安全性群組(限定為簽署負責人),再請對方在「憑證機關」管理主控台中,將它加入發行對象的範本清單。不直接編輯預設範本的原因,是修改會影響整個網域,而且難以復原。
  3. 由簽署負責人申請憑證。在用來簽署的電腦上,以 Get-Certificate Cmdlet 指定範本名稱來提出申請。發行後會直接存入個人存放區(這個 Cmdlet 只能將憑證存放到 My 存放區)。11

    # 向公司內部的企業 CA,指定範本名稱提出申請(Windows 整合驗證)
    # 'CodeSigning' 這部分,請換成 CA 管理員公開為發行對象的範本名稱
    $result = Get-Certificate -Template 'CodeSigning' -Url ldap: `
        -CertStoreLocation Cert:\CurrentUser\My
    $result.Status        # Issued 表示已發行,Pending 表示等待核准中
    
  4. 將信任配布到各電腦。透過群組原則(電腦設定 > 原則 > 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

有 3 項規格需要掌握。65

  • 簽章會以 # SIG # 註解區塊的形式,嵌入在檔案末尾。若已存在簽章,會被取代。也就是說,簽署之後只要指令碼被改動哪怕一個字元,簽章就會失效,這正是竄改偵測發揮作用的方式。
  • 指定 -TimestampServer,即使憑證過期,指令碼也不會因此失敗。簽章的有效性,原則上是「在簽章憑證有效期間內」或是「只要時間戳記伺服器能驗證簽署當下憑證仍有效」,由於多數程式碼簽章憑證的有效期間只有 1 年,時間戳記正是長期運用的命脈。65 URL 不能填虛構的值,必須指定實際存在、支援 RFC 3161 的服務。第一優先是憑證發行者(購買來源的 CA)所提供的 URL,例如 DigiCert 公開了 http://timestamp.digicert.com14 另外,由於 AD CS 的角色服務中並不包含時間戳記回應用的服務10,因此即使是用內部 CA 簽發的憑證進行簽署,時間戳記的取得來源通常也會採用外部服務的架構。這一點在評估導入內部 CA 時容易被忽略,請務必連同網路是否能連到該 URL(代理伺服器・防火牆的允許設定)一併確認。簽署後只要查看 Get-AuthenticodeSignatureTimeStamperCertificate 不是空的,就能確認實際上是否已附加時間戳記。
  • 在 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 指令碼的執行原則・簽署運用設計、群組原則展開方式的評估,以及「僅在特定電腦上指令碼無法執行」這類環境差異的調查。也能協助將既有的批次檔・指令碼資產,替換為安全的運用方式。

參考連結

  1. 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

  2. Microsoft Learn, Chapter 1 - Getting started with PowerShell。關於執行原則不是安全邊界、Windows 10/11 的預設值為 Restricted、Windows Server 2016/2019/2022 的預設值為 RemoteSigned,以及執行原則只影響指令碼、互動式指令永遠可以執行的說明。  2 3

  3. Microsoft Learn, Set-ExecutionPolicy。關於預設範圍為 LocalMachine 需要系統管理員權限、MachinePolicy/UserPolicy 範圍無法變更、無法覆寫群組原則且衝突時會顯示訊息,以及 Unblock-File 在不變更執行原則的情況下解除指令碼封鎖的範例說明。  2 3 4 5

  4. Microsoft Learn, Unblock-File。關於 Unblock-File 會移除代表網際網路區域數值 3 的 Zone.Identifier 替代資料串流、與檔案總管內容中「解除封鎖」按鈕的操作相同、使用前應確認檔案與取得來源的安全性,以及可用 Get-Item -Stream 偵測帶有 Zone.Identifier 的檔案的說明。  2 3 4 5 6

  5. Microsoft Learn, about_Signing。關於簽署對象的檔案類型、憑證機關簽發憑證與自我簽署憑證的差異(自我簽署僅限測試用途,無法在其他電腦上執行)、簽章以 # SIG # 註解區塊的形式加入、PowerShell 7.2 之前需要以 ASCII/UTF8NoBOM 格式儲存、時間戳記伺服器可讓簽章在憑證過期後依然有效,以及不受信任發行者提示的說明。  2 3 4 5 6 7 8 9 10 11 12 13 14 15

  6. Microsoft Learn, Set-AuthenticodeSignature。關於加入 Authenticode 簽章、取代既有簽章、-TimestampServer 參數可讓指令碼在憑證過期後仍不會失敗,以及使用 Cert: 磁碟機的 -CodeSigningCert 參數取得持有私密金鑰之程式碼簽章憑證的範例說明。  2 3

  7. Microsoft Learn, New-SelfSignedCertificate。關於此為建立測試用自我簽署憑證的 Cmdlet、可透過 -Type 參數指定程式碼簽章憑證,以及有效期間預設為 1 年的說明。  2 3

  8. Microsoft Learn, PowerShell security features。關於執行原則被定位為提升指令碼執行環境安全性的多項功能之一、屬於協助防止惡意指令碼執行的安全功能的說明。 

  9. Microsoft Learn, File Streams。關於 NTFS 檔案系統中寫入檔案的資料以串流形式保存、一個檔案可以擁有多個串流、預設資料串流沒有名稱,以及具名串流以「檔名:串流名稱:串流型別」格式指定的說明。 

  10. Microsoft Learn, Active Directory Certificate Services documentation。關於 AD CS 提供用於加密・數位憑證・簽章功能的公開金鑰基礎架構(PKI)、由憑證機關・Web 註冊・線上回應程式(OCSP)・網路裝置註冊服務・憑證註冊 Web 服務等角色服務所構成(不包含時間戳記回應服務)的說明。  2

  11. Microsoft Learn, Get-Certificate。關於此為向註冊伺服器送出憑證要求並安裝回應的 Cmdlet、以 -Template 指定憑證範本的名稱或 OID、未指定認證時會使用 Kerberos 驗證、-CertStoreLocation 只能指定 My 存放區,以及已發行時 Status 為 Issued、待核准時為 Pending 的說明。 

  12. Microsoft Learn, Configure trusted roots and disallowed certificates in Windows。關於透過群組原則(電腦設定 > 原則 > Windows 設定 > 安全性設定 > 公開金鑰原則)將憑證匯入「信任的根憑證授權單位」等存放區以進行發布的步驟說明。 

  13. Microsoft Learn, about_Certificate_Provider。關於 PowerShell 憑證提供者以 Cert: 磁碟機提供對憑證存放區與憑證的存取、CurrentUser 與 LocalMachine 這兩個存放區位置及其下的各存放區、憑證以指紋識別、可用 Get-ChildItem 等指令像檔案系統一樣瀏覽,以及 -CodeSigningCert 參數用於取得擴充金鑰使用方式含有程式碼簽章之憑證的說明。  2 3

  14. DigiCert, RFC 3161 compliant Time Stamp Authority server。關於 DigiCert 公開了符合 RFC 3161 的時間戳記伺服器 http://timestamp.digicert.com,可用於 Authenticode 簽章的時間戳記的說明。 

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

常見問題

整理諮詢這個主題時常見的問題。

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 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽