Alternative to the PowerShell global scope - powershell

I am writing a function in PS that caches a password that a user provides. It does this by encrypting the password and saving it to disk, then saving the encryption password and key as a secure string in a global variable. This way the next time the function is called, it will have access to the key. After x minutes a job deletes the file from disk, and if the session is closed, the encryption key is lost.
having ran the PSScriptAnalyzer through my code it is (rightly) complaining about using a global variable. This is an issue because at some point I could very well want to push it to a repository, so I can't just ignore that error.
I also don't want to save the key to disk, as that wouldn't get cleaned up at the end of the PS session.
Is there a more suitable place I could store this data? (that will get wiped when the session closes)
ETA
I'm looking for an alternative to storing data in the global scope, that won't persist across PowerShell sessions. i.e. when I close the window the data is lost. I already have encrypted data on disk, I want to throw away the encryption key when the session ends.

You might want to keep the credentials inside the variable (perhaps a $script: scope one). I heve build a function for this a time ago.
function Set-Credentials
{
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true,ValueFromPipeline = $true,Position = 0)][ValidateNotNull()][String]$UserName,
[Parameter(Mandatory = $false,ValueFromPipeline = $true,Position = 1)][ValidateNotNull()][String]$UserPassword
)
if (-not$UserPassword)
{
$Password = Read-Host -Prompt "Specify password for $UserName" -AsSecureString
}
else
{
$Password = (ConvertTo-SecureString -String $UserPassword -AsPlainText -Force)
}
$Credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList #($UserName, $Password)
Write-Output -InputObject $Credentials
}

Related

Powershell script unable to decrypt password from Jenkins withCredentials()

I created a powershell script to run DB2 queries in Jenkins
withCredentials([usernamePassword(credentialsId: 'cred-id', usernameVariable: 'ID', passwordVariable: 'PASSWORD')]) {
$cn = new-object system.data.OleDb.OleDbConnection("Server=Server; Provider=IBMDADB2;DSN=DBName;User Id=$ID;Password=$PASSWORD");
$ds = new-object "System.Data.DataSet" "ds"
$q = "myQuery"
$da = new-object "System.Data.OleDb.OleDbDataAdapter" ($q, $cn)
$da.Fill($ds)
$cn.close()
}
If I run the script and hard code my credentials, it run fine.
With withCredentials(), I am getting the following error: Security processing failed with reason "15" ("PROCESSING FAILURE")
From some research, the error seems to be because DB2 can't handle encrypted data. Is there a way to overcome this error?
EDIT:
I tried to add
$SecurePassword = ConvertTo-SecureString $PASSWORD -AsPlainText -Force
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword)
$UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
at the beginning of my powershell script, but it still throws the same error even though the credential work fine if used in plain text
If I understand the docs for the Credentials Binding Jenkins plugin correctly, the variables designated in the withCredentials() call become environment variables, so as to enable their use across process boundaries.
Note that the values of these environment variables are not encrypted, so no extra (decryption) effort is required on the part of the target process.
Therefore, you need to use $env:[1] instead of just $ to refer to these variables in PowerShell:
$cn = new-object system.data.OleDb.OleDbConnection "Server=Server; Provider=IBMDADB2;DSN=DBName;User Id=$env:ID;Password=$env:PASSWORD"
[1] See the conceptual about_Environment_Variables help topic.

Setting autologon to a local user after joining to a domain

I'm building an application to assist in re-configuring devices on our network. I've got the entire script working except for setting the device to auto logon. Nothing I am trying seems to work. Now, the use is a little different, as the device will be joined to the domain, but logged on with a local user account. I'm trying to use the Sysinternals Autologon utility, but it is not working.
Here is the relevant code for logging on:
function Set-Autologon {
param (
[Parameter()][string]$domain
)
$exePath = "$scriptPath\$autologon"
$user = 'aloha'
$logonPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
$defaultdomain = "DefaultDomainName"
$alohass = '<very long string>' | ConvertTo-SecureString -Key $key
$alohaptr = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($alohass)
$aloharesult = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($alohaptr)
LogInfo "Setting auto logon for $aloha user."
Start-Process -FilePath $exePath -ArgumentList "/accepteula", $user, $domain, $aloharesult -Wait -Verbose
}
The domain is being passed in as that is coming from a CSV file. The $key is set somewhere else in the script. I've tried with the /accepteula in both the front and the back.
What am I missing here?
Thanks
This should work with "/accepteula" as the last argument but, you can create a DWORD registry key in HKCU\Software\Sysinternals\AutoLogon for EulaAccept=1 before running the exe. You could also try starting the autologon.exe with the /accepteula flag and then immediately killing the process before running with the username/credentials arguments.

