Wednesday 25 July 2012

PowerShell - Using Credentials

Send-MailMessage

In PowerShell you can use the Send-MailMessage command to simply send an email through SMTP. This is easy enough and the command is simply to call this with some arguments as follows:

Send-MailMessage
1
Send-MailMessage -SmtpServer "mail.yourserver.com" -Subject "Error" -From "user@domain.com" -To "user@domain.com" -Body "test"

The problem is that you may get an authentication error:

The SMTP server requires a secure connection or the client was not authenticated.
The server response was: 5.5.1 Authentication Required.

The issue is that you need to specify a credential by using the -Credential parameter

You can use the Get-Credential Cmdlet, but this will show a prompt unless you specify the username and password in your script file. This is not good for anyone viewing or copying our script file. Turns out we can easily encrypt this information using the ConvertTo-SecureString:

First we need to get the password from user input:
Read-Host
1
Read-Host -AsSecureString
Output
************
System.Security.SecureString
PS C:\>

We can also read it from a UI prompt:

Get-Credential
1
(Get-Credential -Credential "user").Password
Send-MailMessage_1

You can also save this password to a file like this:

Get-Credential
1
(Get-Credential -Credential "user").Password | ConvertFrom-SecureString | Out-File "C:\temp\mycredential.cred"

Which will produce output that looks something like this:

Securestring output
01000000d08c9ddf0115d1118c7a00c04fc297eb0100000064ff74aec2ba6d428b22782a6f91faac000000000200000000001066000000010000200000005b03f7a95e78f996e0a25c5b50fc3f481cf275092131e7fc45eb7553db7d1220000000000e800000000200002000000060b480288abe198159022376076f3f6663478790e547238af5d8550b65a9b6f810000000f536330ee4f0a2a2cd37da4472c4ed17400000008d65be8e08d68ce5472c25f70b60a252d9380b7e950a5780b253e1e20bccbd491bb65d5db6513b0e8925eb6e70f730e21b09223981f43f96dc2b1de12a3407fe

Now these secure strings cannot be decoded by anyone else but your own logged on user, if you try to take this file and decrypt it on a different machine you will get this error:

SecureString error
ConvertTo-SecureString : Key not valid for use in specified state.
 
At line:1 char:58
+ cat ("C:\temp\mycredential.cred") | convertto-securestring <<<<
    + CategoryInfo          : InvalidArgument: (:) [ConvertTo-SecureString], CryptographicException
    + FullyQualifiedErrorId : ImportSecureString_InvalidArgument_CryptographicError,Microsoft.PowerShell.Commands.ConvertToSecureStringCommand

Note: Initially i thought that I could make this more secure by providing an additional security key something like this:

ConvertFrom-SecureString
1
ConvertFrom-SecureString -Key ([System.Text.Encoding]::ASCII.GetBytes("0e5c436f3993432590f12c0b2a48dafa"))

The Key i would use I would simply generate as follows:

NewGuid
1
[System.Guid]::NewGuid().ToString().Replace('-', '')
Output
0e5c436f3993432590f12c0b2a48dafa
PS C:\>

But this actually circumvents the Data Protection API. This page has a good explanation and more information.

So which way is more secure? Well that depends on your needs and environment but I think when the key is in your script file it is easier for an attacker to copy the file and script and then decrypt the information. So as always it is a good idea to apply as many security features as possible and keep the file safe and private, apply permissions and possibly use a different or lower access account.

The simplest way to save the credential to a file would be as follows:

Get-Credential
1
(Get-Credential -Credential "user").Password | ConvertFrom-SecureString | Out-File c:\temp\mycredential.cred
c:\temp\mycredential.cred
01000000d08c9ddf0115d1118c7a00c04fc297eb0100000021edac733db042498f873cbbdc18c766...
And we could save this to a file. However when running this same script say off a flash drive could be a problem when you create the encrypted file and load it again as they will be different for each logged on session. So I thought to create a reusable function that rather takes a store folder and saves a different file for each users unique account which is done by getting the AccountDomainSid:
GetFile
1
2
3
4
5
function GetFile($storeFolder)
{
     return [System.IO.Path]::Combine($storeFolder,
        [System.Security.Principal.WindowsIdentity]::GetCurrent().User.AccountDomainSid.ToString())
}
CreatePassword
1
2
3
4
function CreatePassword($storeFolder)
{
    (Get-Credential -Credential "user").Password | ConvertFrom-SecureString | Out-File (GetFile $storeFolder)
}

And use it:

Output
PS C:\> CreatePassword c:\temp

Creates a file S-X-X-XX-XXXXXXX-XXXXXXX-XXXXXXXXX

c:\temp\S-X-X-XX-XXXXXXX-XXXXXXX-XXXXXXXXX
01000000d08c9ddf0115d1118c7a00c04fc297eb0100000064ff74aec2ba6d428b22782a...

Reading the password:

GetPassword
1
2
3
4
function GetPassword($storeFolder)
{
    return cat ((GetFile $storeFolder)) | ConvertTo-SecureString
}

And use it:

Output
PS C:\> GetPassword c:\temp
System.Security.SecureString

Now as you can see we can't actually see the password because it is securely stored in the System.Security.SecureString But we can use this object to create Credentials.

Creating a credential:

ReadCredential
1
2
3
4
function ReadCredential($userName, $storeFolder)
{
    return new-object -typename System.Management.Automation.PSCredential -argumentlist $userName, (GetPassword $storeFolder)
}

And use it:

Output
PS C:\> ReadCredential "DOMAIN\user" "C:\temp"
 
UserName       Password
--------       --------
DOMAIN\user    System.Security.SecureString
 
PS C:\>

Putting it all together

As always you can create a reusable module from the functions above and use it whenever you need to save or load credentials. Just pick a filename such as CredentialFunctions.psm1 and then import it using:
Import-Module
1
Import-Module  -Name "c:\PowerShell\CredentialFunctions.psm1"

And now you can simply use this in credential functions such as Send-MailMessage

Send-MailMessage
1
Send-MailMessage -SmtpServer "mail.yourserver.com" -Subject "Error" -From "user@domain.com" -To "user@domain.com" -Body "test" -Credential (ReadCredential "DOMAIN\user" "C:\temp")

Powershell has a number of functions that may require Credentials and you could use this to do things like start processes etc.

Final note:

Even though the password is encrypted securely and with a key don't leave the file lying around and still protect it for instance on a flash disk. An attacker could still having access to your machine and your script key decrypt it. Lastly don't abuse the SMTP server.

No comments:

Post a Comment