共用方式為


Windows PowerShell確保殼層的安全性

Don Jones

當 Windows PowerShell 小組齊聚一堂來建立新的殼層時,會議室中不斷提到「指令碼」一詞,使整個總部 (Redmond) 大概都聽得見充滿挫折的呻吟聲。畢竟,Microsoft 之前對於系統管理指令碼所做的努力 (我是指 VBScript) 不能說是完全沒有問題。而

過度寬鬆的執行模型,再加上多數使用者喜歡以完整的系統管理員權限登入的習慣,因此開啟了災難之門。

「不用說,」那場 Windows PowerShell™ 的首次會議的與會者一定祈禱「不要再經歷一次相同的折磨。」他們的確沒有。Microsoft 已經提升它的安全性評價,最主要是因為 Microsoft 開始提前設想安全性,而非在稍後的產品開發週期中才考量。這種哲學在 Windows PowerShell 中表現的淋漓盡致。

我的指令碼為什麼不執行?

在全新的電腦上安裝 Windows PowerShell 並按兩下 .ps1 檔案:出現記事本,而非 Windows PowerShell。這是因為 .ps1 副檔名 (用於 Windows PowerShell 指令碼的副檔名) 與殼層本身沒有關聯性。換句話說,您無法以按兩下指令碼的方式來執行它。執行指令碼唯一的方法是開啟 Windows PowerShell,輸入指令碼名稱,然後按 Enter。

實際上,僅僅輸入指令碼名稱也不足夠。您可以在 [圖 1] 中看到,檔案 Demo1.ps1 存在於目前的資料夾中,但只是輸入 demo1 並按下 Enter 會產生錯誤:「'demo1' 詞彙無法辨識是否為 Cmdlet、函數、可執行程式或指令檔。」為什麼會這樣?畢竟,這個檔案的確存在。

圖 1 為了防止命令劫持 (Command Hijacking),Windows PowerShell 需要指令碼路徑

圖 1** 為了防止命令劫持 (Command Hijacking),Windows PowerShell 需要指令碼路徑 **(按影像可放大)

這是 Windows PowerShell 安全性模型的另一個層面。惡意使用者常在其他殼層中使用的攻擊手法,就是建立與內建命令相同檔名的指令碼。舉例來說,如果您要使用者執行您的指令碼,您可能將其命名為 Dir.ps1,然後放入資料夾中。如果您說服使用者輸入 Dir 並按下 Enter,您的指令碼可能會執行並非使用者期待的 Dir 命令。這種技巧稱為命令劫持 (Command Hijacking)。在 Windows PowerShell 中,您必須總是提供這類指令碼的路徑,以便有效防護 Windows PowerShell 不受命令劫持 (Command Hijacking) 的攻擊。

執行 demo1 沒有作用,因為並未提供路徑,但是 ./demo1 就會執行。這是因為我已經指定了路徑,即目前的資料夾。這個命令行不太可能與內建命令搞混,因為如果您指的是內建命令,就不會輸入這樣的路徑。因此,要求路徑能讓 Windows PowerShell 防止命令劫持 (Command Hijacking),也能避免按下 Enter 之定義的混亂。

指令碼執行原則

安裝了嶄新的 Windows PowerShell,也使用正確的語法之後,現在您嘗試執行指令碼。但出乎意料之外,您又遇到另一個錯誤訊息,告知您不允許 Windows PowerShell 執行指令碼。您可能會驚嘆:為什麼?歡迎來到殼層的執行原則的世界。

您可以在殼層中執行 Get-ExecutionPolicy,即可檢視目前的執行原則。在預設情況下,執行原則會設定為 Restricted,簡單來說,就是無論如何都不會執行指令碼。根據預設,Windows PowerShell 只能以互動方式使用,而非用以執行指令碼。您可以使用 Set-ExecutionPolicy 指令程式 (Cmdlet),從以下四種可用的執行原則設定中選擇其中一種:

  • Restricted,這是預設設定,將不允許執行任何指令碼。
  • AllSigned 只會執行受信任的指令碼 (稍後再詳加說明)。
  • RemoteSigned 會執行本機指令碼而不會要求它們是受信任的指令碼;不過,從網際網路下載的指令碼則必須是受信任的指令碼,才可執行。
  • Unrestricted 允許執行所有指令碼,即便是未受信任的指令碼。