Pass Stored Creds to bat file

I am trying to figure out if there is a way to take a stored credential in Windows Cred vault and pass it to a bat file that needs the credentials. I found a very old bat file that has a username and password in clear text. These are used to authenticate against a portal and have to be read by the bat in clear text. I have stored credentials on my server that I want to use so I can close this security gap but I am not 100% sure how to pass the password because it has to be in clear text. Here is what I have:
$creds = Get-StoredCredential -Target "Username"
$password = $creds.Password
$username = $creds.UserName
Start-Process cmd.exe "/c C:\trigger.bat `"argument1`" $username $password `"Argument2`" Argument3" -NoNewWindow -Verbose
When I enter the password in clear text in my line it works. If I use $password it throws an auth error. I assume that this is because the $password is a stored PSObject and isn't getting passed to cmd "correctly". Is there a way around this?
PS: Get-StoredCredential is from the CredentialManager module.
If $creds contains a PSCredential object, then you should be able to replace this:
$password = $creds.Password
with this:
$password = $creds.GetNetworkCredential().Password
Get-StoredCredential, when called without -AsCredentialObject, yields PSCredential instances.
The PSCredential.Password property you are accessing is not a String but a SecureString, so you cannot retrieve the plain text password like you are attempting.
Based on Convert a secure string to plain text you can use the PSCredential to get a NetworkCredential and then use its Password property...
$password = $creds.GetNetworkCredential().Password
In any case, when you get an authentication error using $username and $password you should ensure those variables contain the values you expect them to.

Is there a way to use ConvertFrom-SecureString and ConvertTo-SecureString with any user?

I'm using the following to create the password file:
$path = "C:\Users\USER\Desktop"
$passwd = Read-Host "enter desired password" -AsSecureString
$encpwd = ConvertFrom-SecureString $passwd
$encpwd > $path\filename.bin
Then calling the file with:
# define path to store password and input password
$path = "C:\Users\USER\Desktop"
# get the encrypted password from the path
$encpwd = Get-Content $path\filename.bin
# convert file to secure string
$passwd = ConvertTo-SecureString $encpwd
# define needed credential
$cred = new-object System.Management.Automation.PSCredential 'WIN-SERVER\AdminForQB',$passwd
# go to DVD drive launch setup.exe as user with privileges to launch the program with no user input required
Set-Location "C:\Program Files (x86)\Intuit\QuickBooks 2017\"
Start-Process PowerShell -windowstyle hidden -Cred $cred -ArgumentList .\QBW32PremierAccountant.exe
My goal is be able to run QB2017 with admin privs without giving the user admin privs. The issue I am having is that I have to generate the password file on each user or I get the following error if a user trys to use one generated from another user:
Key not valid for use in specified state.
Is there a way to use this method without generating a key per user?
When you use ConvertTo-SecureString and ConvertFrom-SecureString without a Key or SecureKey, Powershell will use Windows Data Protection API (DPAPI) to encrypt/decrypt your strings. This means that it will only work for the same user on the same computer.
When you use a Key/SecureKey, the Advanced Encryption Standard (AES) encryption algorithm is used. You are able to use the stored credential from any machine with any user so long as you know the AES Key that was used.
More info on this topic (with example code)
Note: This is only Security through obscurity, which isn't good practice. If you store the Key on the same computer as the encrypted password, it's not much more secure than plain text passwords!
PowerShell is the wrong solution for this.
To fix this you should give them read/write access to the folders and registry key QB2017 needs access to. If you still have issues, create a Shim using Microsoft's Application Compatibly Toolkit.
https://deployhappiness.com/fixing-applications-that-require-administrator-rights/

ConvertTo-SecureString gives different experience on different servers

