ConvertTo-SecureString gives different experience on different servers - powershell

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.

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.

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 Key not valid for use in specified state

I'm getting an error when running a script as a particular user, but not as my own user account. This is on Windows 2012R2. Powershell module was installed and has been in use for some time, but using another account prevents it from running. Because I have been able to run it, I feel like I can rule out any missing files or faulty commands within the script. The rest of the script is dependent on this. I also have run the convertto-securestring options using the service account that will be running the commands through the automation, but it still is not working.
This is the secure password script I ran to convert the password
$password = "password"
$secureStringPwd = $password | ConvertTo-SecureString -AsPlainText -Force
$secureStringText = $secureStringPwd | ConvertFrom-SecureString
Set-Content "E:\temp\ExportedPassword.txt" $secureStringText`
This is the error message after trying to run as user B
line 4 $pwdTxt = Get-Content "E:\temp\ExportedPassword.txt"
line 5 $securePwd = $pwdTxt | ConvertTo-SecureString
ConvertTo-SecureString : Key not valid for use in specified state.
At E:\temp\get_user_cliffsv4.ps1:5 char:24
+ $securePwd = $pwdTxt | ConvertTo-SecureString
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [ConvertTo-SecureString],
CryptographicException
+ FullyQualifiedErrorId :
ImportSecureString_InvalidArgument_CryptographicError,Microsoft.PowerShell.
Commands.ConvertToSecureStringCommand
I have run this script numerous times through automation software, but when testing I had to run the command as my personal account because of this error and to get around this so I could complete testing, I invoked that option. My question is, is the account I'm using missing any particular rights? Do I have to install the Powershell extensions under that account or can I set/change something in PS or on the server that will allow the service account to run this script? Thanks in advance!
The issue was not the service account's ability to run the command, it was that the System account was running the command, so the secure password it was passing was not usable. I found this link that provided a script that scheduled the script to run using the system account, thereby creating the file by system which will correctly used the password file and passed the credentials correctly. Thank you to Keith Francis and the site for hosting the code and always, I'm very thankful for these communities that provide an outlet for support.
https://community.spiceworks.com/scripts/show/3517-use-powershell-securestring-with-windows-system-account

Copying from one server to other with credentials

I'm trying to copy a file from one server to another server so that it doesn't ask me to enter my login info and I don't have to store the password blatently in the code. The problem is, it's still coming up with a credential request GUI, even though I give the -Credential parameter. This is how I store the password in a file:
read-host -assecurestring | convertfrom-securestring | out-file C:\secString.txt
Then the code looks like this:
function GetSecureLogin() {
$username = "usa02xxxswg2\20xbackup"
$password = get-content C:\secString.txt | convertto-securestring
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $password
}
function CopyFileToFolder ([string]$Source, [string]$destination){
Copy-Item "$Source" -Destination "$destination" -Credential $cred #this is where the login info box is coming up but shouldn't be
}
######################start here#####################
$cred = ""
GetSecureLogin
$tempSource = "filename.txt"
$ToLocation = "$ToLoc"
CopyFileToFolder $tempSource $ToLoc
Any ideas? I'm relatively new to PowerShell, so I could be doing something silly. I'm not sure how to tell if $cred is getting back to my Copy function ok or if it's out of scope.
I've looked at these examples but I'm having trouble deciding if I need to do something different because they are specifying the server info and I'm trying to avoid that since I just want to use UNC path like I was doing before. The copying was working fine without login because I was using my Active Directory login to save the task. Now we want to use a locally saved login (where the script is run) and then it needs a different local login when it's trying to access the other server.
copy-item with alt cred,
powershell without prompt for cred,
copy without cred,
copy with alt cred,
cred without password prompt
I'm trying to figure out if returning $cred from my GetSecureLogin function, and assigning it to $cred in the main part of the script will fix the problem where it's asking me for the login info when I do the copy. It looks like my co-worker moved all the directories, so I still have to test this once I figure out where he moved them to.
Looks like I figured it out. I did it like this link. It was a little tricky in that I didn't have to use the directory that came after my sharename, but once I got rid of the subdirectory, it worked.
copy with credentials and New-PSDrive

Use Powershell with CruiseControl.Net for secure password storage?

The CruiseControl.Net tfs integration requires a username and password for a user with rather high privileges to be stored in the configuration files. I'm not very fond of that, and have tried to think of ways to have the password stored securely and yet maintain fully automated builds.
Powershell has good support for this with the ConvertTo-SecureStringand ConvertFrom-SecureString commands, and something like this would handle the encryption:
"password-string" | ConvertTo-SecureString -AsPlainText -Force | Out-File pwd.dat
And this could handle decryption:
$user = "mydomain\myuser"
$pwd = Get-Content pwd.dat | ConvertFrom-SecureString
$cred = New-Object System.Management.Automation.PSCredential($user, $pwd)
CruiseControl.Net has a <powershell> task that can be used for running powershell scripts, but I'm unsure whether this can be made to return anyhting. I'm wondering if anyone has any ideas on how to implement secure passwords with CruiseControl.Net? Using Powershell isn't necessary, I'm just figuring it would be neat.
Found a workaround at least: if you omit the <user>,<password> and <domain> parameters, CruiseControl will use the credentials for the user running the service.
The more general question, whether it is possble to catch output remains to be tested. I guess it could be possible to set an environment variable using [system]::SetEnvironmentVariable(name,value), but I haven't tested this.