Quel est le moyen le plus sûr et le plus simple pour qu'un mot de passe saisi par l'utilisateur sur bash fasse partie de stdin dans un programme?

12

Je cherche le (1) moyen le plus sûr et (2) le plus simple pour qu'un utilisateur tape un mot de passe sur une invite shell bash et que ce mot de passe fasse partie de stdin dans un programme.

Voici à quoi doit ressembler le stdin:, {"username":"myname","password":"<my-password>"}<my-password>est ce qui a été tapé dans l'invite du shell. Si j'avais le contrôle du programme stdin, je pourrais le modifier pour demander en toute sécurité un mot de passe et le mettre en place, mais l'aval est une commande standard à usage général.

J'ai considéré et rejeté les approches qui utilisent les éléments suivants:

  • l'utilisateur tapant le mot de passe dans la ligne de commande: le mot de passe serait affiché à l'écran et serait également visible pour tous les utilisateurs via "ps"
  • interpolation variable du shell dans un argument d'un programme externe (par exemple, ...$PASSWORD...): le mot de passe serait toujours visible par tous les utilisateurs via "ps"
  • variables d'environnement (si elles sont laissées dans l'environnement): le mot de passe serait visible par tous les processus enfants; même les processus dignes de confiance peuvent exposer le mot de passe s'ils vident le noyau ou les variables d'environnement dans le cadre d'un diagnostic
  • le mot de passe reste dans un fichier pendant une longue période, même un fichier avec des autorisations restreintes: l'utilisateur peut accidentellement exposer le mot de passe et l'utilisateur root peut accidentellement voir le mot de passe

Je mettrai ma solution actuelle comme réponse ci-dessous, mais je choisirai volontiers une meilleure réponse si quelqu'un en propose une. Je pense qu'il devrait y avoir quelque chose de plus simple ou peut-être que quelqu'un voit un problème de sécurité que j'ai manqué.

Jim Hoagland
la source
(Le cas d'utilisation complet est que le mot de passe doit faire partie du corps d'un POST vers un serveur HTTPS, mais je ne veux pas rendre cette question trop spécifique. Le stdin devient le corps POST via curl "-d @ - ".)
Jim Hoagland

Réponses:

14

Avec bashou 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

Sans IFS=, readsupprimerait les blancs de début et de fin du mot de passe que vous tapez.

Sans -r, il traiterait les barres obliques inverses comme un caractère de citation.

Vous voulez vous assurer de ne lire que depuis le terminal.

echone peut pas être utilisé de manière fiable. Dans bashet zsh, printfest intégré afin que la ligne de commande ne s'affiche pas dans la sortie de ps.

Dans bash, vous devez citer $password, sinon l' opérateur split + glob lui est appliqué.

C'est toujours faux, car vous devez coder cette chaîne en JSON. Par exemple, les guillemets doubles et les contre-obliques au moins seraient un problème. Vous devez probablement vous soucier de l'encodage de ces caractères. Votre programme attend-il des chaînes UTF-8? Qu'envoie votre terminal?

Pour ajouter une chaîne d'invite, avec zsh:

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

Avec bash:

IFS= read -rsp 'Please enter a password: ' password
Stéphane Chazelas
la source
printf - c'est ce dont nous avons besoin pour éviter l'invocation de perl - bonne astuce. Il est bon que vous ayez le unset passwordet set +alà, bien qu'il puisse être ignoré si vous pouvez faire plus d'hypothèses sur le shell actuel. Bon point sur l'option -r à lire et à IFS=devancer pour une meilleure généralité avec une variété de mots de passe. Bon d'aller de / dev / tty pour forcer l'entrée du terminal.
Jim Hoagland
1
oui, nous avons toujours un problème avec les guillemets doubles et la barre oblique inverse et peut-être quelques autres caractères. Nous aurions besoin de les coder avant de les placer dans le champ entre guillemets doubles dans le blob JSON. Je ne sais pas si curl attend UTF-8.
Jim Hoagland
1
python3 -c 'import json; print(json.dumps({"username": "myname", "password": input()}))', si vous avez Python qui traîne. Pour Python 2, s / input / raw_input /. Si vous dirigez le mot de passe dans cette commande, il crachera le JSON approprié.
Kevin
Kevin, ce serait une bonne suggestion si nous pouvons obtenir le mot de passe en toute sécurité dans stdin et que cela ne me dérange pas d'invoquer python. Cela construit le JSON à la volée. Cela fonctionnerait bien si vous utilisez getpass.getpass () dans Python, bien que vous perdiez la possibilité de continuer à utiliser le mot de passe stocké à plusieurs reprises.
Jim Hoagland du
2

Voici la solution que j'ai (elle a été testée):

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

Explication:

  1. read -slit une ligne de stdin (le mot de passe) sans renvoyer la ligne à l'écran. Il stocke est dans la variable shell MOT DE PASSE.
  2. echo -n $PASSWORDmet le mot de passe sur stdout sans nouvelle ligne. (echo est une commande intégrée au shell, donc aucun nouveau processus n'est créé, donc (AFAIK) le mot de passe comme argument pour echo ne sera pas affiché sur ps.)
  3. perl est invoqué et lit le mot de passe de stdin à $_
  4. perl met le mot de passe $_dans le texte intégral et l'imprime sur stdout

Si cela va à un POST HTTPS, la deuxième ligne serait quelque chose comme:

echo -n $PASSWORD | perl -e '$_=<>; print "{\"username\":\"myname\",\"password\":\"$_\"}"' | curl -H "Content-Type: application/json" -d@- -X POST <url-to-post-to>
Jim Hoagland
la source
3
Cela semble assez bon. Je noterai simplement qu'il n'y a aucune raison d'invoquer perl du tout; vous pouvez parfaitement faire quelque chose comme printf '{"username":"myname","password":"%s"}' $PASSWORD, qui conserve les mêmes propriétés de sécurité et vous évite un processus externe.
Tom Hunt du
2
Si vous souhaitez généraliser la solution aux readcommandes qui ne prennent pas en charge l'indicateur -s, vous pouvez utiliser "stty -echo; read PASSWORD; stty echo"
Jeff Schaller
2
Le tuyau démarre deux nouveaux processus, un pour chaque côté. Utilisez plutôt un ici-string: perl -e '...' <<< "$PASSWORD".
chepner
2
@chepner, <<<en bashstocke le contenu dans un fichier temporaire qui est pire.
Stéphane Chazelas
1
Selon TLDP, il crée un fichier mais il n'est pas lisible par aucun autre processus. "Ici, les documents créent des fichiers temporaires, mais ces fichiers sont supprimés après ouverture et ne sont accessibles à aucun autre processus." tldp.org/LDP/abs/html/here-docs.html juste après l'exemple 19-12.
dragon788
0

Si votre version de readne prend pas en charge, -sessayez la méthode compatible POSIX décrite dans cette réponse .

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

Le set +aest censé empêcher l '"exportation" automatique d'une variable vers l'environnement. Vous devriez consulter les pages de manuel pour stty, il y a beaucoup d'options disponibles. Le <<<"$passwd"est cité parce que les bons mots de passe peuvent avoir des espaces. Le dernier echoaprès avoir activé le stty echoest de démarrer la prochaine commande / sortie sur une nouvelle ligne.

dragon788
la source