Identifying Compromised Passwords (Part 2)

by May 18, 2020

When you want to submit sensitive information to a PowerShell function, you typically use the SecureString type. This type makes sure a user gets prompted with a masked dialog box, and the input is protected from anyone “looking over the shoulder”.

Since SecureString can always be decrypted to plain text by the person who created the SecureString, you can take advantage of masked input boxes but still work with the entered plain text:

function Test-Password
{
  [CmdletBinding()]
  param
  (
    [Parameter(Mandatory, Position=0)]
    [System.Security.SecureString]
    $Password
  )
  
  # take a SecureString and get the entered plain text password
  # we are using a SecureString only to get a masked input box
  $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
  $plain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
    
  "You entered: $plain"
}

When you run the code and then run Test-Password, you get prompted with a masked input. Inside the function, the submitted SecureString is decrypted to a plain text.

However, this approach has one significant drawback: if you plan to submit the information via a parameter, you now have to submit a SecureString. You can no longer submit plain text:

 
# fails:
PS> Test-Password -Password test
Test-Password : Cannot process argument transformation on parameter 'Password'. Cannot convert the "test" value of type "System.String" to type "System.Security.SecureString".

# works
PS> Test-Password -Password ("test" | ConvertTo-SecureString -AsPlainText -Force)
You entered: test 
 

With a custom attribute, you can add the automatic capability to any parameter to auto-convert plain text to SecureString, though:

# create a transform attribute that transforms plain text to a SecureString
class SecureStringTransformAttribute : System.Management.Automation.ArgumentTransformationAttribute
{
  [object] Transform([System.Management.Automation.EngineIntrinsics]$engineIntrinsics, [object] $inputData)
  { if ($inputData -is [SecureString]) { return $inputData }
      elseif ($inputData -is [string]) { return $inputData | ConvertTo-SecureString -AsPlainText -Force }
    throw "Unexpected Error."
  }
}

function Test-Password
{
  [CmdletBinding()]
  param
  (
    [Parameter(Mandatory, Position=0)]
    [System.Security.SecureString]
    [SecureStringTransform()]
    $Password
  )
  
  # take a SecureString and get the entered plain text password
  # we are using a SecureString only to get a masked input box
  $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
  $plain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
    
  "You entered: $plain"
}

Now the user can run Test-Password without a parameter and gets prompted with a masked dialog. The user can also submit a plain text input directly:

 
# use built-in masked input
PS> Test-Password
cmdlet Test-Password at command pipeline position 1
Supply values for the following parameters:
Password: ******
You entered: secret

# use text-to-SecureString transformation attribute
PS> Test-Password -Password secret
You entered: secret 
 

If you’d like to understand how transformation attributes work, here are the details: https://powershell.one/powershell-internals/attributes/transformation


Twitter This Tip! ReTweet this Tip!