Qual é a maneira mais segura e simples de fazer com que uma senha digitada pelo usuário no bash se torne parte do stdin em um programa?

12

Estou procurando a (1) maneira mais segura e (2) mais simples de fazer com que um usuário digite uma senha em um prompt do shell bash e fazer com que essa senha se torne parte do stdin em um programa.

É assim que o stdin precisa se parecer:, {"username":"myname","password":"<my-password>"}onde <my-password>está o que foi digitado no prompt do shell. Se eu tivesse controle sobre o programa stdin, poderia modificá-lo para solicitar com segurança uma senha e colocá-la no lugar, mas o downstream é um comando padrão de uso geral.

Eu considerei e rejeitei abordagens que usam o seguinte:

  • o usuário digitando a senha na linha de comando: a senha seria mostrada na tela e também visível a todos os usuários via "ps"
  • interpolação de variável de shell em um argumento para um programa externo (por exemplo, ...$PASSWORD...): a senha ainda estaria visível a todos os usuários via "ps"
  • variáveis ​​de ambiente (se forem deixadas no ambiente): a senha seria visível para todos os processos filhos; mesmo processos confiáveis ​​podem expor a senha se despejarem variáveis ​​de ambiente principais ou de despejo como parte de um diagnóstico
  • a senha em um arquivo por um longo período de tempo, mesmo um arquivo com permissões restritas: o usuário pode expor acidentalmente a senha e o usuário root pode vê-la acidentalmente

Colocarei minha solução atual como resposta abaixo, mas selecionarei com satisfação uma resposta melhor se alguém tiver uma. Eu estou pensando que deveria haver algo mais simples ou talvez alguém veja uma preocupação de segurança que eu perdi.

Jim Hoagland
fonte
(O caso de uso completo é que a senha precisa fazer parte do corpo de um POST para um servidor HTTPS, mas não quero tornar essa pergunta excessivamente específica. O stdin passa a ser o corpo do POST via curl "-d @ - ".)
Jim Hoagland

Respostas:

13

Com bashou zsh:

unset -v password # make sure it's not exported
set +o allexport  # make sure variables are not automatically exported
IFS= read -rs password < /dev/tty &&
  printf '{"username":"myname","password":"%s"}\n' "$password" | cmd

Sem IFS=, readremoveria espaços em branco à esquerda e à esquerda da senha digitada.

Sem -r, ele processaria barras invertidas como um caractere de citação.

Você quer ter certeza de ler apenas a partir do terminal.

echonão pode ser usado com segurança. Em bashe zsh, printfestá embutido para que a linha de comando não apareça na saída de ps.

Em bash, é necessário citar $password, caso contrário, o operador split + glob será aplicado a ele.

Ainda está errado, pois você precisará codificar essa sequência como JSON. Por exemplo, aspas duplas e barra invertida pelo menos seria um problema. Você provavelmente precisa se preocupar com a codificação desses caracteres. Seu programa espera cadeias UTF-8? O que o seu terminal envia?

Para adicionar uma string de prompt, com zsh:

IFS= read -rs 'password?Please enter a password: '

Com bash:

IFS= read -rsp 'Please enter a password: ' password
Stéphane Chazelas
fonte
printf - é disso que precisamos para evitar a chamada perl - boa dica. É bom que você tenha o unset passworde set +alá, embora possa ser ignorado se você puder fazer mais suposições sobre o shell atual. Bom argumento sobre a opção -r para ler e colocar IFS=à frente para uma melhor generalidade com uma variedade de senhas. É bom ir de / dev / tty para forçar a entrada do terminal.
Jim Hoagland
1
sim, ainda temos um problema com aspas duplas e barra invertida e talvez alguns outros caracteres. Nós precisaríamos codificá-los antes de colocá-los dentro do campo de aspas duplas no blob JSON. Não tenho certeza se a curvatura espera UTF-8.
Jim Hoagland
1
python3 -c 'import json; print(json.dumps({"username": "myname", "password": input()}))', se você tiver Python por aí. Para Python 2, s / input / raw_input /. Se você canalizar a senha nesse comando, ele emitirá o JSON apropriado.
Kevin
Kevin, isso seria uma boa sugestão se conseguirmos inserir a senha com segurança no stdin e não se importar de chamar o python. Isso constrói o JSON em tempo real. Isso funcionaria bem se usar getpass.getpass () dentro do Python, embora você perca a capacidade de continuar usando a senha armazenada repetidamente.
Jim Hoagland
2

Aqui está a solução que tenho (foi testada):

$ read -s PASSWORD
<user types password>
$ echo -n $PASSWORD | perl -e '$_=<>; print "{\"username\":\"myname\",\"password\":\"$_\"}"'

Explicação:

  1. read -slê uma linha de stdin (a senha) sem ecoar a linha na tela. Ele armazena está na variável do shell PASSWORD.
  2. echo -n $PASSWORDcoloca a senha em stdout sem nenhuma nova linha. (echo é um comando interno do shell, portanto, nenhum novo processo é criado, portanto (AFAIK) a senha como argumento para eco não será mostrada no ps.)
  3. o perl é chamado e lê a senha de stdin para $_
  4. perl coloca a senha no $_texto completo e a imprime em stdout

Se isso for para um HTTPS POST, a segunda linha seria algo como:

echo -n $PASSWORD | perl -e '$_=<>; print "{\"username\":\"myname\",\"password\":\"$_\"}"' | curl -H "Content-Type: application/json" -d@- -X POST <url-to-post-to>
Jim Hoagland
fonte
3
Isso parece muito bom. Vou apenas observar que não há nenhuma razão para invocar o perl; você pode perfeitamente fazer algo como printf '{"username":"myname","password":"%s"}' $PASSWORD, que mantém as mesmas propriedades de segurança e economiza um processo externo.
Tom Hunt
2
Se você deseja generalizar a solução para readcomandos que não suportam o sinalizador -s, pode usar "stty -echo; leia PASSWORD; stty echo"
Jeff Schaller
2
O tubo inicia dois novos processos, um para cada lado. Use um aqui-string em vez disso: perl -e '...' <<< "$PASSWORD".
chepner 25/09/15
2
@chepner, <<<em bashlojas o conteúdo em um arquivo temporário que é pior.
Stéphane Chazelas
1
De acordo com o TLDP, ele cria um arquivo, mas não é legível por nenhum outro processo. "Aqui, os documentos criam arquivos temporários, mas esses arquivos são excluídos após a abertura e não são acessíveis a nenhum outro processo." tldp.org/LDP/abs/html/here-docs.html logo após o Exemplo 19-12.
precisa saber é o seguinte
0

Se sua versão do readnão suportar, -stente a maneira compatível com POSIX vista nesta resposta .

echo -n "USERNAME: "; read uname
echo -n "PASSWORD: "; set +a; stty -echo; read passwd; command <<<"$passwd"; set -a; stty echo; echo
passwd= # get rid of passwd possibly only necessary if running outside of a script

O set +aé suposto para evitar auto "exportação" de uma variável para o meio ambiente. Você deve verificar as páginas de manual stty, pois há muitas opções disponíveis. O <<<"$passwd"é citado porque boas senhas podem ter espaços. O último echoapós ativar o stty echoé ter o próximo comando / saída iniciado em uma nova linha.

dragon788
fonte