Jak ukryć hasło przekazane jako argument wiersza poleceń?

43

Korzystam z demona oprogramowania, który wymaga pewnych akcji, aby wprowadzić hasło, aby odblokować niektóre funkcje, które wyglądają na przykład tak:

$ darkcoind masternode start <mypassphrase>

Teraz mam pewne obawy dotyczące bezpieczeństwa na moim bezgłowym serwerze debian.

Ilekroć przeszukuję moją historię bashów, na przykład za Ctrl+Rpomocą tego super silnego hasła. Teraz wyobrażam sobie, że mój serwer jest zagrożony, a intruz ma dostęp do powłoki i może po prostu Ctrl+Rznaleźć moje hasło w historii.

Czy istnieje sposób, aby wprowadzić hasło bez niego mają być wyświetlane w bash historii ps, /procczy gdziekolwiek indziej?


Aktualizacja 1 : Nieprzekazanie hasła demonowi powoduje zgłoszenie błędu. To nie jest opcja.


Aktualizacja 2 : Nie każ mi usuwać oprogramowania ani innych pomocnych wskazówek, takich jak zawieszanie programistów. Wiem, że nie jest to przykład najlepszej praktyki, ale to oprogramowanie jest oparte na bitcoinach, a wszyscy klienci na bazie bitcoinów są pewnego rodzaju serwerem rson json, który nasłuchuje tych poleceń, a jego znany problem bezpieczeństwa jest wciąż omawiany ( a , b , c ) .


Aktualizacja 3 : Demon jest już uruchomiony i działa z poleceniem

$ darkcoind -daemon

Wykonanie powoduje pswyświetlenie tylko polecenia uruchamiania.

$ 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

Zatem przekazywanie poleceń za pomocą hasła nie pojawia się w ogóle pslub /procwcale.

$ 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

To pozostawia pytanie, gdzie pojawia się historia? Tylko w .bash_history?

Waqar Lim
źródło
1
Pierwsze pytanie musi brzmieć: co się stanie, jeśli uruchomisz demona bez argumentu hasła. Czy to tylko monit?
MadHatter obsługuje Monikę
31
Nie sądzę, że istnieje jakaś odpowiedź, która zadziała. Brak możliwości zapytania o hasło jest poważną wadą demona. Jeśli jest to wolne oprogramowanie, poproś programistę i napraw je; nie zapomnij opublikować swoich zmian. Jeśli jest to oprogramowanie prawnie zastrzeżone, zadzwoń do sprzedawcy i krzycz na niego (to niczego nie naprawi, ale poprawi ci humor).
MadHatter obsługuje Monikę
4
Sprawdź swoją dokumentację, może obsługiwać odczytywanie tego hasła ze systemowej zmiennej środowiskowej.
Elliott Frisch
3
Nawet jeśli hasło nie zostanie podane demonowi w wierszu poleceń, nadal problematyczne jest podanie go w wierszu poleceń dowolnego innego polecenia. Jest on widoczny tylko na wyjściu ps przez bardzo krótki czas, ale proces działający w tle nadal może go wykryć. Ale oczywiście nadal warto utrudniać wybranie hasła.
kasperd
2
Spójrz na odpowiedzi na to pytanie , zajmują się dokładnie tym problemem.
dotancohen

Odpowiedzi:

68

Naprawdę powinno to zostać naprawione w samej aplikacji. Takie aplikacje powinny być typu open source, więc rozwiązaniem problemu może być rozwiązanie samej aplikacji. Aplikacja związana z bezpieczeństwem, która popełnia ten błąd, może również popełniać inne błędy, więc nie ufałbym temu.

Prosty interpelator

Ale prosiłeś o inny sposób, więc oto jeden:

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

Skompiluj to z

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

następnie uruchom proces za pomocą

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