I am running into an issue creating a Credential Object from an XML File on a remote server. Here is the code I am using to test
XML File
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
<Obj RefId="0">
<TN RefId="0">
<T>Selected.System.Management.Automation.PSCredential</T>
<T>System.Management.Automation.PSCustomObject</T>
<T>System.Object</T>
</TN>
<MS>
<S N="UserName">domain\username</S>
<S N="Password">01000000d08c9ddf0115d1118c7a00c04fc297eb010000001f19c6a42b9b0d48af2c531892e737ce000000000200000000001066000000010000200000006fb8862fbaea7b83cd2bcab35d7a8c8b4d71b7764c2a91d68eb3873864bc9d83000000000e8000000002000020000000fcbcc5552c3eb40ec337594f8286b08780709c1ac583d4679dcd7a3f5a92441b20000000c8e274811ed7a411b6741b2c65a67363f6aef380e684d13218d1ecc1281dfdb940000000c7279e81e21a1e57eed7da61e969f34fe2adf3d7e534bb5e10b89902adf4fdf20a69ec7e9b9e56dab512c789043a3b2cf0611e3b4893658b7c20f7892ce0ddfd</S>
</MS>
PowerShell Code
$cred = Import-Clixml "Payload\DeploymentCredential.xml"
write-host $cred
$cred.Password = ConvertTo-SecureString $cred.Password
write-host $cred.Password
$Credential = New-Object System.Management.Automation.PsCredential($cred.UserName, $cred.Password)
write-host $Credential.GetNetworkCredential().password
On one server (my local machine) it works completely fine, but on the remote server, I get this error
Key not valid for use in specified state.
+ CategoryInfo : InvalidArgument: (:) [ConvertTo-SecureString], CryptographicException
+ FullyQualifiedErrorId : ImportSecureString_InvalidArgument_CryptographicError,Microsoft.PowerShell.Commands.ConvertToSecureStringCommand
Both have the same version of PowerShell (3.0 Build -1 Revision -1), so I am not sure what the issue is.
The issue is how the original credential is created before being exported to xml.
When you use the command ConvertTo-SecureString it encrypts the plaintext password with the encryption key on the local machine, under your user account. This means that if you export it to xml, you can only use it on that same local machine.
The minute you copy the xml file to another machine and try to import the credential object, it won't work because it will be trying to decrypt it with it's local keys which don't match. (hence the error message). This is an important security measure as it prevents me from copying the file and using it on another computer.
If you need to have the user account on another computer to run something, then there is two options:
(Most secure) Create the credential object on each remote computer that you need it. This way it will use the local encryption keys and will prevent people from being able to steal the account.
(Least secure) When you create the credential with ConvertTo-SecureString you can specify the -Key or -SecureKey parameter. This way instead of using the local encryption keys, it will use the one you specify. Then in your script, you provide the same key to decrypt it. This is less secure because all I have to do is steal the credential file, and take a look inside your script (to see the key) and then I have stolen the account.
--Edit--
Here is an example of how to use a shared key. It is literally only one step up from writing in a plaintext password in your script, and is only used to obfuscate the password. There are many other -better- ways of running scripts on remote machines like PowerShell Remoting (See: Learn to Use Remoting in PowerShell). Or using Task Scheduler with saved credentials.
$PlainPassword = "P#ssw0rd"
$SecurePassword = $PlainPassword | ConvertTo-SecureString -AsPlainText -Force
$key = (3,4,2,3,56,34,254,222,1,1,2,23,42,54,33,233,1,34,2,7,6,5,35,43)
$SecurePasswordKey = ConvertFrom-SecureString $SecurePassword -Key $key
#Output the hash
$SecurePasswordKey
#Output
76492d1116743f0423413b16050a5345MgB8ADIAKwBZAEkALwB0ADUAZwBQAHoAbwBNAEEAUwA0AFQAagB0AGsANwBmAHcAPQA9AHwAYgA3ADgAMwBjAGIANAAzADIAZAAwADEAYQA1AGUAMwBjAGUAYgA2AGMAMQBkADcAYQA3ADMAZAA1ADQAYwA0ADMAYgBlAGEANQAyAGQANQA0AGUAYgA5AGEAMgA0AGIANwBhAGIAMQAzADAAMwAzAGEANAA4ADEANQA0AGEAMAA=
On remote machine:
$SecurePasswordKey = '76492d1116743f0423413b16050a5345MgB8ADIAKwBZAEkALwB0ADUAZwBQAHoAbwBNAEEAUwA0AFQAagB0AGsANwBmAHcAPQA9AHwAYgA3ADgAMwBjAGIANAAzADIAZAAwADEAYQA1AGUAMwBjAGUAYgA2AGMAMQBkADcAYQA3ADMAZAA1ADQAYwA0ADMAYgBlAGEANQAyAGQANQA0AGUAYgA5AGEAMgA0AGIANwBhAGIAMQAzADAAMwAzAGEANAA4ADEANQA0AGEAMAA='
$key = (3,4,2,3,56,34,254,222,1,1,2,23,42,54,33,233,1,34,2,7,6,5,35,43)
$SecurePassword = ConvertTo-SecureString -String $SecurePasswordKey -Key $key
Here's one method for some randomness in creating the key if you choose to use the answer from HAL9256.
[byte[]]$Rand = for($var=1;$var -le 24){
Get-Random -min 1 -max 255
$var++
}
We create an array of bytes which is filled with 24 random numbers from 1 to 255. These numbers are not displayed and exist only when the script is run.
Then we have a key which can be used in the above answer. The value of $Rand will disappear once the script executes, or you use Remove-Variable Rand
Just be sure to save the data from $Rand to some place secure or the key used to encrypt the data is lost.