Powershell: Getting unsecure password of securestring is broken

Created on 12 Mar 2020  路  13Comments  路  Source: PowerShell/PowerShell

After updating to powershell 7 converting secure strings to unsecure plaintext passwords is broken

Steps to reproduce

$APIKey = ConvertTo-SecureString -AsPlainText -Force -String "FooBar"
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($APIKey)
Write-Host -Message ([System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR))
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)

Expected behavior

[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

returning FooBar

Actual behavior

[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

returns only F

Workaround

$APIKey = ConvertTo-SecureString -AsPlainText -Force -String "FooBar"
Write-Host -Message ([System.Net.NetworkCredential]::new("", $APIKey).Password)

Environment data


Name                           Value
----                           -----
PSVersion                      7.0.0
PSEdition                      Core
GitCommitId                    7.0.0
OS                             Linux 5.5.8-arch1-1 #1 SMP PREEMPT Fri, 06 Mar 2020 00:57:33 +0000
Platform                       Unix
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0鈥
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Issue-Question Resolution-Answered

Most helpful comment

@vexx32, actually, there was a change, namely in .NET Core 3.0, but that change was a _bug fix_; in short:

  • PtrToStringAuto was _broken in .NET Core 2.x_, because it incorrectly assumed UTF-16 on Unix platforms too - as a _lucky side effect_ of this bug , the conceptually flawed PtrToStringAuto code _happened to_ work on Unix-like platforms too in PowerShell Core 6.x.

  • .NET Core 3.x, which PowerShell 7 is based on, fixed that - which just so happened to expose the conceptual flaw.

See https://github.com/PowerShell/PowerShell/issues/11953#issuecomment-591572070 for details.

All 13 comments

The code is conceptually flawed: PtrToStringAuto should be PtrToStringBSTR - on Windows, your code happens to work, because the Auto translates to UTF-16, which is what BSTRs are; on Unix-like platforms, it translates to UTF-8, which is why the code breaks.

Note that you should avoid SecureString on Unix-like platforms altogether, because _no encryption_ is used.

Even on Windows its use is no longer recommended.

See https://github.com/dotnet/platform-compat/blob/master/docs/DE0001.md for more.

Your workaround is arguably the better approach to begin with, though note that obtaining a plain-text copy is problematic even on Windows.

GitHub
Roslyn analyzer that finds usages of APIs that will throw PlatformNotSupportedException on certain platforms. - dotnet/platform-compat

It doesn't help that a majority of code written like this will use the Auto method and the pattern is very common in solutions on SO and blog posts all over the place.

But that's because they're all written on Windows, and most of them just copied the solution they found -- @mklement0 is 100% correct. It's unfortunate that the popular solution is the one that only works on WIndows, but I suppose that's just how it goes seeing as how for over a decade PowerShell was a Windows-only tool. 馃檪

@agowa338 You can also use the new built-in functionality to do this:

ConvertFrom-SecureString $APIKey -AsPlainText

I still think this is a breaking change. PtrToStringAuto worked on all platforms on PowerShell version 6.2 (at least). Upgrading to PowerShell 7 broke my code and installing PowerShell 7 replaces version 6, thus forcing me to downgrade by uninstall/install until I could figure out a fix. Switching to PtrToStringUni solved it for me. I probably should have used it from the start, but all examples I found for reverting securestrings to plaintext were using PtrToStringAuto.

@klemmestad it's not a change at all. The method works the same way it always did.

The difference is that it doesn't work _as you expected_, because different operating systems have different default encodings for certain types of memory.

That's not a breaking change, that's just a difference from the underlying platform. PowerShell cannot magically change the .NET method (the documentation for which is here).

If you want to argue .NET Core should change the behaviour at some point, you're more than free to. But I doubt they'd agree. There's nothing wrong with the method, it's just not the appropriate method for the desired task on all platforms.

@vexx32, actually, there was a change, namely in .NET Core 3.0, but that change was a _bug fix_; in short:

  • PtrToStringAuto was _broken in .NET Core 2.x_, because it incorrectly assumed UTF-16 on Unix platforms too - as a _lucky side effect_ of this bug , the conceptually flawed PtrToStringAuto code _happened to_ work on Unix-like platforms too in PowerShell Core 6.x.

  • .NET Core 3.x, which PowerShell 7 is based on, fixed that - which just so happened to expose the conceptual flaw.

See https://github.com/PowerShell/PowerShell/issues/11953#issuecomment-591572070 for details.

Thanks for the clarification @mklement0! 馃檪

@klemmestad, as @vexx32 has stated, it's unfortunate that there are so many flawed examples with PtrtToStringAuto out there.

There was never a good reason to use it, and, similarly, there's no good reason to use PtrToStringUni (though the latter is unlikely to break) - only PtrToStringBSTR should ever have been used.

Going forward, the method suggested by @rkeithhill is the way to go (ConvertFrom-SecureString -AsPlainText), but, to reiterate the warning, on _Unix_ SecureString offers virtually no protection to begin with.

@mklement0, thank you for explaining so clearly! I will use PtrToStringBSTR. I need to support earlier PowerShell versions, so ConvertFrom-SecureString -AsPlainText is out of my reach for now 馃槩

My use case is simple, a password and an API key that are used to authenticate to a SOAP API using BasicHttpsBinding with basic authentication. I suspect my use of SecureString does not add any real security on any platform 馃檲

Any pointers I could use to improve my game would be very welcome.

This is not very performant but it is simple:

03-12 15:43:13 1> $ss = Read-Host -AsSecureString
***********
03-12 15:43:31 2> $cred = [pscredential]::new('a', $ss)
03-12 15:43:58 3> $cred.GetNetworkCredential().Password
open sesame

on Unix SecureString offers virtually no protection to begin with

There are at least two protections provided (indirectly) by using SecureString:

  1. Hiding input as it is typed or pasted onto the console.
  2. Hiding secret text when you output a SecureString to the console.

Whenever Read-Host supports a -MaskInput parameter, that would mitigate #1 to some extent. The better mitigation appears to be the proposal for a SensitiveString in .NET Core where PowerShell could key off that type to provide the two features listed above.

@rkeithhill, yes, but you can cut out the middleman to use what @agowa338's original post shows (I originally missed it too):

$ss = Read-Host -AsSecureString; [Net.NetworkCredential]::new('', $ss).Password

There are at least two protections

Yes, using SecureString is _better_ than dealing with plain-text strings, but I wanted to point out that _no encryption_ is involved on Unix, which is especially problematic if you _persist_ a SecureString instance (by saving the output from ConvertFrom-SecureString to a file / using Export-CliXml with a [pscredential] object), in which case the resulting file can be trivially decoded to reveal its "secret" - see https://stackoverflow.com/a/55797281/45375

the proposal for a SensitiveString in .NET Core

What proposal is that?

Stack Overflow
I wonder if there are any implicit assumptions that I've taken that may make the code malfunction? There is a reason I want to avoid using Import-Clixml cmdlet? Hence, I've developed an alternativ...

Found the reference to SensitiveString and it's not a proposal, just @SteveL-MSFT thinking out loud. Sorry. https://github.com/PowerShell/PowerShell/issues/11067#issuecomment-554143830

Was this page helpful?
0 / 5 - 0 ratings