44 votes

Comment masquer un mot de passe passé en argument de ligne de commande ?

Je lance un logiciel en arrière-plan qui nécessite pour certaines actions de saisir une phrase secrète pour déverrouiller certaines fonctionnalités qui ressemblent par exemple à ceci :

$ darkcoind masternode start 

Maintenant, j'ai quelques préoccupations en matière de sécurité sur mon serveur debian sans interface graphique.

Chaque fois que je recherche mon historique bash par exemple avec Ctrl+R, je peux voir ce mot de passe super fort. Maintenant, j'imagine que mon serveur est compromis et qu'un intrus a accès à un terminal et peut simplement utiliser Ctrl+R pour trouver ma phrase secrète dans l'historique.

Y a-t-il un moyen de saisir la phrase secrète sans qu'elle soit affichée dans l'historique bash, ps, /proc ou ailleurs ?


Mise à jour 1 : Ne pas passer de mot de passe au démon génère une erreur. Ce n'est pas une option.


Mise à jour 2 : Ne me dites pas de supprimer le logiciel ou d'autres astuces utiles comme pendre les développeurs. Je sais que ce n'est pas un exemple de bonnes pratiques, mais ce logiciel est basé sur bitcoin et tous les clients basés sur bitcoin sont des serveurs json rpc qui écoutent ces commandes et c'est un problème de sécurité connu toujours en cours de discussion (a, b, c).


Mise à jour 3 : Le démon est déjà démarré et fonctionne avec la commande

$ darkcoind -daemon

En faisant un ps, seul la commande de démarrage est affichée.

$ ps aux | grep darkcoin
user     12337  0.0  0.0  10916  1084 pts/4    S+   09:19   0:00 grep darkcoin
user     21626  0.6  0.3 1849716 130292 ?      SLl  May02   6:48 darkcoind -daemon

Ainsi, les commandes avec la phrase secrète ne s'affichent pas du tout dans ps ou /proc.

$ darkcoind masternode start 
$ ps aux | grep darkcoin
user     12929  0.0  0.0  10916  1088 pts/4    S+   09:23   0:00 grep darkcoin
user     21626  0.6  0.3 1849716 130292 ?      SLl  May02   6:49 darkcoind -daemon

Cela soulève la question : où l'historique apparaît-il ? Uniquement dans .bash_history ?

75voto

MvG Points 1793

Vraiment, cela devrait être corrigé dans l'application elle-même. Et de telles applications devraient être open source, de sorte que la correction du problème dans l'application elle-même devrait être une option. Une application liée à la sécurité qui commet ce genre d'erreur pourrait faire d'autres erreurs également, donc je ne lui ferais pas confiance.

Simple interposeur

Mais vous demandiez une autre manière, donc en voici une :

#define _GNU_SOURCE
#include 

int __libc_start_main(
    int (*main) (int, char * *, char * *),
    int argc, char * * ubp_av,
    void (*init) (void),
    void (*fini) (void),
    void (*rtld_fini) (void),
    void (* stack_end)
  )
{
  int (*next)(
    int (*main) (int, char * *, char * *),
    int argc, char * * ubp_av,
    void (*init) (void),
    void (*fini) (void),
    void (*rtld_fini) (void),
    void (* stack_end)
  ) = dlsym(RTLD_NEXT, "__libc_start_main");
  ubp_av[argc - 1] = "mot de passe secret";
  return next(main, argc, ubp_av, init, fini, rtld_fini, stack_end);
}

Compilez ceci avec

gcc -O2 -fPIC -shared -o injectpassword.so injectpassword.c -ldl

ensuite exécutez votre processus avec

LD_PRELOAD=$PWD/injectpassword.so darkcoind masternode start fakepassword

