在bash上使用户键入的密码成为程序的stdin的最安全和最简单的方法是什么?


12

我正在寻找(1)最安全和(2)最简单的方法,让用户在bash shell提示符下键入密码,并使该密码成为程序stdin的一部分。

这是stdin需要的样子:{"username":"myname","password":"<my-password>"},在<my-password>shell提示中键入的内容在哪里。如果我可以控制stdin程序,则可以对其进行修改以安全地提示输入密码并将其放置到位,但是下游命令是标准的通用命令。

我已经考虑并拒绝了使用以下方法:

  • 用户在命令行中输入密码的密码:该密码将显示在屏幕上,并且也将通过“ ps”显示给所有用户
  • shell变量插值到外部程序的参数中(例如...$PASSWORD...):通过“ ps”,所有用户仍然可以看到密码
  • 环境变量(如果它们留在环境中):密码对所有子进程都是可见的;如果可信赖的进程在诊断中转储核心或转储环境变量,它们甚至可能会暴露密码。
  • 密码在文件中放置的时间过长,甚至是权限很严格的文件:用户可能会意外泄露密码,而root用户可能会意外看到密码

我在下面将我当前的解决方案作为答案,但是如果有人提出一个答案,我会很乐意选择一个更好的答案。我认为应该有一些简单的方法,或者可能有人看到我错过的安全问题。


(完整的用例是,密码需要是到HTTPS服务器的POST正文的一部分,但是我不想让这个问题过于具体。stdin通过curl“ -d @成为POST正文。 -”。)
Jim Hoagland 2015年

Answers:


14

bashzsh

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

如果不使用IFS=read则会从您输入的密码中删除开头和结尾的空格。

如果不使用-r,它将把反斜杠作为引号处理。

您要确保只从终端读取。

echo不能可靠地使用。在bashzshprintf是内置的,因此命令行不会显示在的输出中ps

在中bash,您需要引用$password,否则将对其应用split + glob运算符。

但这仍然是错误的,因为您需要将该字符串编码为JSON。例如,至少双引号和反斜杠将是一个问题。您可能需要担心这些字符的编码。您的程序需要UTF-8字符串吗?您的终端发送什么?

要添加提示字符串,请使用zsh

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

bash

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

printf-这是我们避免perl调用所需要的-小技巧。最好有unset passwordand set +a,尽管可以对当前shell进行更多假设,但可以跳过。有关使用-r选项进行读取并IFS=使其具有各种密码更好地通用性的要点。可以从/ dev / tty强制从终端输入。
Jim Hoagland

1
是的,我们仍然对双引号和反斜杠以及其他几个字符有疑问。我们需要先对它们进行编码,然后再将其放入JSON blob中的双引号字段。不知道curl是否需要UTF-8。
Jim Hoagland

1
python3 -c 'import json; print(json.dumps({"username": "myname", "password": input()}))',如果您周围有Python。对于Python 2,s / input / raw_input /。如果将密码传递给该命令,它将吐出适当的JSON。
凯文(Kevin)

凯文(Kevin),如果我们可以将密码安全地输入到stdin中并且不介意调用python,那将是一个很好的建议。这将动态构造JSON。如果您在Python中使用getpass.getpass(),则将可以很好地工作,尽管您将无法重复使用存储的密码。
吉姆·霍格兰

2

这是我的解决方案(已通过测试):

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

说明:

  1. read -s读取一行标准输入(密码),而不在屏幕上显示该行。它存储在外壳变量PASSWORD中。
  2. echo -n $PASSWORD将密码放在stdout上,不带任何换行符。(echo是shell的内置命令,因此不会创建新进程,因此(AFAIK)作为回显参数的密码将不会在ps上显示。)
  3. 调用perl并从stdin读取密码到 $_
  4. perl将密码$_放入全文中并将其打印到stdout

如果这将用于HTTPS POST,则第二行将类似于:

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

3
看起来不错。我只是注意到根本没有理由调用perl。您可以很好地执行像一样的操作printf '{"username":"myname","password":"%s"}' $PASSWORD,它保留了相同的安全性并节省了外部流程。
汤姆·亨特

2
如果您想将解决方案推广到read不支持-s标志的命令,则可以使用“ stty -echo;读取PASSWORD; stty echo”
Jeff Schaller

2
管道开始两个新过程,每一边一个。请使用here-string代替:perl -e '...' <<< "$PASSWORD"
chepner

2
@chepner,<<<bash存储在临时文件,更糟糕的是内容。
斯特凡Chazelas

1
根据TLDP,它确实创建了文件,但其他任何进程均无法读取。“这里的文档创建了临时文件,但是这些文件在打开后将被删除,并且任何其他过程都无法访问。” 例19-12之后的tldp.org/LDP/abs/html/here-docs.html
dragon788 '17

0

如果您的版本read不支持,-s请尝试使用此答案中所示的POSIX兼容方式。

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

set +a应该阻止自动变量环境的“出口”。您应该查看的手册页stty,其中有许多可用选项。该<<<"$passwd"被引用,因为好的密码可以有空格。echo启用后,最后stty echo一个命令将使下一个命令/输出从新行开始。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.