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


43

J'exécute un démon logiciel qui requiert certaines actions pour entrer une phrase secrète afin de déverrouiller certaines fonctionnalités qui ressemblent par exemple à ceci:

$ darkcoind masternode start <mypassphrase>

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

Chaque fois que je cherche dans mon historique de bash, par exemple, Ctrl+Rje peux voir ce mot de passe super fort. Maintenant, j'imagine que mon serveur est compromis et qu'un intrus a un accès au shell et peut simplement Ctrl+Rtrouver ma phrase secrète dans l'historique.

Existe-t-il un moyen de saisir la phrase secrète sans que celle-ci ne soit affichée dans l'historique bash ps, /procou ailleurs?


Mise à jour 1 : Ne transmettre 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 bonne 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 dont le problème de sécurité est toujours en cours de discussion ( a , b , c ). .


Mise à jour 3 : le démon est déjà démarré et s'exécute avec la commande

$ darkcoind -daemon

Faire psne montre 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 psou pas /procdu 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 se présente? Seulement dedans .bash_history?


1
La première question doit être: que se passera-t-il si vous démarrez le démon sans l'argument phrase secrète? Est-ce juste l'invite?
MadHatter soutient Monica

31
Je ne pense pas qu'il y ait une réponse qui fonctionnera. L'incapacité à demander une phrase secrète est une lacune majeure du démon. Si c'est un logiciel libre, faites venir un programmeur et corrigez-le; N'oubliez pas de publier vos modifications. Si vous utilisez un logiciel propriétaire, appelez le fournisseur et criez dessus (cela ne réglera rien, mais vous vous sentirez mieux).
MadHatter soutient Monica

4
Consultez votre documentation, elle peut prendre en charge la lecture de ce mot de passe à partir d'une variable d'environnement système.
Elliott Frisch

3
Même si le démon ne donne pas le mot de passe sur la ligne de commande, il est toujours problématique de le donner sur la ligne de commande de toute autre commande. Il n'est visible dans la sortie ps que pendant une très courte période, mais un processus exécuté en arrière-plan peut toujours le détecter. Mais il est bien entendu toujours intéressant de rendre plus difficile la saisie du mot de passe.
Kasperd

2
Regardez les réponses à cette question , ils traitent exactement de cette question.
dotancohen

Réponses:


68

Vraiment, cela devrait être corrigé dans l'application elle-même. Et ces applications doivent ê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 type d’erreur pourrait en commettre d’autres, aussi je ne lui ferais pas confiance.

Interposeur simple

Mais vous demandiez une autre façon, 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);
}

Compiler ceci avec

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

puis lancez votre processus avec

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