La bibliothèque interposeur exécutera ce code avant que la fonction main de votre application ne soit exécutée. Elle remplacera le dernier argument de ligne de commande par le mot de passe réel dans l'appel à main. La ligne de commande telle qu'imprimée dans /proc/*/cmdline (et donc vue par des outils tels que ps) contiendra toujours l'argument faux, cependant. Évidemment, vous devriez rendre le code source et la bibliothèque que vous compilez à partir de celui-ci lisible uniquement pour vous-même, alors opérez dans un répertoire chmod 0700. Et comme le mot de passe ne fait pas partie de l'invocation de commande, votre historique bash est également en sécurité.

Interposeur plus avancé

Si vous voulez faire quelque chose de plus élaboré, vous devriez garder à l'esprit que __libc_start_main est exécuté avant que la bibliothèque runtime ne soit correctement initialisée. Donc je suggérerais d'éviter tout appel de fonction à moins qu'ils ne soient absolument essentiels. Si vous voulez pouvoir appeler des fonctions autant que vous le souhaitez, assurez-vous de le faire juste avant que main lui-même soit invoqué, une fois que toute l'initialisation est faite. Pour l'exemple suivant, je dois remercier Grubermensch qui a souligné comment cacher un mot de passe passé en argument de ligne de commande qui a attiré mon attention sur getpass.

#define _GNU_SOURCE
#include 
#include 

static int (*real_main) (int, char * *, char * *);

static int my_main(int argc, char * * argv, char * * env) {
  char *pass = getpass(argv[argc - 1]);
  if (pass == NULL) return 1;
  argv[argc - 1] = pass;
  return real_main(argc, argv, env);
}

int __libc_start_main(
    int (*main) (int, char * *, char * *),
    int argc, char * * ubp_av,
    void (*init) (void),
    void (*fini) (void),
    void (*rtld_fini) (void),
    void (* stack_end)
  )
{
  int (*next)(
    int (*main) (int, char * *, char * *),
    int argc, char * * ubp_av,
    void (*init) (void),
    void (*fini) (void),
    void (*rtld_fini) (void),
    void (* stack_end)
  ) = dlsym(RTLD_NEXT, "__libc_start_main");
  real_main = main;
  return next(my_main, argc, ubp_av, init, fini, rtld_fini, stack_end);
}

Ceci demande le mot de passe, donc vous n'avez plus besoin de garder la bibliothèque interposeur secrète. L'argument de substitut est réutilisé comme invite de mot de passe, donc invoquez ceci comme

LD_PRELOAD=$PWD/injectpassword.so darkcoind masternode start "Mot de passe: "

Une autre alternative serait de lire le mot de passe à partir d'un descripteur de fichier (comme par exemple gpg --passphrase-fd le fait), ou à partir de x11-ssh-askpass, ou autre.

28voto

Tonny Points 6232

Il ne s'agit pas seulement de l'histoire. Cela va également apparaître dans la sortie de ps.

Qui que ce soit qui a écrit ce logiciel devrait être pendu, écartelé et démembré. C'est absolument inacceptable de devoir fournir un mot de passe en ligne de commande, peu importe le logiciel.
Pour un processus en arrière-plan, c'est encore PLUS impardonnable...

En dehors de la commande rm -f sur le logiciel lui-même, je ne connais aucune solution à ce problème. Honnêtement : Trouvez un autre logiciel pour faire le travail. N'utilisez pas de telles cochonneries.

19voto

Matthew Ife Points 22370

Cela effacera la sortie ps.

SOYEZ TRÈS PRUDENT : Cela pourrait casser l'application. Vous êtes dûment averti qu'il y a des dragons ici.

  • Les processus étrangers ne devraient pas manipuler la mémoire d'un processus.
  • Si le processus dépend de cette région pour le mot de passe, vous pourriez casser votre application.
  • Faire cela pourrait corrompre toutes les données de travail que vous avez dans ce processus.
  • C'est un hack insensé.

Maintenant, vous êtes dûment averti de ces avertissements sérieux. Cela effacera la sortie affichée dans ps. Cela n'effacera pas votre historique, ni l'historique des tâches bash (comme l'exécution du processus comme myprocess myargs &). Mais ps ne montrera plus les arguments.

#!/usr/bin/python
import os, sys
import re

PAGESIZE=4096

if __name__ == "__main__":
  if len(sys.argv) < 2:
    sys.stderr.write("Doit fournir un pid\n")
    sys.exit(1)

  pid = sys.argv[1]

  try:
    cmdline = open("/proc/{0}/cmdline".format(pid)).read(8192)

    ## Sur Linux, du moins, argv est situé dans la pile. Cela est probablement indépendant du système d'exploitation.
    ## Ouvrir le fichier maps et obtenir l'adresse de la pile.
    maps = open("/proc/{0}/maps".format(pid)).read(65536)
    m = re.search('([0-9a-f]+)-([0-9a-f]+)\s+rw.+\[stack\]\n', maps)
    if not m:
      sys.stderr.write("Impossible de trouver la pile dans le processus\n");
      sys.exit(1)

    start = int("0x"+m.group(1), 0)
    end = int("0x"+m.group(2), 0)

    ## Ouvrir le fichier mem
    mem = open('/proc/{0}/mem'.format(pid), 'r+')
    ## Comme la pile descend, commencer à la fin. On s'attend à ce que la valeur que nous recherchons soit en haut de la pile quelque part.
    ## Se positionner à la fin de la pile moins quelques pages.
    mem.seek(end-(2*PAGESIZE))

    ## Lire ce tampon jusqu'à la fin de la pile
    stackportion = mem.read(8192)
    ## chercher une chaîne correspondant à cmdline. C'est assez dangereux.
    ## ICI SE TROUVENT LES DRAGONS
    m = re.search(cmdline, stackportion)
    if not m:
      ## parce que c'est un exemple, n'essayez pas de rechercher de manière exhaustive, abandonnez simplement
      sys.stderr.write("Impossible de trouver la ligne de commande dans la pile. Abandon.")
      sys.exit(1)

    ## Sinon, nous avons une correspondance. Revenir en arrière notre descripteur de fichier, plus où nous avons trouvé le premier argument.
    mem.seek(end-(2*PAGESIZE)+m.start())
    ## En outre, nous conserverons arg0, car c'est le nom du programme.
    arg0len = len(cmdline.split("\x00")[0]) + 1
    mem.seek(arg0len, 1)

    ## enfin, écraser la région restante avec des null.
    writeover = "\x00" * (len(cmdline)-arg0len)
    mem.write(writeover)

    ## nettoyage
    mem.close()

  except OSError, IOError:
    sys.stderr.write("Impossible de trouver le pid\n")
    sys.exit(1)

Exécutez le programme en le sauvegardant, en lui donnant les autorisations avec chmod +x. Puis en faisant ./whatever Si cela fonctionne, il ne produira aucune sortie. S'il échoue, il se plaindra de quelque chose et quittera.

11voto

miracle2k Points 3285

Pouvez-vous passer l'argument à partir d'un fichier, accessible uniquement par root ou l'utilisateur requis?

C'est un GROS non-non de taper des mots de passe dans la console, mais en dernier recours...commencez votre ligne par un espace pour qu'elle n'apparaisse pas dans l'historique.

7voto

Daniele Testa Points 631

Peut-être que cela fonctionne (?):

darkcoind masternode start `cat password.txt`

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X