Tome's Land of IT

IT Notes from the Powertoe – Tome Tanasovski

Category Archives: Security

Storing Passwords to Disk in PowerShell with Machine-key Encryption

I recently was handed these requirements for a task at work:

  • Store a password encrypted to disk so that it can be used to launch another powershell script.
  • The script may need to be launched by any user who belong to the Administrator group on the server.

I decided to add an additional requirement:

  • Try to also keep the password encrypted in memory using a SecureString.

Before I show you how I tackled this problem I’d like to be clear that I realize that there is no such thing as perfect security and encryption of a password in a script.  It’s usually better to use something like schtasks to store the credentials and provide a user permissions to launch the script via the task.  In my case I wanted the user to easily be able to modify the arguments passed to the script.  So really my intention is not to secure the password from the people using it, but to make it very difficult for someone outside of the group who will use the script to read the text in the script and be able to find the password.

If you are not aware, it is very easy to do this per user using a SecureString.  There are two Convert cmdlets that allow us to convert to and from a secure string:  ConvertTo-SecureString and ConvertFrom-SecureString.  For example, this is an extremely common way that people store a password to disk:

(get-credential).password|convertFrom-SecureString|set-content c:\password.txt

The way to load this password from disk is as follows:

$user = 'domainA\Tome'
$cred = New-Object System.Management.Automation.PsCredential $user,(Get-Content c:\password.txt| ConvertTo-SecureString)

The above two examples require the commands to be run using the same credentials in order for it to work properly.  However, ConvertTo-SecureString and ConvertFrom-SecureString also have a -key parameter so that you can perform these conversions by different users on the same computer or on a different computer altogether.  The problem with using the -Key parameter is that you need to store the key within your script.  Hardly very secure when the entire point of this exercise is to keep password out of the hands of someone who is able to read the text in the script.

In the end my solution used the Key parameter of the SecureString cmdlets, but only to ensure that the password is never in plain text in memory.  The Key is stored in the script, but it is the converted SecureString that I encrypt using RSA encryption with a machine key.  Here are the high-level steps that writes the password encrypted to disk.

  1. Get the password using the -AsSecureString parameter of Read-Host
  2. Convert the securestring to a string using ConvertFrom-SecureString using a 32 byte key
  3. Convert the string returned from step 2 into an array of bytes
  4. Create an RSA machine key in the cryptographic service provider (CSP)
  5. Encrypt the bytes in step 3 using RSA
  6. Serialize the encrypted bytes to disk as clixml using Export-Clixml
Here is the script used to perform the above logic:
$key = (2,3,56,34,254,222,1,1,2,23,42,54,33,233,1,34,2,7,6,5,35,43,6,6,6,6,6,6,31,33,60,23)
$pass = Read-Host -AsSecureString
$securepass = $pass |ConvertFrom-SecureString -Key $key
$bytes = [byte[]][char[]]$securepass            

$csp = New-Object System.Security.Cryptography.CspParameters
$csp.KeyContainerName = "SuperSecretProcessOnMachine"
$csp.Flags = $csp.Flags -bor [System.Security.Cryptography.CspProviderFlags]::UseMachineKeyStore
$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider -ArgumentList 5120,$csp
$rsa.PersistKeyInCsp = $true            

$encrypted = $rsa.Encrypt($bytes,$true)
$encrypted |Export-Clixml 'C:\Dropbox\My Dropbox\scripts\word.xml'
The high level workflow to decrypt the password directly into a secure string is as follows:
  1. Read in the decrypted bytes from the clixml file using Import-Clixml
  2. Initiate an instance of RSA that is using the machine key we created during the encryption process
  3. Decrypt the bytes using RSA
  4. Convert the decrypted bytes back into a string by first converting the bytes into chars and then joining them togethter
  5. Convert the string into a secure string using ConvertTo-SecureString along with the 32 byte key used by the encryption script
  6. Create the credential by using a constructor of the PsCredential class that accepts a username and a secure string as a password
  7. Launch the other script using start-process
Here is the decryption script
$encrypted = Import-Clixml 'C:\Dropbox\My Dropbox\scripts\word.xml'            

$key = (2,3,56,34,254,222,1,1,2,23,42,54,33,233,1,34,2,7,6,5,35,43,6,6,6,6,6,6,31,33,60,23)            

$csp = New-Object System.Security.Cryptography.CspParameters
$csp.KeyContainerName = "SuperSecretProcessOnMachine"
$csp.Flags = $csp.Flags -bor [System.Security.Cryptography.CspProviderFlags]::UseMachineKeyStore
$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider -ArgumentList 5120,$csp
$rsa.PersistKeyInCsp = $true            

$password = [char[]]$rsa.Decrypt($encrypted, $true) -join "" |ConvertTo-SecureString -Key $key
$cred = New-Object System.Management.Automation.PsCredential 'tome',$password            

Start-Process -Credential $cred -FilePath 'powershell.exe' -ArgumentList '-noprofile','-file','"c:\dropbox\my dropbox\scripts\t.ps1"', "blah"

The full scripts are not actually the complete scripts I used.  I wound up creating wrapper scripts for each script I needed the users to run with parameters.  The wrapper scripts had the same parameters and some logic within them.

I know there are some shortcuts you can take, e.g., you can convert strings to and from bytes using the System.Text functions. I also realize that it makes more sense to encrypt the 32 byte key using RSA rather than encrypting the SecureString, but to be honest, I realized this a bit late.  I left it as is figuring that an encrypted 192 byte string using a larger RSA key is probably better than 32 bytes encrypted with a smaller key, but it probably doesn’t matter much either way. You take a bit of a hit when you generate the larger RSA key, but it’s really negligible. In the end, I created something that is working fine for me using the techniques that I was quickest with.

It may not be perfect in a security conscious world, but it met my requirements and is working nicely for me.

Oh, one final note.  If you wanted to use this technique for users who are not administrators on the server you only need to grant them access to the Machine Key file created in c:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys.

Attacking Execution Policy

So last night I was hanging out in the chat room during the live taping of the Powerscripting Podcast.  Their guest was self-proclaimed “Security Ninja” David Kennedy.  The interview was a lot of fun and really compelling.  During the conversation he discussed a presentation he gave at Blackhat and Defcon that showed how to use PowerShell as a payload system (not an exploit).  You can check out the code from the presentation here (at the bottom of the page).

The SAM dump script is interesting – David definitely needs to learn how to use pinvoke code with add-type, but we’ll let him slide on that 😉  What is more interesting to me is the technique he used to get around a restricted execution policy.  To summarize he reads a script as Base64 encoding and then passes it into the powershell.exe with the -encodedCommand parameter.

I couldn’t resist one-lining this.  If you put this in a batch file like execute.bat you can call a PowerShell script by typing

execute.bat script.ps1

PowerShell -noprofile -Command "PowerShell -noprofile -encodedCommand ([Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes((Get-Content %1 |%%{$_}|out-string))))"

If you just want to paste it into a cmd prompt you need to omit the %1 and %% like this:

PowerShell -noprofile -Command "PowerShell -noprofile -encodedCommand ([Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes((Get-Content .\t.ps1 |%{$_}|out-string))))"

David says that Microsoft doesn’t consider this an exploit because execution policy was never intended to be a security feature.  In a way this makes sense.  It’s real intention is to prevent a user from accidentally double-clicking on an untrusted PowerShell script.  However, it’s still a way to get a PowerShell script run on a system that is locked down when you don’t have the rights to change the execution policy yourself.

Well, the ninja is cool in my book.  I highly recommend checking out the show when it’s released next week as episode 129.

%d bloggers like this: