Защита и шифрование паролей в скриптах PowerShell

Администраторы часто при написании сценариев автоматизации на PowerShell сохраняют пароли непосредственно в теле PoSh скрипта. Как вы понимаете, это крайне небезопасно при использовании в продуктивной среде, т.к. пароль в открытом виде могут увидеть другие пользователи сервера или администраторы. Поэтому желательно использовать более безопасный метод использования паролей в скриптах PowerShell, или шифровать пароли, если нельзя пользоваться интерактивным вводом.

Безопасно можно запросить от пользователя ввести пароль в скрипте интерактивно с помощью командлета Get-Credential . Например, запросим имя и пароль пользователя и сохраним его в объекте типа PSCredential :

$Cred = Get-Credential

powershell Get-Credential

При обращении к свойствам переменной можно узнать имя ползователя, который был указан.

$Cred.Username

Но при попытке вывести пароль, вернется текст System.Security.SecureString, т.к. пароль теперь хранится в виде SecureString.

$Cred.Password

пароль хранится в System.Security.SecureString

Объект PSCredential, который мы сохранили в переменной $Cred теперь можно использоваться в командлетах, которые поддерживают данный вид объектов.

Параметры $Cred.Username и $Cred.Password можно использовать в командлетах, которые не поддерживают объекты PSCredential, но требуют отдельного ввода имени и пароля пользователя.

Также для запроса пароля пользователя можно использовать команлет Read-Host с атрибутом AsSecureString:
$pass = Read-Host "Введите пароль" –AsSecureString

Read-Host AsSecureString

В данном случае, вы также не сможете увидеть содержимое переменной $pass, в которой хранится пароль.

В рассмотренных выше способах использования пароля в скриптах PowerShell предполагался интерактивный ввод пароля при выполнении скрипта. Но этот способ не подойдет для различных сценариев, запускаемых автоматически или через планировщик.

В этом случае удобнее зашифровать данные учетной записи (имя и пароль) и сохранить их в зашифрованном виде в текстовый файл на диске или использовать непосредственно в скрипте.

Итак, с помощью комадлета ConvertFrom-SecureString можно преобразовать пароль из формата SecureString в шифрованную строку (шифрование выполняется с помощью Windows Data Protection API — DPAPI). Вы можете вывести шифрованный пароль на экран или сохранить в файл:

$Cred.Password| ConvertFrom-SecureString | Set-Content c:pspassfile.txt

ConvertFrom-SecureString

Чтобы использовать зашифрованный пароль из файла нужно выполнить обратное преобразование в формат Securestring с помощью командлета ConvertTo-SecureString :

$username = ″corpadministrator″
$pass = Get-Content c:pspassfile.txt | ConvertTo-SecureString
$creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username, $pass

получение пароля из файла с помощью ConvertTo-SecureString

Таким образом в переменной $creds мы получили объект PSCredential с учетными данными пользователя.

Однако, если попробовать скопировать файл passfile.txt на другой компьютер или использовать его под другим пользователем (не тем, под которым создавался пароль), вы увидите, что переменная $creds.password пустая и не содержит пароля. Дело в том, что шифрованием с помощью DPAPI выполняется с помощью ключей, хранящихся в профиле пользователя. Без этих ключей на другом компьютере вы не сможете расшифровать файл с паролем.
ConvertTo-SecureString : Ключ не может быть использован в указанном состоянии.
"Не удается обработать аргумент, так как значением аргумента "password" является NULL.
Укажите для аргумента "password" значение, отличное от NULL."

null-password

Таким образом, если скрипт будет запускаться под другим (сервисным) аккаунтом или на другом компьютере, необходимо использовать другой механизм шифрования, отдичный от DPAPI. Внешний ключ шифрования можно указать с помощью параметров –Key или –SecureKey .

Например, вы можете с помощью PowerShell сгенерировать 256 битный AES ключ, который можно использовать для расшифровки файла. Сохраним ключ в текстовый файл password_aes.key.

$AESKey = New-Object Byte[] 32
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($AESKey)
$AESKey | out-file C:pspassword_aes.key

генерация aes ключа для шифрования пароля

Теперь можно сохранить пароль в файл с помощью данного ключа:

$Cred.Password| ConvertFrom-SecureString -Key (get-content C:pspassword_aes.key)| Set-Content c:pspassfile.txt

зашифрованный файл с паролем powershell

Не забывайте, что если в Powershell скрипте у вас указывается доменная учетная запись, и на нее действует политика регулярной смены пароля, вам придется обновлять данный файл при каждой смене пароля (вы можете создать для определенных учёток отдельную политику паролей с помощью множественных политик паролей FGPP .

Таким образом у нас получилось два файла: файл с зашифрованным паролем (passfile.txt) и файл с ключом шифрования (password_aes.key).

Их можно перенести на другой компьютер и попытаться из PowerShell получить пароль из файла (можно разместить файл ключа в сетевом каталоге)

$pass = Get-Content c:pspassfile.txt | ConvertTo-SecureString -Key (get-content \Server1Sharepassword_aes.key)
$pass

ConvertTo-SecureString -key

Если не хочется заморачивать с отдельным файлом с AES ключом, можно зашить ключ шифрования прямо в скрипт. В этом случае вместо ключа в обоих случая нужно использовать

[Byte[]] $key = (1..16)
$Cred.Password| ConvertFrom-SecureString –Key $key| Set-Content c:pspassfile.txt

А для расшифровки:

[Byte[]] $key = (1..16)
$pass = Get-Content c:pspassfile.txt | ConvertTo-SecureString -Key $key

простой ключ aes

Как вы видите пароль не пустой, значит он был успешно расшифрован и может быть использован на других компьютерах.

Совет . Необходимо ограничить доступ к файлу с AES ключом, чтобы только пользователь или аккаунт, под которым запускается скрипт имел к нему доступ. Внимательно проверьте NTFS разрешения на файл password_aes.key при размещении его в сетевом каталоге.

И напоследок, самый печальный момент. Пароль из объекта PSCredential в открытом виде вытаскивается очень просто:

$Cred.GetNetworkCredential().password

$Cred.GetNetworkCredential().password

Можно извлечь пароль в текстовом виде и из SecureString:

$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pass)
[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

SecureStringToBSTR получение пароля в открытом виде

Как вы понимаете, именно поэтому нежелательно сохранять пароли привилегированных учетных записей, таких как Domain Admins где бы то ни было кроме DC.

Совет. Для защиты административных учтённых записей от извлечения паролей из памяти с помощью утилит подобных Mimikatz нужно использовать комплексные мероприятия , в том числе организационного плана.
EnglishRussianUkrainian