Como ocultar uma senha passada como argumento de linha de comando?

43

Estou executando um daemon de software que exige que determinadas ações insiram uma senha para desbloquear alguns recursos que se parecem, por exemplo:

$ darkcoind masternode start <mypassphrase>

Agora eu tenho algumas preocupações de segurança no meu servidor debian sem cabeça.

Sempre que procuro meu histórico do bash, por exemplo Ctrl+R, consigo ver essa senha super forte. Agora, imagino que meu servidor esteja comprometido e algum invasor tenha acesso a shell e possa simplesmente Ctrl+Rencontrar minha frase secreta no histórico.

Existe uma maneira de digitar a senha, sem que seja mostrado na história bash, ps, /procou em qualquer outro lugar?


Atualização 1 : não passar senha para o daemon gera um erro. Esta não é uma opção.


Atualização 2 : Não me diga para excluir o software ou outras dicas úteis, como desligar os desenvolvedores. Eu sei que este não é um exemplo de prática recomendada, mas este software é baseado em bitcoin e todos os clientes baseados em bitcoin são algum tipo de servidor json rpc que escuta esses comandos e ainda é discutido um problema de segurança conhecido ( a , b , c ) .


Atualização 3 : O daemon já está iniciado e em execução com o comando

$ darkcoind -daemon

Fazendo psmostra apenas o comando de inicialização.

$ 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

Então, passando os comandos com a senha não aparece na psou /procem tudo.

$ 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

Isso deixa a questão de onde a história aparece? Somente em .bash_history?

Waqar Lim
fonte
1
A primeira pergunta deve ser: o que acontece se você iniciar o daemon sem o argumento da senha. Isso apenas pede isso?
MadHatter apoia Monica
31
Eu não acho que haja uma resposta que funcione. A incapacidade de solicitar uma senha é uma das principais falhas do daemon. Se for software livre, instale um programador e corrija-o; não esqueça de publicar suas alterações. Se for um software proprietário, ligue para o fornecedor e grite com ele (isso não consertará nada, mas fará você se sentir melhor).
MadHatter apoia Monica
4
Verifique sua documentação, ele pode suportar a leitura dessa senha de uma variável de ambiente do sistema.
Elliott Frisch
3
Mesmo que a senha não seja fornecida na linha de comando para o daemon, ainda é problemático fornecê-la na linha de comando de qualquer outro comando. Ele é visível apenas na saída ps por um tempo muito curto, mas um processo em execução no plano de fundo ainda pode recuperá-lo. Mas é claro que ainda vale a pena dificultar a coleta da senha.
Kasperd
2
Veja as respostas para essa pergunta , elas lidam exatamente com esse problema.
dotancohen

Respostas:

68

Realmente, isso deve ser corrigido no próprio aplicativo. E esses aplicativos devem ser de código aberto, para que a correção do problema no próprio aplicativo seja uma opção. Um aplicativo relacionado à segurança que comete esse tipo de erro também pode cometer outros erros, para que eu não confie nele.

Interposer simples

Mas você estava pedindo uma maneira diferente, então aqui está uma:

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

Compile isso com

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

em seguida, execute seu processo com

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

