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
Related
I have a powershell script in my release pipeline stage that runs a command and passes values of a secret variable to it. The issue is that the Logs show each and every command as they are run including each arguments passed, one of which is from a secret variable.
How do I make the powershell output not show the command it is running? output of the command is okay to show if it can't be hidden.
Secrets shouldn't be converted to plain text but kept as such and passed as a SecureString to your application. In other words, the solution lays in making sure that your concerned application accepts a hashed password, a SecureString or a PSCredential object also knowing that sending a plain text password to an application isn't secure by itself.
#iRon Say this to Microsoft. I am trying to call their schtasks
I just did: #16502: Set-ScheduledTask shouldn't accept a plain text Password
As a workaround, you might keep your password covered in a SecureString as long as possible:
$Credentials = Get-Credential
Set-ScheduledTask -User $Credential.UserName -Password $Credential.GetNetworkCredential().Password
This will prevent that the passwords are revealed by logging but as there is still a potentially security risk that the password could be read from memory, I recommend to do a garbage collection ([system.gc]::Collect()) right after this command.
⚠️ Important
A SecureString object should never be constructed from a String, because the sensitive data is already subject to the memory persistence consequences of the immutable String class. The best way to construct a SecureString object is from a character-at-a-time unmanaged source, such as the Console.ReadKey method.
To be completely safe, you might also consider to run Set-ScheduledTask (without -User and -Password) under the credentials of the targeted user Start-Process -Credential $Credential ...
Update 2022-02-24:
Sadly😭, I got zero response on my feedback hub "Set-ScheduledTask shouldn't accept a plain text Password" (security) issue. Therefore, I have also just created a new Microsoft Feedback Portal issue for this: Windows-PowerShell: Set-ScheduledTask shouldn't accept a plain text Password
Anyhow, the organization I work for, deals with the same general issue where the use-case is defined as: "how can we hide sensitive information as passwords used by invoked 3rd party applications in PowerShell scripts"
As suggested before: the problem in not due to any (PowerShell) scripting limitations but how the information (as plain text) is provided (input) to the script and how it is expected to be passed (output) to any other application.
To make this clear and to supply at least some (easy) solution, I have created an [HiddenString] class that might be used in a script to hide information as much as possible end-to-end inside the script itself.
class HiddenString {
hidden [SecureString]$SecureString = [SecureString]::new()
HiddenString([Object]$String) {
if ($String -is [SecureString]) { $This.SecureString = $String }
else {
foreach ($Character in [Char[]]$String) { $This.SecureString.AppendChar($Character) }
}
}
[String]Reveal(){
$Ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($This.SecureString)
$String = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($Ptr)
[System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($Ptr)
Return $String
}
}
Note that I am using the SecureString type in the class not for better security but just for better hiding the concerned string.
Usage example:
function MyScript([String]$TaskName, [String]$UserName, [HiddenString]$Password) {
Start-Transcript -Path .\Transcript.txt
Write-Host "Scheduling $TaskName for $UserName/$Password" # Write-Log ...
Set-ScheduledTask -TaskName $TaskName -User $UserName -Password $Password.Reveal()
Stop-Transcript
}
Recommended invocation of MyScript:
$SecuredString = Read-Host 'Enter Password' -AsSecuredString
MyScript NotePad.Exe JohnDoe $SecuredString
Just hiding the sensitive information inside the MyScript:
$String = 'Sensitive Information'
MyScript NotePad.Exe JohnDoe $String
Transcript started, output file is .\Transcript.txt
Scheduling NotePad.Exe for JohnDoe/HiddenString
Transcript stopped, output file is .\Transcript.txt
Again, (I can't stress this enough):
warning: as a whole, this workaround is nothing more than security through obscurity
As Microsoft states themselves at SecureString shouldn't be used:
The general approach of dealing with credentials is to avoid them and instead rely on other means to authenticate, such as certificates or Windows authentication.
(which they should also do in their own cmdlets along with Set-ScheduledTask)
I have created an enhancement request for this idea:
#16921 Add [HiddenString] Class
You can register secrets with the agent to ensure they're scrubbed from the logs.
Write this to the output:
write-output "##vso[task.setsecret]THEVALUEYOUWANTHIDDEN"
This should register the secret with the agent. If you know your script will also popentially log the base64 value or another representation of the secret, make sure you register all permutations.
I have the below PowerShell script (myscript.ps1) in which I ask for username and password. Depending on the username and password it copies a file to a certain destination.
$credentials = Get-Credential
if ($credentials.Username -eq "user1" -And $credentials.GetNetworkCredential().password -eq "pass1")
{ Copy-Item "test1.pdf" "\test\test1.pdf"; }
else
{ Copy-Item "test2.pdf" "\test\test2.pdf"; }
Requirement: I want to make this file protected so no one can edit it and see the username and password.
PS2EXE
I found a solution found here which converts the PowerShell script to an .exe file. When I originally run the script using PowerShell a dialog box appears allowing me to enter the username and password:
After the .exe is generated and when I run it the credentials dialog box no longer appears. Instead, the console appears saying "Credential:"
I don't know why? I want the credentials form to still appear when running the exe. Any thoughts please?
Q: Why does the EXE prompt with "Credential"?
This isn't an answer to the real question, and is based on guessing/supposition about PS2EXE, but I hope it is useful to clear up some confusion.
Having looked briefly at the PS2EXE page linked above, it seems that this utility encodes the script in Base64 and bundles it with a lightweight (?) custom PowerShell host. When run, I suppose the EXE starts the host, decodes the script and runs it.
The problem is that the Get-Credential cmdlet is running within a PS host that probably can't interact with the desktop. That is, it can't put up the GUI prompt for credentials. It therefore needs to prompt for the Credential property on the command line, explaining why you see that behaviour.
Workaround with Read-Host?
Instead of trying to use Get-Credential to prompt for username and password, you could embrace what PS2EXE seems to be doing and just use Read-Host:
$UserName = Read-Host "Enter username"
$Password = Read-Host "Enter password" -AsSecureString
$Credentials = New-Object System.Management.Automation.PSCredential $UserName,$Password
if ($credentials.Username -eq "user1" -And $credentials.GetNetworkCredential().password -eq "pass1")
{ ... }
Using -AsSecureString will hide the password on the screen. The $Password variable will be of type System.Security.SecureString, which can be used to create a PSCredential object as shown.
You'd need to test this, but it seems that you're able to read from the shell but not from a GUI prompt.
And just to be clear: none of this is anywhere near best-practice security. If you need authentication/authorization for these activities, step back and look at the problem again.
Workaround with two scripts?
It seems that PS2EXE doesn't support -AsSecureString in the same way that normal PowerShell does, i.e. it doesn't hide the characters. A possible workaround for this would be to collect the username and password from the user in one script and then pass them to a PS2EXE-converted script for processing.
Launch-MyScript.ps1:
$Credentials = Get-Credential
& MyScript.exe $Credentials.Username $Credentials.Password
MyScript.exe (coverted with PS2EXE):
param($Username,$Password)
$Credentials = New-Object System.Management.Automation.PSCredential $Username,$Password
if ($Credentials.Username -eq "user1" -and
$Credentials.GetNetworkCredential().password -eq "pass1")
{
...
}
The user runs Launch-MyScript.ps1 and completes the password prompt. Then the EXE is run automatically with the username and password passed in as arguments. Note that, as shown above, the password is a Secure String. Test this; I'm not using PS2EXE so it's a theoretical solution at the moment.
If you can't pass $Password along the pipeline as a Secure String object, you can convert it to text with ConvertFrom-SecureString in the first script, then conver it back with ConvertTo-SecureString in the second one.
According to this article http://windowsitpro.com/powershell/protect-your-powershell-scripts you should first set ur execution policy to AllSigned by Set-ExecutionPolicy AllSigned, then create a certificate using makecert cmdlet.
Then u can sign single script using Set-AuthenticodeSignature cmdlet or use .pfx File to Sign a Script which appears even safer.
Hope it helps a bit.
I have some PowerShell scripts to update data in active directory. Now I want to run these scripts from another domain joined computer, but the user that is currently logged in does not have admin rights to AD to run the scripts. How can I pass the credentials first to connect to domain as administrator and then run the script?
I know about the command get-credentials but I don't want any manual intervention.
There is batch file which runs the script and I want to put the credentials once.
I also don't want to show the password to the logged in user. Is there any possibility we can save the password in encrypted format?
Hope there is trust between the two domains
$Server = 'XXXXXXXXXX'
$username = 'Domain\XXXXXXXXXX'
$password = 'XXXXXXXXXX'
$securepassword = ConvertTo-SecureString $Password -AsPlainText -force
$cred = New-Object System.Management.Automation.PSCredential -ArgumentList ($username,$securepassword)
Get-ADComputer -Identity $Server -Credential $cred
You can change the entire script in to exe file using PowerGUI and use credentials to save it from being opened.
or
use the script by Brenton J.W. Blawat for encryption located at http://gallery.technet.microsoft.com/scriptcenter/PowerShell-Script-410ef9df
or
use the simple script mentioned in the below article
http://www.interworks.com/blogs/trhymer/2013/07/08/powershell-how-encrypt-and-store-credentials-securely-use-automation-script
Instead of using a batch file you could write a VBS wrapper and then use the script encoder to turn it into a VBE. The script encoder is technically not supported in Vista or 7 but it still works if you can find it somewhere. The other option would be to put all your code into a .Net EXE. Once it’s compiled it would hide the password from an ordinary user. Someone that knows what they are doing could still extract it so be aware of that. The same goes of an encoded VBE.
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.
I have a Powershell script that is going to be run through an automation tool against multiple servers.
It works fine on Windows machines, as the remote calls use the tool's service account without any need for prompting or exposing any credentials in code.
This script also runs against Linux machines via SSH using the SharpSSH package. SharpSSH does not automatically use the Powershell user's credentials but requires either a username and password, an RSA key file, or a PSCredential object.
I can't prompt for credentials using Get-Credential, because it's being run through the automation tool. I don't want to expose the username and password in code or have an RSA key sitting out there. I would like to construct a PSCredential object from the current Powershell user (the service account).
Trying [System.Net.CredentialCache]::DefaultNetworkCredentials shows a blank, and [System.Security.Principal.WindowsIdentity]::GetCurrent() doesn't provide the object or information I need.
Does anyone have a method for creating a PSCredential object from the current user? Or maybe a completely different alternative for this problem?
Many thanks!
The Windows API will not expose the information you need, which is why Powershell can't get to them. Its an intentional feature of the security subsystem. The only way for this to work is for the Linux machines to trust the calling machine, such as joining them to an Active Directory (or any kerberos setup really).
Aside from that, you'd need to store and pass this information somehow.
You could store the RSA key in the user's keystore and extract it at runtime (using the .NET Crypto/Keystore libs), so you aren't storing the key around with the code. That way the key itself would be protected by the OS and available only when the calling user was authenticated. You'd have one more thing to install, but may be the only way to achieve what you are aiming for.
"Trying [System.Net.CredentialCache]::DefaultNetworkCredentials shows a blank, and [System.Security.Principal.WindowsIdentity]::GetCurrent() doesn't provide the object or information I need."
You already have your answer. I use this to pass the currently logged in user's credentials along in several scripts:
$Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
$Username = $Credentials.UserName
$Password = $Credentials.Password
If you try to dump them to any kind of readable output, those values are empty when you dump them (for obvious security reasons), however they do work where you need a PSCredential object.
How about encrypting the password using the service account's encryption key?
A quick example:
Run PowerShell as the service account, run the following and save the output to a text file (or embed it in the scheduled task call):
$String = '<PASSWORD>'
ConvertFrom-SecureString -SecureString (ConvertTo-SecureString -String $String -AsPlainText -Force)
Use the following in your scheduled task in order to decrypt and utilize the password:
$EncryptedString = '<ENCRYPTED PASSWORD FROM ABOVE>'
[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR((ConvertTo-SecureString -String $EncryptedString)))
That should do the trick. You cannot reuse the encrypted password on a different computer, though, or if you for whatever reason destroy you local key store :)
Since you can get the password in plaintext from a credential object, I doubt you can get this without prompting.