After updating to powershell 7 converting secure strings to unsecure plaintext passwords is broken
$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)
[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
returning FooBar
[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
returns only F
$APIKey = ConvertTo-SecureString -AsPlainText -Force -String "FooBar"
Write-Host -Message ([System.Net.NetworkCredential]::new("", $APIKey).Password)
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
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.
GitHubRoslyn 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:
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 OverflowI 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
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 flawedPtrToStringAuto
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.