坦白說,所有實際執行的電腦至少都應設為 AllSigned。我發現 RemoteSigned 適合用於開發和測試環境,但一般使用者就不必了。至於 Unrestricted,我認為根本沒有必要,而且我認為未來的 Windows PowerShell 版本應該刪除這個過度寬鬆的設定。

信不信任的問題

您的電腦已經預先設定為信任特定根憑證授權單位 (CA),即核發數位憑證的伺服器。[圖 2] 顯示 [網際網路選項] 控制台應用程式,其中列出受信任的根 CA。在 Windows Vista® 中,這份清單很短,而且只包含一些主要的商業根 CA。相反地,Windows® XP 中的預設清單就很龐大,而且包含許多我從未聽過的根 CA。如果根 CA 列在此清單中,基本上就表示您信任操作此根 CA 的公司會先確實檢查某人的身分,然後才核發數位憑證。

當您取得第三級 (Class III) 數位憑證時 (通稱為程式碼簽署憑證),CA (可能是商業 CA 或組織內部的私人 CA) 必須驗證您的身分。數位憑證就像是電子護照或識別卡。舉例來說,在核發表示我是 Don Jones 的 ID 之前,CA 必須採取一些步驟來確認我真的是 Don Jones。當您使用您的憑證來數位簽署 Windows PowerShell 指令碼時 (您可以使用 Set-AuthenticodeSignature 指令程式來執行這項作業),您是將自己的名字簽署到指令碼中。當然,如果您能夠取得包含別人名字的假憑證,您就可以把那個人的名字簽署到指令碼中,正因如此,[圖 2] 的清單必須只顯示值得信任的 CA。

圖 2 Windows Vista 中預設為受信任的根 CA

圖 2** Windows Vista 中預設為受信任的根 CA **(按影像可放大)

當 Windows PowerShell 查看指令碼是否受信任時 (在 AllSigned 和 RemoteSigned 執行原則設定底下這麼做),它會提出三個問題:

  • 這個指令碼是否經過簽署?若否,則表示指令碼不受信任。
  • 簽章是否完好?換句話說,指令碼在簽署之後是否遭到變更?如果簽章不完整,則表示指令碼不受信任。
  • 簽章是否使用由受信任的根 CA 所核發的數位憑證來建立?若否,則表示指令碼不受信任。

本月指令程式:Set-AuthenticodeSignature

Set-AuthenticodeSignature 是簽署 Windows PowerShell 指令碼的關鍵,可讓您使用殼層最安全的執行原則 AllSigned。若要使用這個指令程式,首先要使用另一個指令程式 (Get-ChildItem) 來擷取已安裝的程式碼簽署憑證:

$cert = Get-ChildItem –Path cert:\CurrentUser\my –codeSigningCert

您必須將該檔案路徑替換成指向已安裝憑證的檔案路徑,任何已安裝的憑證都可以透過 cert:drive 的方式來存取。一旦載入憑證之後,執行下列即可簽署指令碼:

Set-AuthenticodeSignature MyScript.ps1

–cert $cert

當然還要提供 .ps1 指令檔的完整路徑。Shell 會新增簽章區塊到檔案中。

這如何提升安全性?沒錯,駭客的確可能會撰寫惡意的指令碼,簽署此指令碼,然後說服您的環境中的使用者執行這個指令碼。由於此指令碼已經過簽署,因此將會執行。但是因為這個指令碼已經過簽署,因此您可以使用此簽章來尋找作者的身分,並採取適當的行動 (例如,訴諸執法機構)。不過,如果建立惡意指令碼的人還簽署自己的真實名字,那也真夠笨的了!

當然,要是惡意使用者能夠取得其他身分的憑證,例如從身分檢查作業不完善的 CA 取得憑證,您就無法使用簽章來找出真正的罪犯。這也是為什麼您信任的根 CA 應該具備讓您滿意的身分驗證程序。

