PowerShell 指令碼執行策略

sparkdev發表於2017-09-01

為防止惡意指令碼的執行,PowerShell 中設計了一個叫做執行策略(Execution Policy)的東西(我更傾向於把它叫做指令碼執行策略)。我們可以在不同的應用場景中設定不同的策略來防止惡意指令碼的執行。本文主要是解釋這些執行策略,因為筆者在學習的時候發現它們並不是那麼清晰易懂。
PowerShell 提供了 Restricted、AllSigned、RemoteSigned、Unrestricted、Bypass、Undefined 六種型別的執行策略,接下來我們一一介紹。

Restricted

單詞 Restricted 的意思是 "受限制的",所以這種執行策略主要是限制指令碼的執行。說簡單點就是:可以執行單個的命令,但是不能執行指令碼。當執行策略為 Restricted 時執行指令碼會收到下面的錯誤:

遺憾的是在 Windows 8, Windows Server 2012, and Windows 8.1 的系統中,Restricted 被設定為預設的執行策略。所以在這些環境中要執行 PowerShell 指令碼的第一件事就是調整指令碼的執行策略。比如設定成不會限制指令碼執行的 Bypass:

Set-ExecutionPolicy -ExecutionPolicy Bypass

注意,設定指令碼的執行策略需要管理員許可權,因此你需要以管理員許可權啟動 PowerShell ,然後執行上面的命令。

AllSigned

Signed 在這裡指有數字簽名的指令碼,也就是說 AllSigned 執行策略允許執行所有具有數字簽名的指令碼。當然我們也可以換個角度說:只能執行具有數字簽名的指令碼。據我所知我們能見到的絕大多數的 PowerShell 指令碼是沒有數字簽名的。如果執行沒有簽名的指令碼,會提示下面的錯誤:

接下來我們要搞清楚如何給一個 PowerShell 指令碼簽名(打上數字簽名)?並且在 AllSigned 執行策略下執行這個指令碼。
給 PowerShell 指令碼簽名需要使用 Set-AuthenticodeSignature 命令,當然前提是你得擁有一個合法的數字證書。比如筆者使用的數字證書檔名為 test.pfx。先用數字證書檔案構建一個證書物件:

$cert = Get-PfxCertificate -FilePath "test.pfx"

一般情況下都會為證書設定密碼,所以這一步需要輸入密碼進行驗證。然後為 demo.ps1 指令碼檔案簽名:

Set-AuthenticodeSignature -FilePath "demo.ps1" `
    -Certificate $cert -IncludeChain "All" `
    -TimeStampServer "http://timestamp.verisign.com/scripts/timstamp.dll"

上圖中最後一行中間列顯示檔案的前面狀態為 Valid,表示已經簽名成功。讓我們再來看看 demo.ps1 指令碼檔案都發生了什麼變化!開啟 demo.ps1 檔案的屬性介面:

比原來多了一個 "Digital Signatures" 標籤頁,這裡便是數字簽名的資訊。接著開啟 demo.ps1 看看:

除了第一行指令碼命令外,被新增了很多行的註釋,這都是數字簽名乾的。
接下來我們在 AllSigned 執行策略下執行一下指令碼 demo.ps1:

這次的提示是說你的系統還沒有信任這個證書的持有者(筆者把證書持有者的資訊打碼了:)),要不要執行這個指令碼?此時就需要使用者做出判斷了,如果選擇  "Always run",不僅會執行該指令碼,還會把該證書新增到信任列表中:

上圖便是選擇 "Always run" 之後證書管理器中的資訊,筆者用來簽名指令碼的數字證書已經被新增到信任列表中了(就是右邊紅框中的證書資訊)。一旦信任了這個數字證書,以後再執行由這個數字證書籤名的指令碼就不會再有提示了!
注意:數字證書的使用只是增添了一道安全機制,絕不意味著有數字簽名的指令碼就一定是安全可靠的。國內某證書頒發機構就因為沒有底線亂髮證書而被谷歌、火狐等產品移出了信任列表。既然有這樣的證書頒發機構存在,就會有人藉機購買數字證書並給惡意的指令碼簽名,所以不能僅憑是否有數字簽名來區分指令碼是否可信。

RemoteSigned

從 Windows Server 2012 R2 開始,PowerShell 中預設的執行策略改成了 RemoteSigned。這個執行策略的意思是:當執行從網路上下載的指令碼時,需要指令碼具有數字簽名,否則不會執行這個指令碼。如果是在本地建立的指令碼則可以直接執行,不要求指令碼具有數字簽名。
那麼問題來了,PowerShell 是如何知道指令碼是本地建立的還是從網路上下載下來的?
原來,在 Windows 提供的 API 中,有個列舉可以標識檔案的來源。

public enum SecurityZone
{
    NoZone = -1,
    MyComputer = 0,
    Intranet = 1,
    Trusted = 2,
    Internet = 3,
    Untrusted = 4,
}

當瀏覽器或者 outlook 這樣的工具從網路上下載檔案時,應該透過 Windows 系統提供的 API 把這個列舉的值進行更新。讓我們從網路上下載一個 powershell 文(其實可以是任何檔案),檔名為 0Start.ps1,右鍵開啟屬性介面:

上圖紅框中的內容便是對相關屬性的描述。我們還可以透過命令 notepad "0Start.sp1:Zone.Identifier" 在記事本中以文字的方式顯示其屬性:

這裡顯示的已經很清楚了,ZoneId=3 表明這個檔案是從網路上下載的。

Unrestricted

這是一種比較寬容的策略,允許執行未簽名的指令碼。對於從網路上下載的指令碼,在執行前會進行安全性提示:

但這僅僅是個提示,還是允許指令碼執行的。

Bypass

Bypass 執行策略對指令碼的執行不設任何的限制,任何指令碼都可以執行,並且不會有安全性提示。

Undefined

Undefined 表示沒有設定指令碼策略。當然此時會發生繼承或應用預設的指令碼策略。

Execution Policy Scope

Scope  指執行策略的應用範圍。原來我們可以給不同的應用範圍設定執行策略。比如程式、當前使用者和本機。
Get-ExecutionPolicy 和 Set-ExecutionPolicy 命令預設操作的都是本機的指令碼執行策略。如果要獲得當前使用者的執行策略可以使用 -Scope 選項:

Get-ExecutionPolicy -Scope CurrentUser

同樣如果僅修改當前使用者的執行策略可以在 Set-ExecutionPolicy 命令中使用 scope 引數。

Set-ExecutionPolicy -ExecutionPolicy <PolicyName> -Scope CurrentUser

總結

很明顯,PowerShell 精心設計了指令碼的執行策略。遺憾的是在 Windows 8, Windows Server 2012, and Windows 8.1 的系統中,Restricted 被設定為預設的執行策略。這讓學習 PowerShell 的新手們多少有些不知所措,因為所有人都會在第一次執行指令碼時遭遇不能執行的問題。還好 MS 在新的系統中把預設的執行策略改成了 RemoteSigned,至少對新手來說更友好了。

相關文章