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
?
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.
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é.
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.
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.
Cela effacera la sortie ps
.
SOYEZ TRÈS CONSCIENT: Cela pourrait casser l'application. Vous êtes dûment averti que voici des dragons.
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.
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.
Peut-être que cela fonctionne (?):
darkcoind masternode start `cat password.txt`
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.
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
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
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é.