8

Imagine that we have a program or script that can take a password (or other sensitive information) argument:

> program.exe /password:secret

For Linux, best practice generally recommends against specifying the password directly on the command-line because of potential security concerns (the password can appear in the shell's history file and the system's process table):

$ ./program.sh --password 'secret' &
[1] 4152

$ cat /proc/4152/cmdline 
/bin/sh./program.sh--passwordsecret

However, when searching around, I don't see the same vigorous recommendation for Windows.

When writing programs and scripts for Windows, should we provide an alternate means to input a password besides an argument to a command-line option to avoid unintentionally exposing the password?

The answer to this question suggests that a dedicated password input prompt improves security by preventing shoulder-surfing. Are there any other ways to exploit a visible password in a command string that justifies writing alternate input methods, both in the context of a shell and in the way Windows manages processes?

Do the security implications change when starting a program from a batch file or PowerShell script that passes a password as an argument to the program?

$password = # prompt for password 
program.exe /password:$password

Edit - I understand that we can misinterpret this question as opinion-based because I mention "best practices" and "recommendations" to provide background. However, I seek specific evidence that demonstrates how a password might be vulnerable in a command argument and—if this assumption is valid—concrete examples or references that depict secure alternatives.

Cy Rossignol
  • 16,216
  • 4
  • 57
  • 83
  • 4
    Your plaintext password will show up in `Show-History` or using up/down arrows in the session. The 'best practice' is using DPAPI (securestring) or relying on authentication from Windows. – Maximilian Burszley Oct 31 '17 at 17:59

2 Answers2

11

Windows historically didn't save command history between sessions, only within the session. This was true for the command prompt and for PowerShell.

As Bill Stewart pointed out, Windows PowerShell on Windows 10 and Windows 2016 includes PSReadline by default, which does save your command history between sessions. You can see this by looking at the file here: (Get-PSReadLineOption).HistorySavePath.

But even if it's set to off, or on an OS version that didn't offer the option, that doesn't mean entering a plaintext password as an argument is a good idea.

If you must offer that, you should also have a way to have the program prompt at run time.

For PowerShell and other .Net applications, you have another issue with accepting plaintext passwords: they linger in memory and there's no good way to explicitly clear them.

This issue is two-fold: strings are immutable in .Net, which means you cannot just modify the string with nulls or random characters to clear it in memory (you will actually be creating a brand new string), and on top of that you cannot control when a specific object will handled by garbage collection, so you can't explicitly remove it.

This is why the SecureString class exists, but not everything can use this.

In PowerShell, there is a PSCredential object which stores a user name in plain text and a password as a SecureString. This should always be used in PowerShell, and should be the preferred argument type (in lieu of a separate user name and password).

Most commands in PowerShell that require a credential take it as this type of object.

You can retrieve a plaintext version of the password easily with this object as well. Doing so then puts that into a managed string and you get the risks I mentioned above.

In my opinion though, it is still preferable to use a PSCredential object in these situations, right up until the point you need the plaintext version. It helps to maintain the standardization of this type in both a built-in/'official' capacity, as well as in user-defined commands.

This type is also easily serializable with Export-Clixml into a form that is encrypted. This can give you a really nice way of providing an automated option to use stored credentials in scripts, with nothing in plaintext, and no prompting or user intervention required.

briantist
  • 45,546
  • 6
  • 82
  • 127
  • 5
    "Windows doesn't save command history between sessions" - FYI this is no longer the case if one uses the PowerShell PSReadline module which stores a command history file (and PSReadline is installed and active by default on Windows 10/Server 2016 and presumably newer). – Bill_Stewart Oct 31 '17 at 18:35
  • @Bill_Stewart that's a great point, but I don't think PSReadline is configured to do so by default; at least on my Windows 10 and 2016 machines, my command history is not saved between sessions. I wonder if that default was changed in an update to those OS's, but upgrading in place preserved the original value? If you have some references on that I'd love to include them in the answer (also feel free to edit). – briantist Oct 31 '17 at 18:50
  • 4
    Interesting. Are you sure? Try running `(Get-PSReadLineOption).HistorySavePath`. – Bill_Stewart Oct 31 '17 at 19:00
  • 4
    @Bill_Stewart thanks for that. The path is set, the file is there, `.HistorySaveStyle` is set to `SaveIncrementally`, and the file contains history. When I closed all my PowerShell windows and reopened one, it did indeed have some history, but it doesn't go back very far at all. A day or two at most. Also the history doesn't show with `h` so that had me confused. Anyway, I should edit this stuff in. – briantist Oct 31 '17 at 19:07
  • Thanks, Brian, great info. At least from inside scripts, it seems like PowerShell doesn't keep history of commands executed as script statements, which makes sense. Assuming that a script can securely prompt for and store a password using methods described above, is there a concern, perhaps in the way Windows starts processes, if the script passes that password as a command-line argument to another script or program? – Cy Rossignol Oct 31 '17 at 21:04
  • That depends on what the receiving program does with the plain-text password string it receives on its command line. – Bill_Stewart Oct 31 '17 at 21:24
  • @Bill_Stewart - Agreed. Assuming the receiving program handles the sensitive data appropriately, I guess I should have asked, _"is the **transfer** of the password from the first program to the second using a command argument secure?"_ In *nixes, the answer is often "no" -- other users and programs can see the argument in the proc fs. I'm not sure how to investigate this behavior in Windows. – Cy Rossignol Oct 31 '17 at 21:42
  • 1
    The answer is no, because if the password is plaintext on a command line, then by definition it's plaintext in memory at some point. If the sending and receiving program are both diligent about overwriting the plain-text string in their process memory _quickly_, then this vulnerability is probably pretty small. – Bill_Stewart Oct 31 '17 at 22:16
  • 1
    What @Bill_Stewart said, with the caveat that in managed memory (in .Net), you _cannot_ overwrite the plain-text string quickly (or reliably). If neither the sending nor receiving program are managed applications, then they could possibly do that. In theory the managed applications could also store the string in non-managed memory, and perhaps p/invoke the API to start a process.. – briantist Nov 01 '17 at 23:00
  • 2
    And to add to what Brian said: I imagine this was part of the impetus for creating `SecureString` in the first place. – Bill_Stewart Nov 01 '17 at 23:40
9

To more directly answer my question, here's a small supplement to briantist's excellent answer:

When security is a concern, do not input or request passwords or other sensitive data as command-line arguments. Other users and programs can snoop on command-line arguments fairly trivially.

We can use Windows Management Instrumentation to pull command-line arguments in PowerShell and through other programs like those built on .NET. Here's an example using PowerShell:

PS> Get-WmiObject Win32_Process -Filter 'Name = "program.exe"' | Select-Object CommandLine

C:\program.exe /password:secret

The command-line password argument is also visible in the Task Manager for any users on the same system that can see processes from other users:

Screenshot of Task Manager showing a plain-text password argument

As briantist wrote in his answer, programs and scripts that we write should provide alternate means to input sensitive information besides command-line arguments. PowerShell scripts can securely pass this information between scripts using PSCredential or SecureString objects.

Cy Rossignol
  • 16,216
  • 4
  • 57
  • 83