37

Suppose that I were using sha1pass to generate a hash of some sensitive password on the command line. I can use sha1pass mysecret to generate a hash of mysecret but this has the disadvantage that mysecret is now in the bash history. Is there a way to accomplish the end goal of this command while avoiding revealing mysecret in plain text, perhaps by using a passwd-style prompt?

I'm also interested in a generalized way to do this for passing sensitive data to any command. The method would change when the sensitive data is passed as an argument (such as in sha1pass) or on STDIN to some command.

Is there a way to accomplish this?


Edit: This question attracted a lot of attention and there have been several good answers offered below. A summary is:

  • As per @Kusalananda's answer, ideally one would never have to give a password or secret as a command-line argument to a utility. This is vulnerable in several ways as described by him, and one should use a better-designed utility that is capable of taking the secret input on STDIN
  • @vfbsilva's answer describes how to prevent things from being stored in bash history
  • @Jonathan's answer describes a perfectly good method for accomplishing this as long as the program can take its secret data on STDIN. As such, I've decided to accept this answer. sha1pass in my OP was just an example, but the discussion has established that better tools exist that do take data on STDIN.
  • as @R.. notes in his answer, use of command expansion on a variable is not safe.

So, in summary, I've accepted @Jonathan's answer since it's the best solution given that you have a well-designed and well-behaved program to work with. Though passing a password or secret as a command-line argument is fundamentally unsafe, the other answers provide ways of mitigating the simple security concerns.

  • 6
    Not only that: Anybody on the same machine with permission to list running processes can potentially see that sha1pass mysecret is running, and hence know what mysecret is. (This only works for the few seconds while the program is actually running, of course...) – MathematicalOrchid Apr 23 '18 at 15:35
  • @MathematicalOrchid This could be avoided by running in a private virtual machine. But that may be too much work to set up to generate only a single password... :-) – Kusalananda Apr 23 '18 at 15:41
  • 5
    @Kusalananda My point was more "don't ever put sensitive data on the command line, even if you figure out how to turn off command history"... – MathematicalOrchid Apr 23 '18 at 16:14
  • 2
    Just so you know, SHA-1 is deprecated for key or password hashes since quite a few years now. – David Foerster Apr 23 '18 at 21:20
  • 2
    Note that if the system is running an audit daemon, all commands and all arguments by all users are logged centrally by root, so anyone who can access those logs will see this. – Randall Apr 24 '18 at 9:57
17

If using the zsh or bash shell, use the -s option to the read shell builtin to read a line from the terminal device without it echoing it.

IFS= read -rs VARIABLE < /dev/tty

Then you can use some fancy redirection to use the variable as stdin.

sha1pass <<<"$VARIABLE"

If anyone runs ps, all they'll see is "sha1pass".

That assumes that sha1pass reads the password from stdin (on one line, ignoring the line delimiter) when not given any argument.

  • I believe that we established that sha1pass does not use its standard input stream. – Kusalananda Apr 23 '18 at 19:47
  • @Kusalananda Okay, but this method will work for programs that do read from stdin. – Jonathan Apr 24 '18 at 17:25
  • So, assuming the utility can take its secret input on STDIN, this is a sound and safe solution, security-wise? – cemulate Apr 24 '18 at 20:50
  • I've decided to accept this answer, given that it's the best solution given that you have a well-designed program to work with. sha1pass was just an example in my OP, and clearly not the best choice to use for this. – cemulate Apr 24 '18 at 21:00
  • 1
    @cemulate, ...not safe on a command line. In the context of a heredoc or herestring (as in the answer here), it's not exposed to ps, but may be written to a temporary file -- if one wants to avoid that, then sshpass < <(printf '%s\n' "$VARIABLE") might be considered (because printf is a builtin, not an external command, it isn't passed to execv and accessible via ps). – Charles Duffy Apr 25 '18 at 17:51
35

Ideally, you never type a clear-text password on the command line as an argument to a command. Doing so makes the password an argument to the command, and command line arguments may be seen in the process table by using simple tools like ps or logged into some audit logs.

