使用 PowerShell 處理認證身分時,常會被要求以 System.Security.SecureString 型別傳遞密碼參數。Windows 作業系統針對 SecureString 有額外的安全防護,包含內容加密儲存、降低記憶體搬移或轉存次數、可透過 Dispose() 主動釋放記憶體... 等等,可有效降低機敏資訊外洩風險。參考

一個典型案例子是 Invoke-WebRequest,如要存取非匿名存取網址需透過 -Credential 參數傳入 PSCredential 物件以提供登入帳號、密碼。而 New-Object System.Management.Automation.PSCredential($account, $passwd) 的 $passwd 不能直接給明碼字串,需傳入 SecureString。一般建議透過 $passwd = Read-Host "Password" -AsSecureString 請使用者當場輸入,不要將密碼寫死明碼存入腳本,能減少密碼外流風險。Read-Host 啟用 -AsSecureString 時,輸入密碼時顯示字元會變成 * 防止窺伺,而傳回結果亦會改為 SecureString 型別。如以下示範:

但在自動化排程中,無法依賴人員操作輸入密碼(總不能要求 OP 人員排十二兩、兩四夜哨進系統敲密碼吧?),就可能需要預先輸入密碼保存。如果情境允許,指定排程執行身分配合 -UseDefaultCredentials 是不錯的解決方案,但有時就是躲不掉直球對決,得自己處理密碼保存事宜。無論如何,我仍強烈建議別將密碼用明碼寫死在腳本裡,應考慮密碼加密後另外保存 Registry、環境變數,或獨立文字檔案,儲存管道愈分散愈安全(例如:若駭客只取得檔案系統存取權限,摸不到環境變數或 Registry,則資訊仍然是安全的),但即使只將密碼另存檔案也比寫死在 PowerShell 腳本來得好,至少不會因為單一 .ps1 檔外流即全面失守。

以下是個簡單示範,在設定階段由操作人員事先 Read-Host -AsSecureString 輸入密碼,ConvertFrom-SecureString 匯出加密內容後存入密碼檔(D:\Safe\Secret.txt);排程執行時,由檔案取回 ConvertTo-SecureString 轉回 SecureString 進行登入:

Read-Host "Password" -AsSecureString | ConvertFrom-SecureString | Set-Content "D:\Safe\Secret.txt"
Invoke-WebRequest -Uri http://localhost/WinAuth/test.txt -Credential (New-Object System .Management.Automation.PSCredential("demo", (Get-Content "D:\Safe\Secret.txt" | ConvertTo-SecureString)))

上圖 3 印出 Secret.txt 的內容,它是由使用者輸入密碼產生 SecureString 經 ConvertFrom-ScureString 匯出的結果,為加密形式,可透過 ConvertTo-SecureString 還原回 SecureString 供 PSCredentials 使用。但要注意,SecureString 預設是用 Windows DPAPI (Data Protection API)的金鑰進行加解密(註:在非 Windows 平台,SecureString 未對內部儲存體進行加密,而是透過其他技術提供額外保護),每位使用者在每台機器上的金鑰皆不相同,好處是駭客就算偷走 Secret.txt 檔案,除非回到同一台電腦用同一使用者登入,否則也無法解密。此一特性雖然讓人備感安全,但意味著操作人員需以排程身分逐台登入各主機設定,若要部署數十上百台,可就讓人笑不出來了。

面對這類情境,可善用 ConvertTo-SecureString / ConvertFrom-SecureString 可以 -Key 指定金鑰的特性。我們可用 .NET RNGCryptoServiceProvider 隨機產生金鑰存成 AES.key 檔,再改以其加密及還原密碼,如此 AES.key、Secret.txt、排程腳本一併複製到主機上,部署即告完成:(當然,代價是 AES.key 與 Secret.txt 若一起被偷走就破功了,必須嚴密保護)

#設定
$key = New-Object Byte[] 16
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($key)
$key | Out-File "D:\Safe\AES.key"
Read-Host "Password" -AsSecureString | ConvertFrom-SecureString -Key (Get-Content "D:\Safe\AES.Key") | Set-Content "D:\Safe\Secret.txt"

#執行
Invoke-WebRequest -Uri http://localhost/WinAuth/test.txt -Credential (New-Object System .Management.Automation.PSCredential("demo", (Get-Content "D:\Safe\Secret.txt" | ConvertTo-SecureString -Key (Get-Content "D:\Safe\AES.key"))))

實測如下:

最後談一個不建議但實務上可能會遇到需求:如何將明碼轉成 SecureString 以及從 SecureString 轉回明碼? (提醒:這類轉換極易形成資安漏洞,一般不建議使用,請審慎評估)

#將明碼轉為 SecureString (有安全疑慮,故需加入 -Force 強制執行)
$secString = ConvertTo-SecureString -AsPlainText -Force "PlainText"

# PowerShell 7 可用 ConvertFrom-SecureString -AsPlainText 轉回明文
#$plainPwd = ConvertFrom-SecureString -SecureString $passwd -AsPlainText
# PowerShell 6- 做法較麻煩 https://stackoverflow.com/a/28353003/288936
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secString)
$restored = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

【參考資料】

Tips of using SecureString in PowerShell.


Comments

Be the first to post a comment

Post a comment