Is it possible to concatenate a secure string with variables and unsecure strings in PowerShell?
I have a formula that is used for local administrator passwords in our standalone environment (no domain). I would like to store part of that formula in a file securely, then get that, convert it to a regular string, and concatenate it with another object and another string to form the password.
My intent is not to expose our formula in plain text in the workflow that I am putting together. Is there another way that I am missing? It's too many machines to easily store each password separately.
Credential objects allow you to decrypt a secure string (source):
PS C:\> $pw = 'bar' | ConvertTo-SecureString -AsPlainText -Force
PS C:\> $pw.GetType().FullName
System.Security.SecureString
PS C:\> $cred = New-Object Management.Automation.PSCredential 'foo', $pw
PS C:\> $cred.GetType().FullName
System.Management.Automation.PSCredential
PS C:\> $cred.UserName
foo
PS C:\> $cred.Password
System.Security.SecureString
PS C:\> $cred.GetNetworkCredential().Password
bar
However, the encryption key for the secure string is tied to the user and computer that created the secure string, so it's not usable on another computer or by another user. You can work around this, but that would require to encrypt the secure string with a key generated by you, and that key would have to be available in plain text (or rather plain bytes) whenever the secure string needs to be decrypted. Which kinda defeats the purpose of encrypting it in the first place.
On top of that, if you have enough machines to make storing separate passwords a hassle, you also have enough machines to seriously consider putting them in a domain.
Related
I am having challenge with one of my Powershell scripts. The idea is to run a tool from a client, which will kill some processes using the PID. Proof of concept works - ps1 script is converted to exe (PS2GUI) and the tool is successfully killing the PID of the user on the server.
$credentials = New-Object System.Management.Automation.PsCredential("domain\user",$password)
$scriptBlockSQL = {
sqlplus.exe command
}
Invoke-Command -ComputerName "server" -Credential $credentials -scriptBlock $scriptBlockSQL
However I have a bit of a problem, because I currently store the password in the script as clear text which is very unsafe - since the .exe can be decompiled in less than a few seconds.
I have tried the ConvertTo-SecureString with the encrypted string, however this ties it with the user account & pc - which is not an option. The usage of the Key file is also not an option.
Do you have any suggestion how to make the script safer & usable? Or any other solution which will work in the same way?
Thanks!
Well, when you are encrypting password with a SecureString then you have to define the password somewhere in the code or in another file. So this is not a good idea. Then you have to look for a way to protect script source code in an efficient way.
PS2GUI will encrypt your source code in a reversible encryption, that means when the script is ran by powershell, the engine will decrypt the code. It is also weak because of using symmetric key algorithm.
Powershell scripts are also encrypted in Base64 format, and PS2GUI does the quite same like thing.
The best way I think now is to prompt the user for credentials. Like this:
$password = Read-Host "Enter password" -AsSecureString
Would this in some way make it more secure than just a secure string and storing it in a credential? Why would someone do this?
$secVal = read-host -AsSecureString -prompt "Enter xstoreuser password"
$strVal = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secVal)
$dasPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($strVal)
and then later
psftp -l somelogin -pw $dasPassword -P 22 -b .\ftp.txt somehost
The technique you're showing is not about making the command more secure - on the contrary:
You need to do extra work to recover the plain-text password from the [securestring] instance, because in order to pass a password to an external program such as psftp.exe you need an insecure plain-text representation, because external programs know nothing about .NET secure strings.
How secure is SecureString? tells you about .NET secure strings and their limitations in general.
As an aside: The command for extracting the plain-text password can be simplified a little via an aux. [pscredential] instance:
# Returns the plain-text content of secure string $secVal:
(New-Object pscredential somelogin, $secVal).GetNetworkCredential().Password
The only secure approach is to avoid plain-text passwords altogether and use a PKI approach (based on public and private keys) instead, if supported by the target program.
To demonstrate that the plain-text approach is insecure (even without the use of an intermediate variable to store the unencrypted password):
# Create a [pscredential] instance with a secure-string password:
# Note: To make this demo self-contained, I'm converting *from* plain
# text in order to construct the credential object.
# Note that you should never do that in production code.
# The need to specify -Force to do it is a reminder that it
# normally shouldn't be done.
$cred = New-Object pscredential someuser,
(ConvertTo-SecureString -AsPlainText -Force 'actualPassword')
# Open a new hidden window that passes the password *as plain text* to
# `cmd /c echo` (and then waits for a keypress, to keep the process alive).
$ps = Start-Process -PassThru -WindowStyle Hidden cmd "/c echo $($cred.GetNetworkCredential().Password) & pause"
# Now inspect the process' command line using CIM (WMI):
(Get-CimInstance Win32_Process -Filter "ProcessID = $($ps.ID)").CommandLine
# Kill (stop) the sample process.
$ps | Stop-Process
The above yields "C:\WINDOWS\system32\cmd.exe" /c echo actualPassword & pause, demonstrating that the plain-text password can indeed be obtained by other processes.
You can also use GUI methods for inspecting a running process' command line: Task Manager (Taskmgr.exe) or, without potential truncation, SysInternals' Process Explorer (procexp.exe), which, however, must be installed on demand - see this ServerFault answer.
In the following code, where does the system store the value for $Credential?
$Credential= $host.ui.PromptForCredential("","Please Enter Your domain username, eg. user1#contoso.com","","")
What happens to the value after the code has been executed?
Pardon the very basic questions... just want to be sure about any security implications on the script I'm making. I was also hoping I could find the MSDN/TechNet article that specifically addresses this matter.
Thank you very much.
Firstly, you should use Get-Credential instead - it's easier and does the same job:
$credential = Get-Credential -Message "Please Enter Your domain username, eg. user1#contoso.com"
Both methods create a PsCredential object, a reference to which is stored in $credential. The password is stored in memory as a SecureString, to help keep it safe, and not written to disk, registry, etc, unless you go out of your way to do so:
PS C:\> $credential
UserName Password
-------- --------
domain\user System.Security.SecureString
So, it is safe from casual attempts to read. However, it isn't entirely secure:
PS C:\> $credential.GetNetworkCredential() | Format-List *
UserName : user
Password : Password123
SecurePassword : System.Security.SecureString
Domain : domain
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/
I am trying to create a file using powershell in a specific user context. E.g I have a user user01 on my local machine and I want to create a file in its context.
I am doing something like
New-Item c:\file.txt -Credential User01
It works but prompts me for password which I dont want it to. Is there any way I can accomplish this without having it prompt for password ?
The credential parameter on new-item is not actually supported for filesystems, so I'm not sure what you mean by "it works." It does NOT create the file as the passed user. In fact, the filesystem provider will say:
"The provider does not support the use of credentials. Perform the operation again without specifying credentials."
Taking an educated guess, I'd say you're trying to create a file with a different owner. PowerShell cannot do this on its own, so you'll need the following non-trivial script:
http://cosmoskey.blogspot.com/2010/07/setting-owner-on-acl-in-powershell.html
It works by enabling the SeBackup privilege for your security token (but you must already be an administrator.) This allows you to set any arbitrary owner on a file. Normally you can only change owner to administrators or your own account.
Oh, and this script is for powershell 2.0 only.
Rather than use a PowerShell cmdlet or .NET scripting on this one, you might take a look at the Windows utility takeown.exe. However, even it requires you supply the user's password that you're assigning ownership to.
Ok, I do start process in the user context and then create a file. Works like a charm.
Password, FilePath and UserName are passed in as arguments from command line.
$pw = convertto-securestring "$Password" -asplaintext –force
$credential = new-object -typename system.management.automation.pscredential -argumentlist "-default-",$pw
$localArgs = "/c echo>$FilePath"
[System.Diagnostics.Process]::Start("cmd", $localArgs, "$UserName", $credential.Password, "$Computer")
Or just make a call to SUBINACL.EXE? No need for password then.