Having said that, there are certainly ways of hiding the actual password from the shell's command history.

sha1pass "$( head -n 1 )"

Then type in the password and press Enter. The head command used here accept exactly one line of input and the last newline that you type will not be part of the data that is passed to sha1pass.

To prevent the characters from echoing:

sha1pass "$( stty -echo; head -n 1; stty echo )"

The stty -echo command turns off echoing of the typed characters on the terminal. The echoing is then restored with stty echo.

To pass on standard input, that last command could be altered (you would have done this if sha1pass accepted data on standard input, but appears as if this particular utility is ignoring its standard input):

{ stty -echo; head -n 1; stty echo; } | somecommand

If you need multi-line input (the above assumes a single line should be passed, with no newline character at the end), then replace the whole head command with cat and terminate the input (assuming somecommand itself reads until end-of-file) with Ctrl+D (following Return if you want to include a newline character in the input, or twice if not).

This would work regardless of what shell you were using (as long as it was a Bourne-like or rc-like shell).

Some shells may be made to not save the typed-in commands in their history files if the command is preceded by a space. This usually involves having to set HISTCONTROL to the value ignorespace. This is supported by at least bash and ksh on OpenBSD, but not by e.g. ksh93 or dash. zsh users may use the histignorespace option or their HISTORY_IGNORE variable to define a pattern to ignore.

In shells that support reading with read without echoing characters to the terminal, you may also use

IFS= read -rs password     # -s turns off echoing in bash or zsh
                           # -r for reading backslashes as-is,
                           # IFS= to preserve leading and trailing blanks
sha1pass "$password"

but this obviously still has the same issue with potentially revealing the password in the process table.

If the utility reads from standard input, and if the shell supports "here-strings", the above could be changed to

IFS= read -rs password
somecommand <<<"$password"

Summary of comments below:

  • Executing a command with a password given on the command line, which all of the commands above does, except the one that pipes the data to the command, will potentially make the password visible to anyone running ps at the same time. None of the commands above will however save the typed-in password in the shell's history file if executed from an interactive shell.

  • Well behaved programs that reads clear-text passwords do so by reading from their standard input, from a file, or directly from the terminal.

  • sha1pass does requires the password on the command line, either typed in directly or using some form of command substitution.

  • If at all possible, use another tool.

  • 11
    This is incorrect. The result of command expansion $(...) will be part of the command line and visible in ps output except on a system with hardened /proc. – R.. Apr 23 '18 at 15:42
  • 3
    @Kusalananda, even overwriting the argv after you started leaves a vulnerable window before that overwrite takes place; it's a mitigation, not a fix. – Charles Duffy Apr 23 '18 at 15:45
  • 5
    @Kusalananda, no well-designed program for hashing passwords will require input on the command line, so that conditional is basically a given -- if it's not, one should choose a different tool. – Charles Duffy Apr 23 '18 at 15:54
  • 4
    @CharlesDuffy: The tool here seems fundamentally broken. sha1pass run with no password on the command line seems to produce an output without reading anything... So OP needs to choose a different tool. – R.. Apr 23 '18 at 15:59
  • 8
    This answer is unsafe from a security perspective, and the only advantage is that the password doesn't show up in the shell history, but that advantage is easier to accomplish by including a space in front of the command – Ferrybig Apr 23 '18 at 17:06
25

If you set HISTCONTROL like so:

HISTCONTROL=ignorespace

and start the command with a space:

~$  mycommand

it won't be stored in the history.

10

Pass sensitive data via a pipe or here-doc:

command_with_secret_output | command_with_secret_input

or:

command_with_secret_input <<EOF
$secret
EOF

It's fine for secrets to be in (non-exported) shell variables, but you can never use those variables on command lines, only in here-documents and shell internals.

