PSCredential Object Secure?
One of the things I really love about Powershell is the Secure Credential object, or PSCredential object. I even wrote about using it here and here. But a recent script request over at Spiceworks has shed some light on the PSCredential and now I’m not so sure I like it anymore! Read on.
Update – January 22, 2013
Just wanted to update this as I ran across a script awhile ago that solves one of the problems with PSCredential. When saving to a file as a secure-string the file can only be decrypted by the same computer. So the PSCredential is only good for that computer–which from a security standpoint may be a good thing. I did run across a script over at Poshcode.org that will save the file independently of the computer which I think is a pretty cool feature. The author is Daniel and the script is Save-Credentials. Anyway, read on to see why I was so disappointed with the PSCredential object.
In this case, we have a user with 2 domains. They are migrating from the old domain (call it Domain A) over to a new one (Domain B). Users are still logging into A, but now need to map some drives to servers over on B. Usernames are the same on both sides, but the passwords are different.
Now, normally, you would simply grant access to the resources on B to the user objects in A and things will map over with no problem (assuming a trust exists, which in this case it does). In fact, this would be the preferred method of doing this kind of thing, but in this case due to the complexity of permissions it’s not something the OP can do. Next plan was to just use the NET USE command built into DOS and do it, but the OP doesn’t want a prompt every time, and doesn’t want to put the password in plain text–something I am completely on board with!
WMI and Mapping Drives
The next problem comes with how you map a drive from Powershell. There is no cmdlet for mapping drives in Powershell, though you could probably use NET USE if you wanted to. Typically though, we will want to use WMI and the Win32_MappedLogicalDisk class. More problems though, Win32_MappedLogicalDisk doesn’t actually allow you to map a drive, but we can use it to interrogate the drives that are already mapped. We do this to check to see if the drive is already mapped, and if so we don’t bother doing it again.
So to map the drive we need to use a COM object, WScript.Network to be specific. If you’ve ever written a login script in vbScript you are very familiar with all of these concepts.
The rest of the script is pretty straight forward. Use the WMI class to enumerate all of the currently mapped drives and loop through them to see if I get a match. If I do get a match then don’t do anything. If I get a match on the drive letter, but the mapping is different then remove the drive mapping to make room for the new one. Then I modified my GetCredentials function to accept the domain and username for the new domain and the Get-Credential cmdlet to get the password and save it all to a file.
Then it gets weird. The problem with the old WMI classes and most COM objects is they are old technology that have not been designed to work with the new Powershell PSCredential object. Here’s the syntax for mapping a drive with WScript.Network:
$Net = New-Object -COMObject Wscript.Network $Net.MapNetworkDrive([string]driveletter,[string]share,[bool]persistent,[string]username,[string]password)
The “catch” here is the password parameter, notice it only accepts a string value. This is bad, and I started thinking I wasn’t going to be able to solve this problem using Powershell–at least not without my own lame encryption scheme! But a little research quickly turned up this cool script by mtown_nerd on Poshcode. He went through a lot more complexity then I wanted to put into this script but it clearly showed you could do this using the COM object.
I had to see it for myself, so I wrote a little test script:
$Cred = Get-Credential $Cred.Password $Cred.GetNetworkCredential().Password
As you can see, the results are a bit disturbing. Looking at the $Cred.Password property returns what I would expect, but when you use the GetNetworkCredentials() method you actually get the password in plain text.
I have to admit, I simply made an assumption that when you put a password into the PSCredential object it would be encrypted/hashed and you would not be able to pull it out again. So saving it to a file would be perfectly safe, but it turns out that’s not the case. I decided to test this on one of the files my GetCredentials function creates.
$Password = Get-Content c:\utils\Credentials-mpugh.crd | ConvertTo-SecureString $Credential = New-Object System.Management.Automation.PsCredential("mpugh",$Password) $Credential.GetNetworkCredential().Password
Sure enough, there’s my password bright as day–think I’ll spare you and me the hassle of a screen shot for that!
It’s pretty disappointing that one of the things I was most excited about turns out to not be what I thought it was. It’s certainly more secure then anything vbScript can do so it’s nice not have a password stored in plain text in the script but it’s simply not as secure as I wish it was. I wouldn’t even mind if the GetNetworkCredential() method only returned the password when it’s called but blocked storage into a variable or display on screen–though how you would pull that off I haven’t a clue.
Does this kill the validity of the GetCredentials function? I guess that depends. If you’re in a high security environment that requires absolute security on the passwords then yes, this is not a procedure that will work at all. Some other method of password storage will need to be done. If you’re a SMB where you just want the password to be difficult to see–say you don’t want that guy who thinks he knows what he’s doing opening up a script file and seeing your administrator password–then the function will still work for you.
At least I think I was able to help the OP solve his problem, and I learned something about PSCredential so it’s not the worst day ever. But I’m still pretty bummed.