Biblioteka interposer uruchomi ten kod, zanim mainfunkcja z aplikacji zostanie wykonana. Zastąpi ostatni argument wiersza poleceń rzeczywistym hasłem w wywołaniu main. Jednak wiersz poleceń w postaci drukowanej /proc/*/cmdline(i dlatego widziany przez narzędzia takie jak ps) nadal będzie zawierał fałszywy argument. Oczywiście kod źródłowy i skompilowana z niego biblioteka będą musiały być czytelne tylko dla ciebie, więc najlepiej działaj w chmod 0700katalogu. A ponieważ hasło nie jest częścią wywołania polecenia, twoja historia bash jest również bezpieczna.

Bardziej zaawansowany interposer

Jeśli chcesz zrobić coś bardziej skomplikowanego, pamiętaj, że __libc_start_mainzostanie on wykonany przed prawidłową inicjalizacją biblioteki środowiska wykonawczego. Sugeruję więc unikanie wywołań funkcji, chyba że są one absolutnie niezbędne. Jeśli chcesz móc wywoływać funkcje do treści twojego serca, upewnij się, że robisz to tuż przed mainwywołaniem samego siebie, po zakończeniu całej inicjalizacji. Za poniższy przykład muszę podziękować Grubermenschowi, który wskazał, jak ukryć hasło przekazane jako argument wiersza poleceń, który zwrócił getpassmoją uwagę.

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

To monituje o hasło, więc nie musisz już utrzymywać w tajemnicy biblioteki interposer. Argument zastępczy jest ponownie używany jako monit o hasło, więc wywołaj to w ten sposób

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

Inna alternatywa polegałaby na odczytaniu hasła z deskryptora pliku (jak np. gpg --passphrase-fdRobi) lub z x11-ssh-askpass, czy cokolwiek innego.

MvG
źródło
4
Chociaż nie rozumiem i nie mogę przetestować kodu, rozumiem go w skrócie, a to wygląda na prawdziwą odpowiedź i powinna być najwyższą odpowiedzią.
Mark Henderson
To jest naprawdę niesamowite.
Waqar Lim
Niesamowite. O ile mogę powiedzieć, to powinno zadziałać. Oczywiście potrzebujesz dostępu do źródła i możesz dokonać ponownej kompilacji. Hasło można odczytać w źródle i skompilowanych plikach, jeśli używasz „ciągów” lub czegoś podobnego, więc lepiej upewnij się, że nikt inny nie może ich odczytać.
Tonny
1
Powinno być możliwe przyjęcie hasła na STDIN i nadal mieć tę pracę, która usuwa stringslukę. Zobacz : Ukryj hasło na terminalu .
Grubermensch
1
@ mulg0r: Standardowe zewnętrzne „C” powinno załatwić sprawę zniekształcania nazwy odpowiedniej funkcji, a mianowicie __libc_start_main.
MvG
28

To nie tylko historia. Pojawi się również w wynikach ps .

Ktokolwiek napisał to oprogramowanie, powinien zostać zawieszony, narysowany i podzielony na ćwiartki. Absolutnie NIE trzeba podawać hasło w wierszu poleceń, niezależnie od tego, jakie to oprogramowanie.
W przypadku procesu demona jest WIĘCEJ niewybaczalne ...

Oprócz rm -f na samym oprogramowaniu nie znam na to żadnego rozwiązania. Szczerze mówiąc: znajdź inne oprogramowanie, aby wykonać zadanie. Nie używaj takich śmieci.

Tonny
źródło
9
Dzięki, że w ogóle nie byłeś pomocny. Jest to długo dyskutowana kwestia bezpieczeństwa , wciąż nierozwiązana i potrzebuję lepszego obejścia niż rm -fteraz.
Waqar Lim
17
W rzeczywistości jest bardzo pomocny. Jeśli podasz hasło jako argument, pojawi się ps. Więc dopóki deweloper nie będzie w stanie tego naprawić, sugeruje użycie czegoś innego.
Safado
3
W takim razie lepiej zacznij pisać inny system operacyjny. Nie ma obecnie żadnego innego dostępnego rozwiązania, o którym wiem. Na Boga, żałuję, że nie było. Nie tylko ty masz ten problem.
Tonny
8
vertoe, nie bądź złośliwy. Możesz poprosić o sposób przekazania go na małe kartki papieru, ale to nie znaczy, że taki sposób istnieje automatycznie. read_x jest w porządku, ale nadal odsłania hasło poprzez np. ps, więc nie jest lepsze niż rmrozwiązanie.
MadHatter obsługuje Monikę
7
Zanim pójdziecie i rzucić kolejne +1 na to nie-naprawdę-odpowiedź i narzekać, że jest to niemożliwe, proponuję zapoznać się z odpowiedzią MvG poniżej
Mark Henderson
19

Spowoduje to wyczyszczenie danych pswyjściowych.

BĄDŹ BARDZO ŚWIADOME : może to spowodować uszkodzenie aplikacji. Jesteście odpowiednio ostrzeżeni, że tu będą smoki.

  • Obce procesy nie powinny bawić się w pamięci procesów.
  • Jeśli hasło opiera się na tym regionie, możesz przerwać aplikację.
  • Może to spowodować uszkodzenie wszystkich działających danych w tym procesie.
  • To szalony hack.

Teraz jesteś należycie powiadamiany o tych strasznych ostrzeżeniach. Spowoduje to wyczyszczenie wyjścia wyświetlanego w ps. Nie wyczyści twojej historii, ani nie wyczyści historii zadań bash (takich jak uruchomienie procesu jak myprocess myargs &). Ale psnie będzie już pokazywał argumentów.

#!/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)

Wywołaj program, zapisując chmod +xgo. Następnie robienie ./whatever <pidoftarget> Jeśli to zadziała, nie da żadnych wyników. Jeśli zawiedzie, na coś narzeka i rezygnuje.

Matthew Ife
źródło
18
. . . jest to zarówno twórcze, jak i przerażające.
voretaq7
EEK! Teraz boję.
Janne Pikkarainen
Yikkes, to może zadziałać ... Nie jestem pewien, czy coś takiego złapie AppArmor? Wiruscanner może potencjalnie to złapać i spowodować spustoszenie, blokując konto, które byłoby „rootem”. Rzeczywiście są smoki ....
Tonny
@ Tonny W przypadku domen chronionych SELinux zapobiegnie temu. W podstawowych uprawnieniach systemu Unix (DAC) brakuje wystarczającej szczegółowości tematu, aby zapewnić jakąkolwiek ochronę przed takim zachowaniem (umożliwia modyfikację pamięci procesów w ramach tego samego identyfikatora UID). W każdym razie, to nie jest błąd - to funkcja. Wierzę, że w ten sposób gdbmożna zmodyfikować pamięć uruchomionych procesów (z większą precyzją chirurgiczną niż mogę dodać).
Matthew Ife
11

Czy możesz przekazać argument z pliku dostępnego tylko dla użytkownika root lub wymaganego użytkownika?

WIELKIE nie można wpisywać haseł w konsoli, ale ostatnie odwołanie ... rozpocznij linię spacją, aby nie pojawiła się w historii.

vn.
źródło
Była opcja powłoki, która ją włącza, ale myślę, że nie była domyślnie włączona.
heinrich5991
export HISTCONTROL=ignorebothignoruje zarówno duplikaty, jak i wiersze z wiodącym miejscem na wejście do historii. Dodaj go do swojego .bashrc lub .bash_profile.
Andreas,
7

Może to działa (?):

darkcoind masternode start `cat password.txt`
Daniele Testa
źródło
3
Lub nawet darkcoind masternode start `head -1`, jeśli chcesz ręcznie wprowadzić hasło.
kasperd
14
Hasło jest nadal dostępne za pośrednictwem psi podobnych narzędzi.
voretaq7
1
Przejście z hasła w .bash_historypostaci zwykłego tekstu na hasło w postaci zwykłego tekstu password.txtdaje ci dokładnie to, co?
MikeyB
1
@MikeyB: Jest mała wygrana: przypadkowo jej nie ujawnisz podczas przeszukiwania historii, gdy ktoś patrzy ci przez ramię.
MvG
1
@MikeyB, możesz utworzyć i usunąć ten plik za każdym razem.
RiaD
4

Niestety, jeśli twoje darkcoindpolecenie oczekuje hasła jako argumentu wiersza poleceń, zostanie ono ujawnione za pomocą narzędzi takich jak ps. Jedynym prawdziwym rozwiązaniem jest edukacja programistów .

Chociaż psujawnienie może być nieuniknione, możesz przynajmniej uniemożliwić zapisanie hasła w pliku historii powłoki.

$ xargs darkcoind masternode start

password

CtrlD

Plik historii powinien tylko rejestrować xargs darkcoind masternode start, a nie hasło.

200_sukces
źródło
2
Lub jeśli używasz bash, umieścić ignorespacew $HISTCONTROL, a następnie można zapobiec żadnego polecenia od wchodzenia w historii powłoki poprzedzając polecenia ze spacjami.
derobert
3

Jak powiedzieli inni, sprawdź swoją kontrolę historii powłoki, aby ukryć informacje przed historią.

Ale jedną rzeczą, której nikt jeszcze nie sugerował, jest montowanie za /procpomocą tego hidepidparametru. Spróbuj zmodyfikować swój /procwiersz, /etc/fstababy zawierał hidepid:

# <file system> <mount point>   <type>  <options>       <dump>  <pass>
proc            /proc           proc    defaults,hidepid=2        0       0
ptman
źródło
2

Możesz ukryć hasło w historii powłoki, wykonując polecenie z nowego procesu powłoki, który następnie natychmiast przerywasz. Na przykład:

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

Upewnij się, że shskonfigurowano, aby nie zapisywać historii w pliku.

Oczywiście nie rozwiązuje to innych problemów, takich jak widoczne hasło w ps. Wierzę, że istnieją sposoby, aby darkcoindsam program ukrył informacje ps, ale to tylko skraca okno podatności.

Keith Thompson
źródło
1
hasło jest nadal dostępne za pośrednictwem psi podobnych narzędzi.
voretaq7
3
@ voretaq7: Tak, jak wyraźnie potwierdziłem w ostatnim akapicie mojej odpowiedzi.
Keith Thompson
3
Rzeczywiście - z mojej strony byłeś ofiarą bezmyślnego copypasta :)
voretaq7
2

W przypadku Bitcoin oficjalną odpowiedzią programisty jest użycie dostarczonego opakowania Pythona w contrib/bitrpc/bitrpc.py( github ):

Pyta o hasło w bezpieczny sposób, jeśli walletpassphrasena przykład użyjesz polecenia . Nie ma planów dodania interaktywnych funkcji bitcoin-cli.

i:

bitcoin-cli pozostanie bez zmian i nie zyska interaktywnej funkcjonalności.

Źródło: # 2318

Odblokuj portfel:

$ python bitrpc.py walletpassphrase

Zmień hasło:

$ python bitrpc.py walletpassphrasechange

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

W przypadku darkcoin działa anlogue:

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

Waqar Lim
źródło