使用 AllSigned 執行原則設定時,您甚至需要在執行自己產生的指令碼之前,先逐一數位簽署這些指令碼。Set-AuthenticodeSignature 指令程式很容易使用,但這些作業還是有點繁瑣。這時協力廠商軟體就派上用場了。建議您選擇指令碼編輯器或視覺式開發環境,來使用您指定的憑證自動簽署指令碼。如此一來,簽章的使用情況就一目了然,而且也不費力。如需支援的編輯器與開發環境的建議,請造訪線上指令碼論壇 (例如我的網站:ScriptingAnswers.com),並張貼訊息來詢問其他 Windows PowerShell 支持者使用的編輯器與開發環境。

當您取得憑證之後,您也可以執行下面的單行指令來簽署特定位置的所有指令碼:

Get-Childitem *.ps1 | %{Set-AuthenticodeSignature $_.fullname $cert}

集中保障安全性

顯然 Windows PowerShell 執行原則可以依照逐部電腦的方式進行設定。但是對於企業環境來說,這不是理想的解決方案。在這種情況下,您可以使用群組原則 (您可以從以下網址下載 Windows PowerShell 系統管理範本:go.microsoft.com/fwlink/?LinkId=93675)。只要將檔案拖放到可影響整個網域的群組原則物件 (GPO) 中,然後設為 Restricted 即可。這麼一來,無論 Windows PowerShell 安裝在哪裡,您都不必擔心會執行指令碼。接著,您可以在要執行指令碼的電腦上 (像是您自己的工作站) 套用更寬鬆的設定 (例如 AllSigned)。

替代憑證

不同於 Windows 之前的系統管理指令碼語言,Windows PowerShell 甚至已經做好準備,能夠在安全的情況下應付替代憑證。舉例來說,Get-WMIObject 指令程式包含 –credential 參數,可讓您為遠端 WMI 連線指定替代憑證。此參數可接受使用者名稱但不接受密碼,藉此防範將密碼用硬式編碼的方式寫在純文字指令碼中。而是在您提供使用者名稱時,Windows PowerShell 會使用對話方塊提示您輸入密碼。若您打算再次使用替代憑證,您可以使用 Get-Credential 指令程式來儲存驗證資訊:

$cred = Get-Credential DOMAIN\USER

您會立刻看到輸入密碼的提示,而最終憑證將會儲存在變數 $cred 中。接著可以視需要將 $cred 變數傳遞到 –credential 參數。您甚至可以使用 ConvertTo-SecureString 和 ConvertFrom-SecureString 指令程式,將憑證轉換成加密的字串,或將先前加密的字串轉換回憑證。

但是此舉的問題在於,您的加密金鑰會儲存在純文字指令碼中,完全失去安全防護功能。因此替代的作法是新增 Get-Credential 的呼叫到 Windows PowerShell 設定檔中。那麼當 Windows PowerShell 執行時,您會立刻看到輸入使用者名稱和密碼的提示,而使用者名稱和密碼接著會儲存在名為 $cred 的變數中。如此一來,Shell 中將會永遠有一個代表網域系統管理員帳戶的 $cred 變數。您也無須在任何地方儲存該憑證;因為每次開啟殼層時都會重新建立憑證,也就無須費心儲存了。

預設的安全環境

在預設情況下安裝和設定的 Windows PowerShell,會提供支援指令碼的系統管理殼層所能提供的最高安全等級設定。除非您變更這些設定,否則 Windows PowerShell 將會盡可能維護安全性。也就是說,只有您的決策和動作會降低 Windows PowerShell 的安全性。因此,在變更任何預設值之前 (重新設定副檔名關聯、變更執行原則...等等),請先確認您已完全了解這些動作的後果,並且願意負起這些變更的責任。

Don Jones 是 SAPIEN Technologies 的指令碼語言大師,也是 ScriptingTraining.com 的講師。您可以透過 Don 的網站與他連絡:ScriptingAnswers.com

© 2008 Microsoft Corporation and CMP Media, LLC. 保留所有權利;未經允許,嚴禁部分或全部複製.