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

43

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

$ darkcoind masternode start <mypassphrase>

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

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

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


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


Обновление 2 : Не говорите мне, чтобы удалить программное обеспечение или другие полезные советы, такие как зависание разработчиков. Я знаю, что это не лучший пример, но это программное обеспечение основано на биткойнах, и все клиенты, основанные на биткойнах, являются своего рода сервером json rpc, который слушает эти команды, и его известная проблема безопасности все еще обсуждается ( a , b , 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?

Вакар Лим
источник
1
Первый вопрос должен быть следующим: что произойдет, если вы запустите демон без аргумента парольной фразы. Это просто подсказывает?
MadHatter поддерживает Монику
31
Я не думаю, что есть ответ, который будет работать. Неспособность запросить парольную фразу является основным недостатком демона. Если это бесплатное программное обеспечение, подключите программиста и исправьте его; не забудьте опубликовать свои изменения. Если это проприетарное программное обеспечение, позвоните продавцу и кричите на него (это ничего не исправит, но вы почувствуете себя лучше).
MadHatter поддерживает Монику
4
Проверьте вашу документацию, она может поддерживать чтение этого пароля из системной переменной среды.
Эллиот Фриш
3
Даже если пароль не задан в командной строке для демона, все равно проблематично ввести его в командной строке любой другой команды. Он виден только в выводе ps в течение очень короткого времени, но процесс, работающий в фоновом режиме, все еще может его получить. Но, конечно, все же стоит усложнить подбор пароля.
Касперд
2
Посмотрите на ответы на этот вопрос , они имеют дело именно с этим вопросом.
dotancohen

Ответы:

68

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

Простой вставщик

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

#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

Библиотека Interposer запустит этот код до того, как будет выполнена 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);
}

Это запрашивает пароль, поэтому вам больше не нужно хранить секретную библиотеку промежуточных программ. Аргумент placeholder повторно используется как запрос пароля, поэтому вызывайте его следующим образом:

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

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

MVG
источник
4
Хотя я не понимаю и не могу протестировать код, я понял его суть, и это выглядит как фактический ответ и должен быть главным ответом.
Марк Хендерсон
Это действительно потрясающе.
Вакар Лим
Потрясающе. Насколько я могу сказать, это должно работать. Конечно, вам нужен доступ к источнику и возможность перекомпилировать. Пароль доступен для чтения в исходном и скомпилированном файле (ах), если вы используете «строки» или что-то подобное, поэтому лучше убедиться, что никто другой не сможет их прочитать.
Тонни
1
Должно быть возможно взять пароль на STDIN и все еще иметь эту работу, которая устраняет stringsуязвимость. См. SO: Скрыть ввод пароля на терминале .
Grubermensch
1
@ mulg0r: стандартный extern "C" должен уловить подавление искажения имени для соответствующей функции, а именно __libc_start_main.
MvG
28

Это не просто история. Это также будет отображаться в выводе ps .

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

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

Tonny
источник
9
Спасибо, что не помогли. Это долго обсуждаемая проблема безопасности , которая до сих пор не решена, и мне нужно лучшее решение, чем rm -fсейчас.
Вакар Лим
17
На самом деле, он очень помогает. Если вы передаете парольную фразу в качестве аргумента, она появится в ps. Поэтому, пока разработчик не сможет это исправить, он предлагает использовать что-то еще.
Сафадо
3
Тогда вам лучше начать писать другую операционную систему. В настоящее время нет другого доступного решения, о котором я знаю. Клянусь Богом, что был один. Вы не единственный с этой проблемой.
Тонни
8
Vertoe, не становись скупым. Вы можете попросить способ передать его на маленьких листочках бумаги, но это не значит, что такой способ автоматически существует. read_x - это хорошо, но все еще предоставляет парольную фразу, например ps, так что это не лучше, чем rmрешение.
MadHatter поддерживает Монику
7
Прежде чем вы пойдете и добавите еще +1 к этому не очень-то-то-то-то-то-то и-то и ответьте, что это невозможно, я предлагаю вам ознакомиться с ответом MvG ниже
Марк Хендерсон
19

Это очистит 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> Если это работает, он не будет производить вывод. Если не получится, он пожалуется на что-то и уйдет.

Мэтью Ифе
источник
18
, , , это одновременно и креативно, и пугающе.
voretaq7
EEK! Теперь я напуган.
Янне Пиккарайнен
Иккес, это может сработать ... Я не уверен, что что-то вроде AppArmor поймает это? Кроме того, virusscanner потенциально может это перехватить и вызвать хаос, заблокировав нарушающую учетную запись, которая будет «root». Там действительно драконы ....
Тонни
@Tonny Для защищенных доменов SELinux предотвратит это. Вашим базовым разрешениям Unix (DAC) не хватает достаточной детализации предмета, чтобы предложить какие-либо средства защиты от этого поведения (разрешает модификацию памяти процессов в пределах одного и того же UID). В любом случае, это не ошибка - это особенность. Я считаю, что именно так gdbможно изменить память о запущенных процессах (с гораздо большей хирургической точностью, чем я мог бы добавить).
Мэтью Ифе
11

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

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

уп.
источник
Был параметр оболочки, который включает его, но я думаю, что он не был включен по умолчанию.
heinrich5991
export HISTCONTROL=ignorebothигнорирует как дубликаты, так и строки с пробелом для ввода в историю. Добавьте его в свой .bashrc или .bash_profile.
Андреас
7

Может быть, это работает (?):

darkcoind masternode start `cat password.txt`
Даниэле Теста
источник
3
Или даже darkcoind masternode start `head -1`, если вы хотите ввести пароль вручную.
Касперд
14
Ключевая фраза все еще доступна через psи подобные утилиты.
voretaq7
1
Переход от обычного пароля .bash_historyк открытому паролю в password.txtвыигрыше, что именно?
MikeyB
1
@MikeyB: Есть небольшая победа: вы не будете случайно раскрывать ее, просматривая свою историю, пока кто-то смотрит через ваше плечо.
MvG
1
@MikeyB, вы можете создавать и удалять этот файл каждый раз.
RiaD
4

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

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

$ xargs darkcoind masternode start

password

CtrlD

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

200_success
источник
2
Или , если вы используете Баш, положить ignorespaceв $HISTCONTROL, а затем вы можете предотвратить любую команду из вдаваясь в историю оболочки, предварив команду с пробелом.
Дероберт
3

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

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

# <file system> <mount point>   <type>  <options>       <dump>  <pass>
proc            /proc           proc    defaults,hidepid=2        0       0
ptman
источник
2

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

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

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

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

Кит Томпсон
источник
1
фраза-пароль все еще доступна через psи подобные утилиты.
voretaq7
3
@ voretaq7: Да, как я четко отметил в последнем абзаце моего ответа.
Кит Томпсон,
3
Действительно - вы стали жертвой бессмысленного копипаста с моей стороны :)
voretaq7
2

Для Bitcoin официальный ответ разработчика заключается в использовании предоставленной оболочки 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

Для Darkcoin это работает anlogue:

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

Вакар Лим
источник