web-dev-qa-db-fra.com

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

J'exécute un démon logiciel qui nécessite pour certaines actions d'entrer une phrase secrète pour déverrouiller certaines fonctionnalités qui ressemblent par exemple à cela:

$ darkcoind masternode start <mypassphrase>

Maintenant, j'ai des problèmes de sécurité sur mon serveur Debian sans tête.

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 à Shell et peut simplement Ctrl+R pour trouver ma phrase secrète dans l'histoire.

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


Mise à jour 1 : la transmission d'aucun 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 suspendre les développeurs. Je sais que ce n'est pas un exemple de meilleure pratique mais ce logiciel est basé sur bitcoin et tous les clients basés sur bitcoin sont une sorte de serveur json rpc qui écoute ces commandes et c'est un problème de sécurité connu qui est toujours en discussion ( a , b , c ).


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

$ darkcoind -daemon

Faire ps n'affiche que la commande de démarrage.

$ 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

Donc, passer les commandes avec la phrase secrète n'apparaît pas dans ps ou /proc du tout.

$ darkcoind masternode start <mypassphrase>
$ 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 laisse la question où l'histoire apparaît-elle? Seulement dans .bash_history?

43
Afr

Vraiment, cela devrait être corrigé dans l'application elle-même. Et ces applications devraient être open source, de sorte que la résolution 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 également en faire d'autres, donc je ne lui ferais pas confiance.

Interposeur simple

Mais vous demandiez une manière différente, alors en voici une:

#define _GNU_SOURCE
#include <dlfcn.h>

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] = "secret password";
  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

puis exécutez votre processus avec

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

