É seguro usar o eco para passar dados confidenciais para o chpasswd?

17

Estou tentando definir em massa algumas senhas de contas de usuário usando chpasswd. As senhas devem ser geradas aleatoriamente e impressas stdout(preciso anotá-las ou colocá-las em um armazenamento de senhas) e também passadas para chpasswd.

Ingenuamente, eu faria isso assim

{
  echo student1:$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13 ; echo '')
  echo student2:$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13 ; echo '')
} | tee >(chpasswd)

No entanto, me preocupo em passar a nova senha como um argumento de linha de comando para echo, porque os argumentos geralmente são visíveis para outros usuários ps -aux(embora eu nunca tenha visto nenhuma echolinha aparecer ps).

Existe uma maneira alternativa de acrescentar um valor à minha senha retornada e passá-la para chpasswd?

Nils Werner
fonte
6
echoé um shell embutido. Não apareceria na tabela de processos.
Kusalananda

Respostas:

15

Seu código deve ser seguro, pois echonão será exibido na tabela de processos, pois é um shell interno.

Aqui está uma solução alternativa:

#!/bin/bash

n=20
paste -d : <( seq -f 'student%.0f' 1 "$n" ) \
           <( tr -cd 'A-Za-z0-9' </dev/urandom | fold -w 13 | head -n "$n" ) |
tee secret.txt | chpasswd

Isso cria os nomes e as senhas ndos alunos , sem passar nenhuma senha em qualquer linha de comando de nenhum comando.

O pasteutilitário cola vários arquivos como colunas e insere um delimitador entre eles. Aqui, usamos :como delimitador e atribuímos dois "arquivos" (substituições de processo). O primeiro contém a saída de um seqcomando que cria 20 nomes de usuário de alunos e o segundo contém a saída de um pipeline que cria 20 seqüências aleatórias de comprimento 13.

Se você possui um arquivo com nomes de usuário já gerados:

#!/bin/bash

n=$(wc -l <usernames.txt)

paste -d : usernames.txt \
           <( tr -cd 'A-Za-z0-9' </dev/urandom | fold -w 13 | head -n "$n" ) |
tee secret.txt | chpasswd

Isso salvará as senhas e nomes de usuários no arquivo, em secret.txtvez de mostrar as senhas geradas no terminal.

Kusalananda
fonte
2
Essa é uma solução inteligente que não precisa echoou printf, porém o código é um pouco estranho para decifrar
Nils Werner
@NilsWerner Adicionou um pouco mais de explicação. Deixe-me saber se há uma coisa em particular que você gostaria que eu expandisse.
Kusalananda
1
Você não deve confiar em echoser um shell embutido. Na maioria das conchas, é verdade, mas não há absolutamente nenhum requisito.
Austin Hemmelgarn
1
@ Kusalananda Apenas curioso, existe uma diferença efetiva entre tee secret.txt > >(chpasswd)e tee secret.txt | chpasswd? Este último parece muito mais comum, então eu estou querendo saber por que você escolheu para usar a substituição de processo em vez de um tubo simples
Christopher Shroba
1
@ChristopherShroba Não há outra razão que não seja semelhante ao código que já estava em questão (usando uma substituição de processo com tee). Agora que você apontou, acho que vou realmente mudar (parece melhor). Obrigado.
Kusalananda
8

echoprovavelmente está embutido no shell, portanto, não apareceria pscomo um processo separado.

Mas você não precisa usar a substituição de comando, basta obter a saída do pipeline diretamente para chpasswd:

{  printf "%s:" "$username";
   head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13 ; echo ''
} | chpasswd 

Se você deseja alterar várias senhas com apenas uma execução chpasswd, deve ser fácil repetir as partes essenciais. Ou transformá-lo em uma função:

genpws() {
    for user in "$@"; do
        printf "%s:" "$user";
        head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13
        echo
    done
}
genpws username1 username2... | chpasswd 

Como um aparte: isso head /dev/urandomparece um pouco estranho, pois urandomnão é orientado a linhas. Ele pode ler quantidades excessivas de bytes, o que afetará a noção de entropia disponível do kernel, o que, por sua vez, pode levar ao /dev/randombloqueio. Pode ser mais fácil ler apenas uma quantidade fixa de dados e usar algo como base64converter os bytes aleatórios em caracteres imprimíveis (em vez de apenas jogar fora cerca de 3/4 dos bytes obtidos).

Algo assim daria a você aprox. 16 caracteres e números:

head -c 12 /dev/urandom | base64 | tr -dc A-Za-z0-9 

(ou seja, 16 menos a quantidade de +e /caracteres na saída de base64. A chance de qualquer um é 1/32 por caractere; portanto, se eu acertar minhas combinações, isso dará uma chance de 99% de deixar pelo menos 14 caracteres e uma chance de 99,99% de deixar pelo menos 12.)

ilkkachu
fonte
Sua printfsolução não faz o meu código original não: retornar várias linhas para vários nomes de usuário, mas eu aprecio os seus comentários onhead urandom
Nils Werner
@ NilsWerner, bem, eu acho óbvia a expansão para vários usuários. Mas não importa, poderíamos criar uma função para produzir os pares nome de usuário: senha para vários usuários de uma só vez. (veja editar)
ilkkachu
3
Você realmente não pode confiar nas 10 primeiras "linhas" de urandom contendo os caracteres que você deseja. Basta executar o urandom diretamente no tr.
Kusalananda
@ Kusalananda, você quer dizer o meu head -c 10 | base64oleoduto ou o de Nils head | tr? É provável que 10 "linhas" de bytes aleatórios sejam centenas de bytes (dado que apenas 1 em 256 seriam novas linhas), embora em teoria possa ser exatamente 10 bytes, mas as chances disso são completamente desprezíveis. Da mesma forma, ao usar base64, se os bytes aleatórios forem exatamente justos \xff, então a base64 será apenas uma sequência de barras, mas isso também é bastante improvável. Se você se importa, pode ler mais de 10 bytes, reduzir as chances disso e reduzir o comprimento da saída resultante.
Ilkkachu
1
@ilkkachu Estou me referindo aos dois códigos. Se você ler bytes aleatórios e quiser algo com um comprimento específico depois de filtrar o que não deseja, não corte a fonte de dados até ter certeza de que conseguiu o que precisa. Como você diz, é muito improvável obter uma série de dez novas linhas /dev/urandom, mas o risco não é de 0%.
Kusalananda