¿Cuál es la forma más segura y sencilla de hacer que una contraseña escrita por el usuario en bash se convierta en parte de stdin para un programa?

12

Estoy buscando la (1) forma más segura y (2) más simple de hacer que un usuario escriba una contraseña en un indicador de shell bash y que esa contraseña se convierta en parte de stdin para un programa.

Así es como debe verse el stdin:, {"username":"myname","password":"<my-password>"}dónde <my-password>es lo que se escribió en el indicador del shell. Si tuviera control sobre el programa stdin, entonces podría modificarlo para solicitar una contraseña de forma segura y ponerlo en su lugar, pero el downstream es un comando estándar de propósito general.

He considerado y rechazado enfoques que usan lo siguiente:

  • el usuario escribiendo la contraseña en la línea de comando: la contraseña se mostrará en la pantalla y también será visible para todos los usuarios a través de "ps"
  • interpolación variable de shell en un argumento a un programa externo (por ejemplo, ...$PASSWORD...): la contraseña aún sería visible para todos los usuarios a través de "ps"
  • variables de entorno (si se dejan en el entorno): la contraseña sería visible para todos los procesos secundarios; incluso los procesos confiables pueden exponer la contraseña si vuelcan las variables de entorno principales o de volcado como parte de un diagnóstico
  • la contraseña se encuentra en un archivo durante un período prolongado de tiempo, incluso un archivo con permisos estrictos: el usuario puede exponer accidentalmente la contraseña y el usuario raíz puede ver la contraseña accidentalmente

Pondré mi solución actual como respuesta a continuación, pero con mucho gusto seleccionaré una mejor respuesta si a alguien se le ocurre una. Estoy pensando que debería haber algo más simple o tal vez alguien vea un problema de seguridad que me haya perdido.

Jim Hoagland
fuente
(El caso de uso completo es que la contraseña debe formar parte del cuerpo de una POST a un servidor HTTPS, pero no quiero que esta pregunta sea demasiado específica. El stdin se convierte en el cuerpo de la POST a través de curl "-d @ - ".)
Jim Hoagland

Respuestas:

14

Con basho 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

Sin IFS=, readeliminaría los espacios en blanco iniciales y finales de la contraseña que escriba.

Sin -r, procesaría barras invertidas como un carácter entre comillas.

Debes asegurarte de que solo lees desde la terminal.

echono se puede usar de manera confiable. In bashy zsh, printfestá integrado para que la línea de comando no se muestre en la salida de ps.

En bash, debe citar $passwordya que de lo contrario se le aplicará el operador split + glob .

Sin embargo, todavía está mal, ya que necesitarías codificar esa cadena como JSON. Por ejemplo, las comillas dobles y la barra diagonal inversa al menos serían un problema. Probablemente deba preocuparse por la codificación de esos caracteres. ¿Su programa espera cadenas UTF-8? ¿Qué envía tu terminal?

Para agregar una cadena de solicitud, con zsh:

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

Con bash:

IFS= read -rsp 'Please enter a password: ' password
Stéphane Chazelas
fuente
printf - eso es lo que necesitamos para evitar la invocación de perl - buen consejo Es bueno que tenga el unset passwordy set +aallí, aunque se puede omitir si puede hacer más suposiciones sobre el shell actual. Buen punto sobre la opción -r para leer y poner IFS=por delante para una mejor generalidad con una variedad de contraseñas. Es bueno ir desde / dev / tty para forzar la entrada desde la terminal.
Jim Hoagland
1
Sí, todavía tenemos un problema con las comillas dobles y la barra diagonal inversa, y tal vez un par de caracteres más. Tendríamos que codificarlos antes de ponerlos dentro del campo de doble comilla en el blob JSON. No estoy seguro si curl espera UTF-8.
Jim Hoagland
1
python3 -c 'import json; print(json.dumps({"username": "myname", "password": input()}))', si tienes Python por ahí. Para Python 2, s / input / raw_input /. Si canaliza la contraseña en ese comando, escupirá el JSON apropiado.
Kevin
Kevin, sería una buena sugerencia si podemos obtener la contraseña de forma segura en stdin y no nos importa invocar a python. Esto construye el JSON sobre la marcha. Esto funcionaría bien si usa getpass.getpass () dentro de Python, aunque pierde la posibilidad de seguir usando la contraseña almacenada repetidamente.
Jim Hoagland el
2

Aquí está la solución que tengo (ha sido probado):

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

Explicación:

  1. read -slee una línea de stdin (la contraseña) sin hacer eco de la línea en la pantalla. Se almacena en la variable de shell CONTRASEÑA.
  2. echo -n $PASSWORDpone la contraseña en stdout sin ninguna nueva línea. (echo es un comando de shell incorporado, por lo que no se crea ningún proceso nuevo, por lo que (AFAIK) la contraseña como argumento para echo no se mostrará en ps).
  3. se invoca perl y lee la contraseña de stdin a $_
  4. perl pone la contraseña en $_el texto completo y la imprime en stdout

Si esto va a una POST HTTPS, la segunda línea sería algo como:

echo -n $PASSWORD | perl -e '$_=<>; print "{\"username\":\"myname\",\"password\":\"$_\"}"' | curl -H "Content-Type: application/json" -d@- -X POST <url-to-post-to>
Jim Hoagland
fuente
3
Esto se ve bastante bien. Solo notaré que no hay razón para invocar a Perl; puede perfectamente hacer algo como printf '{"username":"myname","password":"%s"}' $PASSWORD, que conserva las mismas propiedades de seguridad y le ahorra un proceso externo.
Tom Hunt
2
Si desea generalizar la solución a los readcomandos que no admiten el indicador -s, puede usar "stty -echo; leer CONTRASEÑA; stty echo"
Jeff Schaller
2
La tubería inicia dos nuevos procesos, uno para cada lado. Utilice una cadena multilínea en su lugar: perl -e '...' <<< "$PASSWORD".
chepner
2
@chepner, <<<en bashalmacena el contenido en un archivo temporal que es peor.
Stéphane Chazelas
1
De acuerdo con TLDP, crea un archivo pero ningún otro proceso puede leerlo. "Aquí los documentos crean archivos temporales, pero estos archivos se eliminan después de abrirlos y no son accesibles para ningún otro proceso". tldp.org/LDP/abs/html/here-docs.html justo después del Ejemplo 19-12.
dragon788
0

Si su versión de readno es compatible -s, pruebe la forma compatible POSIX que se ve en esta respuesta .

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

Se set +asupone que evita la "exportación" automática de una variable al entorno. Debería consultar las páginas de manual para stty, hay muchas opciones disponibles. Se <<<"$passwd"cita porque las buenas contraseñas pueden tener espacios. Lo último echodespués de habilitar stty echoes tener el siguiente comando / salida en una nueva línea.

dragon788
fuente