La bibliothèque d'interposants exécutera ce code avant que la fonction main de votre application ne soit exécutée. Il remplacera le dernier argument de ligne de commande par le mot de passe réel dans l'appel à main. La ligne de commande imprimée en /proc/*/cmdline (et donc vu par des outils tels que ps) contiendra toujours le faux argument, cependant. De toute évidence, vous devez rendre le code source et la bibliothèque que vous compilez à partir de celui-ci lisibles uniquement par vous-même, il est donc préférable de fonctionner dans un chmod 0700 répertoire. Et puisque le mot de passe ne fait pas partie de l'appel de commande, votre historique bash est également sécurisé.

Interposeur plus avancé

Si vous voulez faire quelque chose de plus élaboré, vous devez garder à l'esprit que __libc_start_main est exécuté avant que la bibliothèque d'exécution ne soit correctement initialisée. Je suggère donc d'éviter tous les appels de fonction à moins qu'ils ne soient absolument essentiels. Si vous voulez être en mesure d'appeler des fonctions au contenu de votre cœur, assurez-vous de le faire juste avant que main lui-même ne soit invoqué, une fois l'initialisation terminée. Pour l'exemple suivant, je dois remercier Grubermensch qui a souligné comment masquer un mot de passe passé en argument de ligne de commande qui a attiré getpass à mon attention.

#define _GNU_SOURCE
#include <dlfcn.h>
#include <unistd.h>

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);
}

Cela vous invite à saisir le mot de passe, vous n'avez donc plus besoin de garder secrète la bibliothèque d'interposeurs. L'argument d'espace réservé est réutilisé en tant qu'invite de mot de passe.

LD_PRELOAD=$PWD/injectpassword.so darkcoind masternode start "Password: "

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

70
MvG

Ce n'est pas seulement l'histoire. Il va également apparaître dans la sortie ps.

Celui qui a écrit ce logiciel doit être accroché, dessiné et coupé en quartiers. C'est un NON absolu d'avoir à fournir un mot de passe sur la ligne de commande quel que soit le logiciel.
Pour un processus démon, c'est encore PLUS impardonnable ...

Outre rm -f sur le logiciel lui-même, je ne connais aucune solution pour cela. Honnêtement: Trouvez d'autres logiciels pour faire le travail. N'utilisez pas de tels déchets.

28
Tonny

Cela effacera la sortie ps.

SOYEZ TRÈS CONSCIENT: Cela pourrait casser l'application. Vous êtes dûment averti que voici des dragons.

  • Les processus étrangers ne doivent pas jouer dans la mémoire des processus.
  • Si le processus s'appuie sur cette région pour le mot de passe, vous pouvez interrompre votre application.
  • Cela pourrait corrompre toutes les données de travail que vous avez dans ce processus.
  • Ceci est un hack fou.

Vous êtes maintenant dûment informé de ces terribles avertissements. Cela effacera la sortie affichée dans ps. Il n'effacera pas votre historique, ni ne supprimera l'historique du travail bash (comme l'exécution du processus comme myprocess myargs &). Mais ps n'affichera plus les arguments.

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

PAGESIZE=4096

if __name__ == "__main__":
  if len(sys.argv) < 2:
    sys.stderr.write("Must provide a pid\n")
    sys.exit(1)

  pid = sys.argv[1]

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

    ## On linux, at least, argv is located in the stack. This is likely o/s
    ## independent.
    ## Open the maps file and obtain the stack address.
    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("Could not find stack in process\n");
      sys.exit(1)

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

    ## Open the mem file
    mem = open('/proc/{0}/mem'.format(pid), 'r+')
    ## As the stack grows downwards, start at the end. It is expected
    ## that the value we are looking for will be at the top of the stack
    ## somewhere
    ## Seek to the end of the stack minus a couple of pages.
    mem.seek(end-(2*PAGESIZE))

    ## Read this buffer to the end of the stack
    stackportion = mem.read(8192)
    ## look for a string matching cmdline. This is pretty dangerous.
    ## HERE BE DRAGONS
    m = re.search(cmdline, stackportion)
    if not m:
      ## cause this is an example dont try to search exhaustively, just give up
      sys.stderr.write("Could not find command line in the stack. Giving up.")
      sys.exit(1)

    ## Else, we got a hit. Rewind our file descriptor, plus where we found the first argument.
    mem.seek(end-(2*PAGESIZE)+m.start())
    ## Additionally, we'll keep arg0, as thats the program name.
    arg0len = len(cmdline.split("\x00")[0]) + 1
    mem.seek(arg0len, 1)

    ## lastly overwrite the remaining region with nulls.
    writeover = "\x00" * (len(cmdline)-arg0len)
    mem.write(writeover)

    ## cleanup
    mem.close()

  except OSError, IOError:
    sys.stderr.write("Cannot find pid\n")
    sys.exit(1)

Appelez le programme en l'enregistrant, chmod +x il. Puis en faisant ./whatever <pidoftarget> Si cela fonctionne, il ne produira aucune sortie. S'il échoue, il se plaindra de quelque chose et quittera.

20
Matthew Ife

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

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

11
vn.

Peut-être que cela fonctionne (?):

darkcoind masternode start `cat password.txt`
7
Daniele Testa

Malheureusement, si votre commande darkcoind attend le mot de passe comme argument de ligne de commande, il sera exposé via des utilitaires tels que ps. La seule vraie solution est de éduquer les développeurs .

Bien que l'exposition ps soit inévitable, vous pouvez au moins empêcher l'écriture du mot de passe dans le fichier d'historique Shell.

$ xargs darkcoind masternode start

password

CtrlD

Le fichier d'historique ne doit enregistrer que xargs darkcoind masternode start, pas le mot de passe.

4
200_success

Pour Bitcoin, la réponse officielle du développeur consiste à utiliser le wrapper python fourni dans contrib/bitrpc/bitrpc.py ( github ):

Il vous demande un mot de passe de manière sécurisée si vous utilisez la commande walletpassphrase, par exemple. Il n'est pas prévu d'ajouter des fonctionnalités interactives à bitcoin-cli.

et:

bitcoin-cli restera tel quel et n'obtiendra pas de fonctionnalité interactive.

Source: # 2318

Déverrouillez le portefeuille:

$ python bitrpc.py walletpassphrase

Modifier la phrase secrète:

$ python bitrpc.py walletpassphrasechange

https://github.com/bitcoin/bitcoin/tree/master/contrib/bitrpc

Pour darkcoin cela fonctionne anlogue:

https://github.com/darkcoin/darkcoin/tree/master/contrib/bitrpc

3
Afr

Comme d'autres l'ont indiqué, examinez votre contrôle d'historique Shell pour masquer les informations de l'historique.

Mais une chose que personne ne semble avoir encore suggérée est de monter /proc avec le paramètre hidepid. Essayez de modifier votre /proc faire la queue /etc/fstab pour inclure hidepid, comme ceci:

# <file system> <mount point>   <type>  <options>       <dump>  <pass>
proc            /proc           proc    defaults,hidepid=2        0       0
3
ptman

Vous pouvez garder le mot de passe hors de l'historique de votre Shell en exécutant la commande à partir d'un nouveau processus Shell, que vous terminez immédiatement. Par exemple:

bash$ sh
sh$ darkcoind masternode start 'correct horse battery staple'
sh$ exit
bash$

Assurez-vous que sh est configuré pas pour enregistrer son historique dans un fichier.

Bien sûr, cela ne résout pas les autres problèmes, tels que le mot de passe visible dans ps. Il y a, je crois, des moyens pour le programme darkcoind lui-même de cacher les informations de ps, mais cela ne fait que raccourcir la fenêtre de vulnérabilité.

2
Keith Thompson