As noted by Kusalananda in a comment, if you're entering commands in an interactive shell, the lines you enter for a here document will be stored in the shell history, so it's not safe to type a password there, but it should still be safe to use shell variables containing secrets; the history will contain the text $secret rather than whatever $secret expanded to.

Use of command expansions is not safe:

command_with_secret_input "$(command_with_secret_output)"

because the output will be included on the command line and visible in ps output (or manually reading from /proc) except on systems with hardened /proc.

Assignment to a variable is okay too:

secret=$(command_with_secret_output)
  • What I'm looking for is an actual command command_with_secret_output that allows me to type the secret output. Does such a command exist that could be substituted here? – cemulate Apr 23 '18 at 15:50
  • Note that typing a here-document in an interactive bash session will save the document in the history file. – Kusalananda Apr 23 '18 at 15:50
  • @cemulate: Just use the shell builtin read, e.g. read -r secret. Then your secret is in $secret. You can run stty before/after it to hide the input if you like. – R.. Apr 23 '18 at 15:58
  • 3
    @Kusalananda: sha1pass seems fundamentally broken/unusable/unsafe since it can't read the password from stdin or a file. A different utility is needed to solve the problem, I think. – R.. Apr 23 '18 at 16:03
  • 1
    @cemulate Your last comment to R is exactly on the spot. That's why it won't help. read -rs will do the job (if you include -r to be able to have passwords with backslashes in them), but then you're back at potentially showing off the password in the process list (and depending on whether process accounting is turned on and how that's configured, in the process accounting logs as well). – Kusalananda Apr 24 '18 at 6:31
6

Just write the value into a file and pass the file:

$ cat > mysecret
Big seecreeeet!
$ cat mysecret | sha1pass 

I'm not sure how sha1pass works, if it can take a file as input, you can use sha1pass < mysecret. If not, using cat might be a problem since it includes the final newline. If that's the case, use (if your head supports -c):

head -c-1 mysecret | sha1pass 
  • 2
    Why write the password to disk in your first example when you can just do cat | sha1pass? cat mysecret | sha1pass and sha1pass < mysecret have the same behaviour with respect to final newlines. cat doesn't add newlines. Anyway, if sha1pass supports passing the password through standard input, I would expect it to ignore the final newline by itself. Files with lines that aren't terminated by newlines are unnatural in unix after all, so it'd be awkward to expect a file that is not terminated by a newline. – JoL Apr 23 '18 at 16:07
  • @JoL i) because I hadn't thought of that :/ But how would that work? cat | sha1pass seems to run sha1pass before giving me a chance to enter any input. ii) No, of course cat mysecret doesn't add a newline, I never said cat adds it, only that it includes it. – terdon Apr 23 '18 at 16:20
  • I see, but it's not like < mysecret removes it either. :) – JoL Apr 23 '18 at 16:24
  • Nevermind. Now that I re-read, I see that you said that to later propose head -c-1. I guess I just got confused as to why use cat mysecret | sha1pass and then later propose sha1pass < mysecret. I guess the worry is that sha1pass might complain about regular files for stdin; I'm not sure why. – JoL Apr 23 '18 at 16:31
  • @JoL it's more because I've never used sha1pass and couldn't find a manual or -h or any other form of documentation in the few minutes I spent searching and so couldn't figure out if running sha1pass foo treated foo as an input string or as an input file. I therefore gave options to deal with each possibility. – terdon Apr 23 '18 at 16:36
1

If what terdon did is possible, then that's the best solution, passing through standard input. The only problem left is he wrote the password to disk. We can do this instead:

stty -echo
echo -n "password: "
head -1 | sha1pass
stty echo

Like Kusalananda said, stty -echo ensures what you type is not seen until you do stty echo again. head -1 will get one line from standard input and pass it on to sha1pass.

0

I would use

sha1pass "$(cat)"

cat would read from stdin until EOF, which can be caused by pressing Ctrl+D. Then, the result would be passed as argument to sha1pass

  • 3
    R..'s answer explains why this is unsafe. – hvd Apr 25 '18 at 12:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.