Назад | Перейти на главную страницу

Как скрыть пароль, переданный как аргумент командной строки?

Я запускаю программный демон, который требует для определенных действий ввести кодовую фразу, чтобы разблокировать некоторые функции, которые выглядят, например, так:

$ darkcoind masternode start <mypassphrase>

Теперь у меня возникли некоторые проблемы с безопасностью моего безголового сервера debian.

Когда я ищу свою историю bash, например, с помощью Ctrl+R Я вижу этот сверхнадежный пароль. Теперь я представляю, что мой сервер скомпрометирован, и какой-то злоумышленник имеет доступ к оболочке и может просто Ctrl+R найти мой пароль в истории.

Есть ли способ ввести кодовую фразу, чтобы она не отображалась в истории bash, ps, /proc или еще где-нибудь?


Обновление 1: Передача демону пароля без пароля вызывает ошибку. Это не вариант.


Обновление 2: Не говорите мне удалить программное обеспечение или другие полезные подсказки, например, вешать разработчиков. Я знаю, что это не лучший пример, но это программное обеспечение основано на биткойн и все клиенты, основанные на биткойнах, представляют собой своего рода json rpc-сервер, который слушает эти команды, и это известная проблема безопасности, которая все еще обсуждается (а, б, c).


Обновление 3: Демон уже запущен и работает с командой

$ darkcoind -daemon

Делать ps показывает только команду запуска.

$ 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

Таким образом, передача команд с парольной фразой не отображается в ps или /proc вообще.

$ 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

Остается вопрос, где появляется история? Только в .bash_history?

Действительно, это должен быть исправлено в самом приложении. И такие приложения должен иметь открытый исходный код, поэтому исправление проблемы в самом приложении должно быть вариантом. Приложение, связанное с безопасностью, которое допускает такую ​​ошибку, может допускать и другие ошибки, поэтому я бы ему не доверял.

Простой переходник

Но вы просили другой способ, поэтому вот один:

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

Скомпилируйте это с

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

затем запустите свой процесс с помощью

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

Библиотека интерпозера запустит этот код перед main функция из вашего приложения будет выполнена. Он заменит последний аргумент командной строки фактическим паролем при вызове main. Командная строка, напечатанная в /proc/*/cmdline (и, следовательно, видны такими инструментами, как ps) тем не менее, будет содержать ложный аргумент. Очевидно, вам придется сделать исходный код и компилируемую из него библиотеку доступными для чтения только вам, поэтому лучше всего работать в chmod 0700 каталог. А поскольку пароль не является частью вызова команды, ваша история bash также в безопасности.

Более продвинутый интерпозер

Если вы хотите сделать что-то более сложное, помните, что __libc_start_main выполняется до того, как библиотека времени выполнения будет правильно инициализирована. Поэтому я бы посоветовал избегать любых вызовов функций, если они не являются абсолютно необходимыми. Если вы хотите иметь возможность вызывать функции, сколько душе угодно, обязательно сделайте это непосредственно перед main сам вызывается после завершения инициализации. За следующий пример я должен поблагодарить Grubermensch, который указал как скрыть пароль, переданный как аргумент командной строки который принес getpass к моему вниманию.

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

При этом запрашивается пароль, поэтому вам больше не нужно держать в секрете библиотеку интерпозера. Аргумент заполнителя повторно используется как запрос пароля, поэтому вызывайте его как

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

Другая альтернатива - прочитать пароль из файлового дескриптора (например, gpg --passphrase-fd делает), или из x11-ssh-askpass, или что-то еще.

Дело не только в истории. Он появится в пс вывод тоже.

Тот, кто написал эту программу, должен быть повешен, нарисован и четвертован. Абсолютно НЕТ необходимости вводить пароль в командной строке независимо от того, какое это программное обеспечение.
Для процесса-демона это еще БОЛЬШЕ непростительно ...

Кроме rm -f на самом программном обеспечении я не знаю никакого решения для этого. Честно: найдите другое программное обеспечение для выполнения этой работы. Не используйте такой хлам.

Это очистит ps вывод.

БУДЬТЕ ВНИМАТЕЛЬНЫ: Это может нарушить работу приложения. Вас предупредили, что здесь драконы.

  • Посторонние процессы не должны возиться с памятью процессов.
  • Если процесс полагается на этот регион для пароля, вы можете сломать свое приложение.
  • Это может повредить любые рабочие данные, которые у вас есть в этом процессе.
  • Это безумный взлом.

Теперь вы должным образом уведомлены об этих ужасных предупреждениях. Это очистит вывод, отображаемый в ps. Это не очистит вашу историю и не очистит историю заданий bash (например, запуск процесса, например myprocess myargs &). Но ps больше не будет показывать аргументы.

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

Вызвать программу, сохранив ее, chmod +x Это. Затем делая ./whatever <pidoftarget> Если это сработает, результат не будет. В случае неудачи он на что-то пожалуется и уйдет.

Можете ли вы передать аргумент из файла, доступного только пользователю root или нужному пользователю?

ОГРОМНОЕ запрещение вводить пароли в консоли, но последний выход ... начинайте строку с пробела, чтобы она не отображалась в истории.

Может это сработает (?):

darkcoind masternode start `cat password.txt`

К сожалению, если ваш darkcoind команда ожидает пароль в качестве аргумента командной строки, затем он будет доступен с помощью таких утилит, как ps. Единственное реальное решение - обучать разработчиков.

В то время ps раскрытие может быть неизбежным, вы могли бы, по крайней мере, предотвратить запись пароля в файл истории оболочки.

$ xargs darkcoind masternode start

паssшорd

CtrlD

Файл истории должен записывать только xargs darkcoind masternode startа не пароль.

Как заявляли другие, посмотрите в свой элемент управления историей оболочки, чтобы скрыть информацию из истории.

Но одной вещи, кажется, еще никто не предложил, это установить /proc с hidepid параметр. Попробуйте изменить свой /proc линия в /etc/fstab включать hidepid, как это:

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

Для Биткойна официальный ответ разработчика - использовать предоставленную оболочку python в contrib/bitrpc/bitrpc.py (github):

Он запрашивает пароль безопасным способом, если вы используете команду walletpassphrase, например. Нет планов по добавлению интерактивных функций в bitcoin-cli.

и:

bitcoin-cli останется как есть и не получит интерактивной функциональности.

Источник: # 2318

Разблокировать кошелек:

$ python bitrpc.py walletpassphrase

Изменить кодовую фразу:

$ python bitrpc.py walletpassphrasechange

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

Для дарккойнов это работает в журнале:

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

Вы можете сохранить пароль вне истории вашей оболочки, выполнив команду из нового процесса оболочки, который затем немедленно завершите. Например:

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

Удостовериться sh настроен не чтобы сохранить его историю в файл.

Конечно, это не решает других проблем, таких как отображение пароля в ps. Я считаю, что есть способы darkcoind сама программа, чтобы скрыть информацию от ps, но это только сокращает окно уязвимости.