A biblioteca do interposer executará esse código antes que a mainfunção do seu aplicativo seja executada. Ele substituirá o último argumento da linha de comando pela senha real na chamada para main. A linha de comando impressa /proc/*/cmdline(e, portanto, vista por ferramentas como ps) ainda conterá o argumento falso. Obviamente, você teria que tornar o código-fonte e a biblioteca compiladas legíveis apenas para si mesmo, para melhor operar em um chmod 0700diretório. E como a senha não faz parte da chamada do comando, seu histórico do bash também é seguro.

Interposer mais avançado

Se você quiser fazer algo mais elaborado, lembre-se de que __libc_start_mainé executado antes que a biblioteca de tempo de execução seja inicializada corretamente. Portanto, sugiro evitar chamadas de função, a menos que sejam absolutamente essenciais. Se você quiser chamar funções para o conteúdo do seu coração, faça isso antes que mainele seja chamado, depois que toda a inicialização estiver concluída. No exemplo a seguir, devo agradecer a Grubermensch, que apontou como ocultar uma senha passada como argumento de linha de comando, que me chamou getpassa atenção.

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

Isso solicita a senha, para que você não precise mais manter a biblioteca do interposer em segredo. O argumento do espaço reservado é reutilizado como solicitação de senha, portanto, invoque isso como

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

Outra alternativa seria ler a senha de um descritor de arquivo (como, por exemplo, gpg --passphrase-fdfaz), ou de x11-ssh-askpass, ou o que for.

MvG
fonte
4
Embora eu não entenda e não possa testar o código, eu entendo o essencial, e isso parece uma resposta real e deve ser a resposta principal.
Mark Henderson
Isso é realmente incrível.
Waqar Lim
Impressionante. Tanto quanto posso dizer, isso deve funcionar. Claro que você precisa acessar a fonte e poder recompilar. A senha é legível na fonte e no (s) arquivo (s) compilado (s) se você usar "strings" ou algo semelhante, para garantir que ninguém mais possa lê-las.
Tonny
1
Deve ser possível usar a senha no STDIN e ainda ter esse trabalho, o que remove a stringsvulnerabilidade. Consulte SO: ocultar a entrada de senha no terminal .
Grubermensch
1
@ mulg0r: O padrão externo "C" deve fazer o truque de suprimir o nome desconectado para a função relevante, a saber __libc_start_main.
MvG 16/04
28

Não é apenas a história. Ele também aparecerá na saída ps .

Quem escreveu esse software deve ser pendurado, desenhado e esquartejado. É um NÃO absoluto precisar fornecer uma senha na linha de comando, independentemente do software que seja.
Para um processo daemon, é ainda mais imperdoável ...

Além do rm -f no próprio software, não conheço nenhuma solução para isso. Honestamente: Encontre outro software para fazer o trabalho. Não use esse lixo.

Tonny
fonte
9
Obrigado por não ter sido útil. Este é um problema de segurança discutido há muito tempo , ainda não resolvido e preciso de uma solução melhor do que rm -fagora.
Waqar Lim
17
Na verdade, ele está sendo muito útil. Se você estiver passando a senha como argumento, ela aparecerá ps. Então, até que o desenvolvedor conserte isso, ele está sugerindo o uso de outra coisa.
Safado
3
Então é melhor começar a escrever outro sistema operacional. Atualmente, não tenho outra solução disponível. Por Deus, eu gostaria que houvesse um. Você não é o único com esse problema.
Tonny
8
vertoe, não fique furioso. Você pode pedir uma maneira de distribuí-lo em pequenos pedaços de papel, mas isso não significa que exista automaticamente. read_x é bom, mas ainda expõe a frase secreta por exemplo ps, portanto, não é melhor que a rmsolução.
MadHatter apoia Monica
7
Antes de vocês ir e jogar outro +1 nessa-an-resposta não-real e se queixam de que isso é impossível, eu sugiro que você reveja a resposta de MvG abaixo
Mark Henderson
19

Isso limpará a pssaída.

SEJA MUITO CONSCIENTE : Isso pode interromper o aplicativo. Você está devidamente avisado de que aqui devem haver dragões.

  • Os processos externos não devem estar mexendo na memória dos processos.
  • Se o processo depender dessa região para a senha, você poderá interromper seu aplicativo.
  • Fazer isso pode corromper qualquer dado que você tenha nesse processo.
  • Este é um truque insano.

Agora você está devidamente notificado desses avisos terríveis. Isso limpará a saída exibida em ps. Ele não limpará seu histórico nem o histórico de tarefas básicas (como executar o processo como myprocess myargs &). Mas psnão mostrará mais os argumentos.

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

Invoque o programa salvando- chmod +xo. Em seguida, fazendo ./whatever <pidoftarget> se isso funciona, ele vai produzir nenhuma saída. Se falhar, irá reclamar de algo e sair.

Matthew Ife
fonte
18
. . . isso é criativo e assustador.
Voretaq7
EEK! Agora eu estou assustado.
Janne Pikkarainen
Yikkes, isso poderia funcionar ... Não tenho certeza se algo como o AppArmor entenderia isso? Além disso, o virusscanner poderia pegar isso e causar estragos ao bloquear a conta ofensiva que seria 'raiz'. There Be Dragons, na verdade ....
Tonny
@Tonny Para domínios protegidos, o SELinux evitaria isso. Suas permissões básicas do Unix (DAC) carecem de granularidade de assunto suficiente para oferecer qualquer proteção contra esse comportamento (permite a modificação da memória dos processos no mesmo UID). De qualquer forma, não é um bug - é um recurso. Acredito que é assim que se gdbpode modificar a memória dos processos em execução (com muito mais precisão cirúrgica do que eu acrescentaria).
Matthew Ife
11

Você pode passar o argumento de um arquivo, acessível apenas pela raiz ou pelo usuário requerido?

É um enorme NÃO-NÃO digitar senhas no console, mas o último recurso ... comece sua linha com um espaço para que não apareça no histórico.

vn.
fonte
Havia uma opção de shell que a habilita, mas acho que não foi ativada por padrão.
Heinrich5991
export HISTCONTROL=ignorebothignora duplicatas e linhas com um espaço inicial para entrada no histórico. Adicione-o ao seu .bashrc ou .bash_profile.
Andreas
7

Talvez isso funcione (?):

darkcoind masternode start `cat password.txt`
Daniele Testa
fonte
3
Ou ainda darkcoind masternode start `head -1`, se você quiser inserir a senha manualmente.
Kasperd
14
A senha ainda está disponível em psutilitários e similares.
precisa saber é o seguinte
1
Passar de uma senha de texto sem formatação .bash_historypara uma senha de texto sem formatação password.txtganha o que, exatamente?
5608 MikeyB
1
@MikeyB: Há uma pequena vitória: você não a expõe acidentalmente enquanto pesquisa no seu histórico enquanto alguém está olhando por cima do seu ombro.
MvG
1
@ MikeyB, você pode criar e remover esse arquivo sempre.
RiaD
4

Infelizmente, se seu darkcoindcomando espera a senha como um argumento da linha de comando, ela será exposta por meio de utilitários como ps. A única solução real é educar os desenvolvedores .

Embora a psexposição possa ser inevitável, você pode pelo menos impedir que a senha seja gravada no arquivo de histórico do shell.

$ xargs darkcoind masternode start

password

CtrlD

O arquivo de histórico deve registrar apenas xargs darkcoind masternode start, não a senha.

200_success
fonte
2
Ou se você estiver usando bash, colocou ignorespaceem $HISTCONTROL, e então você pode impedir que qualquer comando de ir para a história shell prefixando o comando com um espaço.
Derobert 5/05
3

Como outros já declararam, examine seu controle do histórico do shell para ocultar as informações do histórico.

Mas uma coisa que ninguém parece ter sugerido ainda é montar /proccom o hidepidparâmetro Tente modificar sua /proclinha /etc/fstabpara incluir da hidepidseguinte maneira:

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

Você pode manter a senha fora do histórico do seu shell executando o comando a partir de um novo processo do shell, que você encerra imediatamente. Por exemplo:

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

Verifique se shestá configurado para não salvar seu histórico em um arquivo.

É claro que isso não resolve os outros problemas, como a senha visível ps. Acredito que existem maneiras de o darkcoindpróprio programa ocultar as informações ps, mas isso apenas reduz a janela de vulnerabilidade.

Keith Thompson
fonte
1
a senha ainda está disponível em psutilitários e similares.
precisa saber é o seguinte
3
@ voretaq7: Sim, como reconheci explicitamente no último parágrafo da minha resposta.
Keith Thompson
3
Na verdade - você foi vítima de copypasta devassa da minha parte :)
voretaq7
2

Para Bitcoin, a resposta oficial do desenvolvedor é usar o wrapper python fornecido no contrib/bitrpc/bitrpc.py( github ):

Ele solicita uma senha de forma segura se você usar o comando walletpassphrase, por exemplo. Não há planos para adicionar funcionalidades interativas bitcoin-cli.

e:

bitcoin-cli permanecerá como está e não obterá funcionalidade interativa.

Fonte: # 2318

Desbloquear carteira:

$ python bitrpc.py walletpassphrase

Alterar senha:

$ python bitrpc.py walletpassphrasechange

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

Para darkcoin, funciona análogo:

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

Waqar Lim
fonte