La bibliothèque interposeur exécutera ce code avant que la mainfonction de votre application soit exécutée. Il remplacera le dernier argument de la ligne de commande par le mot de passe actuel lors de l'appel à main. La ligne de commande telle qu'elle est imprimée dans /proc/*/cmdline(et donc vue par des outils tels que ps) contiendra toujours le faux argument, cependant. Il est évident que vous devez rendre le code source et la bibliothèque que vous compilez lisibles par vous-même, afin de mieux fonctionner dans un chmod 0700répertoire. Et comme le mot de passe ne fait pas partie de l'invocation de commande, votre historique bash est également sécurisé.

Interposeur plus avancé

Si vous voulez faire quelque chose de plus élaboré, gardez à l'esprit que celui-ci __libc_start_mainest exécuté avant que la bibliothèque d'exécution ait été correctement initialisée. Je suggère donc d'éviter tout appel de fonction à moins qu'il ne soit absolument essentiel. Si vous voulez pouvoir appeler des fonctions comme bon vous semble, assurez-vous de le faire juste avant d'être appelé main, une fois que toutes les initialisations sont terminées. Pour l'exemple suivant, je dois remercier Grubermensch qui a expliqué comment masquer un mot de passe passé en argument de ligne de commande, ce qui a attiré getpassmon 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 demande le mot de passe, vous n'avez donc plus besoin de garder la bibliothèque d'interposition secrète. L'argument fictif est réutilisé comme invite de mot de passe, invoquez-le ainsi

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

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


4
Bien que je ne comprenne pas et que je ne puisse pas tester le code, j'en comprends l'essentiel. Cela ressemble à une réponse réelle et devrait être la meilleure des réponses.
Mark Henderson

C'est vraiment génial.
Waqar Lim

Impressionnant. Autant que je sache, cela devrait fonctionner. Bien sûr, vous devez avoir accès à la source et être capable de recompiler. Le mot de passe est lisible dans le fichier source et le (s) fichier (s) compilé (s) si vous utilisez des "chaînes" ou quelque chose de similaire, il est donc préférable de s’assurer que personne d’autre ne peut les lire.
Tonny

1
Il devrait être possible de prendre le mot de passe sur STDIN tout en conservant ce travail, ce qui élimine la stringsvulnérabilité. Voir SO: Masquer la saisie du mot de passe sur le terminal .
Grubermensch

1
@ mulg0r: Standard extern "C" devrait faire en sorte que le nom ne soit pas modifié pour la fonction correspondante, à savoir __libc_start_main.
MVG le

28

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

Quiconque a écrit ce logiciel doit être suspendu, dessiné et coupé en quatre. Il est absolument NON de devoir fournir un mot de passe sur la ligne de commande, quel que soit le logiciel utilisé.
Pour un processus démon, c'est encore PLUS impardonnable ...

En dehors de rm -f sur le logiciel lui-même, je ne connais aucune solution à cela. Honnêtement: Trouvez un autre logiciel pour faire le travail. Ne pas utiliser une telle ordure.


9
Merci de ne pas être utile du tout. Il s'agit d'un problème de sécurité discuté depuis longtemps , toujours non résolu et j'ai besoin d'une meilleure solution de contournement que rm -fmaintenant.
Waqar Lim

17
En fait, il est très utile. Si vous passez la phrase secrète sous forme d'argument, elle apparaîtra dans ps. Donc, jusqu'à ce que le développeur puisse résoudre ce problème, il suggère d'utiliser autre chose.
Safado

3
Ensuite, vous feriez mieux de commencer à écrire un autre système d'exploitation. À ma connaissance, il n’ya AUCUNE autre solution disponible actuellement. Par Dieu, j'aimerais qu'il y en ait un. Vous n'êtes pas le seul à avoir ce problème.
Tonny

8
Vertoe, ne soyez pas snippy. Vous pouvez demander un moyen de le faire passer sur de petits bouts de papier, mais cela ne signifie pas qu’un tel moyen existe automatiquement. read_x va bien, mais expose toujours la phrase secrète via ps, par exemple , ce n'est donc pas mieux que la rmsolution.
MadHatter soutient Monica

7
Avant que vous alliez tous jeter un autre +1 sur ce pas-vraiment-une-réponse et vous plaindre que c'est impossible, je vous suggère de revoir la réponse de MvG ci
Mark Henderson

19

Cela effacera la pssortie.

Soyez très conscient : Cela pourrait casser l'application. Vous êtes dûment prévenu qu'il s'agisse de dragons.

  • Les processus étrangers ne doivent pas être manipulés dans une mémoire de processus.
  • Si le processus s'appuie sur cette région pour le mot de passe, vous pouvez casser votre application.
  • Cela pourrait corrompre les données de travail que vous avez dans ce processus.
  • C'est un hack fou.

Vous êtes maintenant dûment informé de ces avertissements. Cela effacera la sortie affichée dans ps. Votre historique ne sera pas effacé, pas plus que l'historique du travail bash (tel que l'exécution du processus myprocess myargs &). Mais psne 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("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)

Invoquez le programme en le sauvegardant, chmod +xil. Faire ensuite ./whatever <pidoftarget> si cela fonctionne, il ne produira pas de sortie. Si cela échoue, il se plaindra de quelque chose et cessera de fumer.


18
. . . c'est à la fois créatif et effrayant.
voretaq7

EEK! Maintenant j'ai peur.
Janne Pikkarainen

Yikkes, ça pourrait marcher ... Je ne suis pas sûr que quelque chose comme AppArmor pourrait attraper ça? De plus, virusscanner pourrait potentiellement attraper cela et causer des dégâts en bloquant le compte incriminé qui serait "root". Il y aura des dragons en effet ....
Tonny

@Tonny Pour les domaines protégés, SELinux empêcherait cela. Vos autorisations Unix de base (DAC) ne possèdent pas assez de granularité de sujet pour offrir une protection contre ce comportement (permet la modification de la mémoire des processus dans le même UID). Quoi qu'il en soit, ce n'est pas un bug - c'est une fonctionnalité. Je crois que c’est ainsi que l’ gdbon peut modifier la mémoire des processus en cours (avec une précision chirurgicale bien supérieure à celle que je pourrais ajouter).
Matthew Ife

11

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

C'est un énorme non-non pour taper les mots de passe dans la console, mais dernier recours ... commencez votre ligne par un espace afin qu'il n'apparaisse pas dans l'historique.


Il y avait une option de shell qui l'active, mais je pense qu'elle n'a pas été activée par défaut.
heinrich5991

export HISTCONTROL=ignorebothignore les doublons et les lignes avec un espace principal pour l'entrée dans l'historique. Ajoutez-le à votre .bashrc ou .bash_profile.
Andreas

7

Peut-être que cela fonctionne (?):

darkcoind masternode start `cat password.txt`

3
Ou même darkcoind masternode start `head -1`, si vous voulez entrer le mot de passe manuellement.
Kasperd

14
La phrase secrète est toujours disponible via psdes utilitaires similaires.
voretaq7

1
Passer d'un mot de passe en texte clair .bash_historyà un mot de passe en texte clair password.txtvous rapporte quoi exactement?
MikeyB

1
@MikeyB: Il y a un petit gain: vous ne l'exposerez pas accidentellement en cherchant dans votre historique alors que quelqu'un vous regarde par-dessus votre épaule.
MvG

1
@ MikeyB, vous pouvez créer et supprimer ce fichier à chaque fois.
RiaD

4

Malheureusement, si votre darkcoindcommande attend le mot de passe en tant qu'argument de ligne de commande, il sera exposé via des utilitaires tels que ps. La seule vraie solution consiste à éduquer les développeurs .

Bien que l' psexposition puisse être inévitable, vous pouvez au moins empêcher le mot de passe d'être écrit dans le fichier historique du shell.

$ xargs darkcoind masternode start

password

CtrlD

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


2
Ou, si vous utilisez bash, mettez-le ignorespacedans $HISTCONTROL, puis vous pouvez empêcher toute commande d'entrer dans l'historique du shell en préfixant la commande par un espace.
derobert

3

Comme d'autres l'ont déjà dit, examinez le contrôle de l'historique de votre shell pour masquer les informations de l'historique.

Mais une chose que personne ne semble avoir encore suggérée est de monter /procavec le hidepidparamètre. Essayez de modifier votre /procligne /etc/fstabpour inclure hidepid, comme ceci:

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

2

Vous pouvez conserver le mot de passe en dehors 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 qu'il shest configuré pour ne pas enregistrer son historique dans un fichier.

Bien sûr, cela ne résout pas les autres problèmes, tels que le mot de passe étant visible dans ps. Je crois que le darkcoindprogramme lui-même dispose de moyens pour cacher les informations ps, mais cela ne fait que réduire la fenêtre de vulnérabilité.


1
la phrase secrète est toujours disponible via psdes utilitaires similaires.
voretaq7

3
@ voretaq7: Oui, comme je l'ai explicitement reconnu dans le dernier paragraphe de ma réponse.
Keith Thompson

3
En effet - vous avez été victime de copypasta gratuite de ma part :)
voretaq7

2

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

Il 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 une fonctionnalité interactive à bitcoin-cli.

et:

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

Source: n ° 2318

Déverrouiller le portefeuille:

$ python bitrpc.py walletpassphrase

Changer le mot de passe:

$ 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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.