Powershell task: Hide not the output but the actual command containing sensitive info in devops logs

Viewed 310

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.

2 Answers

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.

Related