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

43

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

$ darkcoind masternode start <mypassphrase>

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

Sempre que eu pesquisar meu histórico bash, por exemplo, com Ctrl+R , posso ver essa senha super strong. Agora imagino que meu servidor está comprometido e algum intruso tem acesso ao shell e pode simplesmente Ctrl+R encontrar minha senha no histórico.

Existe uma maneira de inserir a frase secreta sem que ela seja mostrada no histórico bash, ps , /proc ou em qualquer outro lugar?

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

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

Atualização 3 : o daemon já foi iniciado e está sendo executado com o comando

$ darkcoind -daemon

Fazer ps mostra 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

Portanto, passar os comandos com a senha não aparece em ps ou /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

Isso deixa a pergunta onde a história aparece? Apenas em .bash_history ?

    
por soc1c 02.05.2014 / 17:30

9 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 pode cometer outros erros também, por isso não confiaria nele.

Interposer simples

Mas você estava pedindo um jeito diferente, então aqui está um:

#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

execute seu processo com

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

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

Interposer mais avançado

Se você quiser fazer algo mais elaborado, deve ter em mente que __libc_start_main é executado antes que a biblioteca de tempo de execução seja inicializada corretamente. Então, sugiro evitar qualquer chamada de função, a menos que seja absolutamente essencial. Se você quiser chamar funções para o conteúdo do seu coração, certifique-se de fazer isso antes que main seja invocada, depois que toda a inicialização estiver concluída. Para o exemplo a seguir, tenho que agradecer a Grubermensch, que apontou como ocultar uma senha passada como argumento de linha de comando que trouxe getpass para minha 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, assim você não precisa mais manter a biblioteca do interposer em segredo. O argumento de espaço reservado é reutilizado como prompt 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-fd ), ou de x11-ssh-askpass , ou qualquer outra coisa.

    
por 03.05.2014 / 21:49
28

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

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

Além de 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.

    
por 02.05.2014 / 17:41
18

Isso limpará a saída ps .

SEJA MUITO CONSCIENTE : isso pode interromper o aplicativo. Você está devidamente avisado que aqui são dragões.

  • Processos externos não devem estar mexendo em uma memória de processos.
  • Se o processo depender dessa região para a senha, você poderá interromper seu aplicativo.
  • Isso pode corromper todos os dados de trabalho que você tem nesse processo.
  • Este é um truque insano.

Agora você está devidamente notificado sobre esses terríveis avisos. Isso limpará a saída exibida em ps . Ele não limpará seu histórico nem limpará o histórico de trabalho bash (como executar o processo como myprocess myargs & ). Mas ps nã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-o, chmod +x it. Então fazendo ./whatever <pidoftarget> Se isso funcionar, não produzirá saída. Se falhar, vai reclamar de algo e desistir.

    
por 03.05.2014 / 00:04
11

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

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

    
por 02.05.2014 / 18:33
7

Talvez isso funcione (?):

darkcoind masternode start 'cat password.txt'
    
por 02.05.2014 / 21:31
4

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

Embora a exposição ps possa ser inevitável, você pode pelo menos evitar 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.

    
por 04.05.2014 / 10:13
2

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

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

Certifique-se de que sh esteja configurado não para salvar seu histórico em um arquivo.

É claro que isso não resolve os outros problemas, como a senha visível em ps . Existem, acredito, maneiras de o próprio programa darkcoind ocultar as informações de ps , mas isso apenas encurta a janela de vulnerabilidade.

    
por 02.05.2014 / 22:38
2

Como outros afirmaram, olhe para o 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 /proc com o parâmetro hidepid . Tente modificar sua linha /proc em /etc/fstab para incluir hidepid , desta forma:

# <file system> <mount point>   <type>  <options>       <dump>  <pass>
proc            /proc           proc    defaults,hidepid=2        0       0
    
por 17.05.2014 / 14:19
2

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

It asks for a password in a secure way if you use the command walletpassphrase, for example. There are no plans to add interactive functionality to bitcoin-cli.

e:

bitcoin-cli will remain as-is and not gain interactive functionality.

Fonte: # 2318

Desbloquear carteira:

$ python bitrpc.py walletpassphrase

Mude a senha:

$ python bitrpc.py walletpassphrasechange

link

Para darkcoin, funciona anlogue:

link

    
por 01.01.2015 / 09:44