What is most secure and simplest way to have a user-typed password on bash become part of stdin to a program?

9,924

Solution 1

With bash or zsh:

unset -v password # make sure it's not exported
set +o allexport  # make sure variables are not automatically exported
IFS= read -rs password < /dev/tty &&
  printf '{"username":"myname","password":"%s"}\n' "$password" | cmd

Without IFS=, read would strip leading and trailing blanks from the password you type.

Without -r, it would process backslashes as a quoting character.

You want to make sure you only ever read from the terminal.

echo can't be used reliably. In bash and zsh, printf is builtin so that command line wouldn't show in the output of ps.

In bash, you need to quote $password as otherwise the split+glob operator is applied to it.

That's still wrong though as you'd need to encode that string as JSON. For instance, double-quote and backslash at least would be a problem. You probably need to worry about the encoding of those characters. Does your program expect UTF-8 strings? What does your terminal send?

To add a prompt string, with zsh:

IFS= read -rs 'password?Please enter a password: '

With bash:

IFS= read -rsp 'Please enter a password: ' password

Solution 2

Here's the solution I have (it's been tested):

$ read -s PASSWORD
<user types password>
$ echo -n $PASSWORD | perl -e '$_=<>; print "{\"username\":\"myname\",\"password\":\"$_\"}"'

Explanation:

  1. read -s reads a line of stdin (the password) without echoing the line to the screen. It stores is in the shell variable PASSWORD.
  2. echo -n $PASSWORD puts the password on stdout without any newline. (echo is a shell built-in command so no new process is created so (AFAIK) the password as an argument to echo will not be shown on ps.)
  3. perl is invoked and reads the password from stdin to $_
  4. perl puts the password in $_ into the full text and prints it to stdout

If this is going to a HTTPS POST, the second line would be something like:

echo -n $PASSWORD | perl -e '$_=<>; print "{\"username\":\"myname\",\"password\":\"$_\"}"' | curl -H "Content-Type: application/json" -d@- -X POST <url-to-post-to>

Solution 3

If your version of read doesn't support -s you try the POSIX compatible way seen in this answer.

echo -n "USERNAME: "; read uname
echo -n "PASSWORD: "; set +a; stty -echo; read passwd; command <<<"$passwd"; set -a; stty echo; echo
passwd= # get rid of passwd possibly only necessary if running outside of a script

The set +a is supposed to prevent auto "export" of a variable to the environment. You should check out the man pages for stty, there are lots of options available. The <<<"$passwd" is quoted because good passwords can have spaces. The last echo after enabling the stty echo is to have the next command/output start on a new line.

Share:
9,924

Related videos on Youtube

Jim Hoagland
Author by

Jim Hoagland

Updated on September 18, 2022

Comments

  • Jim Hoagland
    Jim Hoagland almost 2 years

    I'm looking for the (1) most secure and (2) simplest way to have a user type a password on a bash shell prompt and to have that password become part of stdin to a program.

    This is what the stdin needs to look like: {"username":"myname","password":"<my-password>"}, where <my-password> is what was is typed into the shell prompt. If I had control over the the program the stdin, then I could modify it to securely prompt for a password and put it into place, but the downstream is a standard general purpose command.

    I have considered and rejected approaches that use the following:

    • the user typing the password into the command line: the password would be shown on the screen and would also visible to all users via "ps"
    • shell variable interpolation into a argument to an external program (e.g., ...$PASSWORD...): the password would still be visible to all users via "ps"
    • environment variables (if they are left in the environment): the password would be visible to all child processes; even trustworthy processes might expose the password if they dump core or dump environment variables as part of a diagnostic
    • the password sitting in a file for an extended period of time, even a file with tight permissions: the user may accidentally expose the password and the root user might accidentally see the password

    I'll put my current solution as an answer below, but will happily select a better answer if someone comes up with one. I'm thinking there should be something simpler or maybe someone sees a security concern that I have missed.

  • Tom Hunt
    Tom Hunt almost 9 years
    This looks pretty good. I'll just note there's no reason to invoke perl at all; you can perfectly well do something like printf '{"username":"myname","password":"%s"}' $PASSWORD, which retains the same security properties and saves you an external process.
  • Jeff Schaller
    Jeff Schaller almost 9 years
    If you'd like to generalize the solution to read commands that don't support the -s flag, you can use "stty -echo; read PASSWORD; stty echo"
  • Angel Todorov
    Angel Todorov almost 9 years
    A further level of obfuscation: don't provide a tell-tale variable name like "PASSWORD" and use the default variable name "REPLY": read -s; printf "{...}" "$REPLY" -- make sure you quote your variable "$PASSWORD" or "$REPLY" (you don't know what the user will type and you need to avoid word splitting or filename generation).
  • chepner
    chepner almost 9 years
    The pipe starts two new processes, one for each side. Use a here-string instead: perl -e '...' <<< "$PASSWORD".
  • Stéphane Chazelas
    Stéphane Chazelas almost 9 years
    @chepner, <<< in bash stores the content in a temporary file which is worse.
  • chepner
    chepner almost 9 years
    Oof. I had always assumed that here-strings were handled in-memory.
  • Jim Hoagland
    Jim Hoagland almost 9 years
    printf - that's what we need to avoid the perl invocation - good tip. It is good that you have the unset password and set +a there, though it can be skipped if you can make more assumptions about current shell. Good point about the -r option to read and putting IFS= ahead of it for better generality with a variety of passwords. Good to go from /dev/tty to force input from the terminal.
  • Jim Hoagland
    Jim Hoagland almost 9 years
    yes, we still have a problem with double-quote and backslash and maybe a couple other chars. We would need to encode those before putting then inside the double-quoted field in the JSON blob. Not sure if curl expects UTF-8.
  • Jim Hoagland
    Jim Hoagland almost 9 years
    Kevin, that would be a good suggestion if we can get the password securely into stdin and don't mind invoking python. This constructs the JSON on the fly. This would work well if use getpass.getpass() inside Python, though you lose being able to keep using the stored password repeatedly.
  • dragon788
    dragon788 almost 7 years
    According to TLDP it does create a file but it isn't readable by any other processes. "Here documents create temporary files, but these files are deleted after opening and are not accessible to any other process." tldp.org/LDP/abs/html/here-docs.html just after Example 19-12.