Czy używanie echa do przesyłania poufnych danych do chpasswd jest bezpieczne?

17

Próbuję masowo ustawić kilka haseł do kont użytkowników chpasswd. Hasła powinny być generowane losowo i drukowane na stdout(muszę je zapisać lub umieścić w magazynie haseł), a także przekazywane do chpasswd.

Naiwnie zrobiłbym to w ten sposób

{
  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)

Jednak martwię się o przekazanie nowego hasła jako argumentu wiersza poleceń echo, ponieważ argumenty są zwykle widoczne dla innych użytkowników w ps -aux(chociaż nigdy nie widziałem, aby echopojawiał się w nim jakikolwiek wiersz ps).

Czy istnieje alternatywny sposób przygotowania wartości do mojego zwróconego hasła, a następnie przekazania go do chpasswd?

Nils Werner
źródło
6
echojest wbudowaną powłoką. Nie pojawi się w tabeli procesów.
Kusalananda

Odpowiedzi:

15

Twój kod powinien być bezpieczny, ponieważ echonie pojawi się w tabeli procesów, ponieważ jest wbudowany w powłokę.

Oto alternatywne rozwiązanie:

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

Spowoduje to utworzenie twoich nazwisk uczniów i haseł, nbez przekazywania żadnych haseł w dowolnym wierszu polecenia dowolnego polecenia.

Te pastekleje użytkowych razem kilka plików jak kolumny i wkładki separatora między nimi. In Tutaj używamy :jako separatora i dajemy mu dwa „pliki” (podstawienia procesu). Pierwszy zawiera dane wyjściowe seqpolecenia, które tworzy 20 nazw użytkowników, a drugi zawiera dane wyjściowe potoku, który tworzy 20 losowych ciągów o długości 13.

Jeśli masz plik z już wygenerowanymi nazwami użytkowników:

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

Spowodują to zapisanie haseł i nazw użytkowników w pliku secret.txtzamiast wyświetlania wygenerowanych haseł w terminalu.

Kusalananda
źródło
2
To sprytne rozwiązanie, które nie wymaga echoani printfjednak kod jest nieco niewygodne do rozszyfrowania
Nils Werner
@NilsWerner Dodano nieco więcej wyjaśnień. Daj mi znać, jeśli jest coś, co chciałbym rozwinąć.
Kusalananda
1
Nie powinieneś jednak polegać na echowbudowanej powłoce. Tak jest w większości pocisków, ale absolutnie nie ma takiego wymagania.
Austin Hemmelgarn,
1
@Kusalananda Ciekawe, czy istnieje skuteczna różnica między tee secret.txt > >(chpasswd)i tee secret.txt | chpasswd? Ta ostatnia wydaje się o wiele bardziej powszechna, więc zastanawiam się, dlaczego zdecydowałeś się na zastąpienie procesu zamiast zwykłej rury
Christopher Shroba
1
@ChristopherShroba Nie ma innego powodu niż ten, który jest podobny do kodu, który był już w pytaniu (przy użyciu podstawienia procesu za pomocą tee). Teraz, gdy już to zauważyłeś, myślę, że rzeczywiście to zmienię (wygląda lepiej). Dzięki.
Kusalananda
8

echonajprawdopodobniej jest wbudowany w powłokę, więc nie pojawi się psjako osobny proces.

Ale nie musisz używać podstawiania poleceń, możesz po prostu otrzymać dane wyjściowe z potoku bezpośrednio do chpasswd:

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

Jeśli chcesz zmienić wiele haseł za jednym razem chpasswd, powtórzenie niezbędnych części powinno być łatwe. Lub przekształć go w funkcję:

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 

Na marginesie: head /dev/urandomto trochę dziwne, ponieważ urandomnie jest zorientowane liniowo. Może odczytać z niego nadmierną ilość bajtów, co wpłynie na pojęcie dostępnej entropii przez jądro, co z kolei może prowadzić do /dev/randomblokowania. Lepiej byłoby po prostu odczytać określoną ilość danych i użyć czegoś takiego jak base64konwersja losowych bajtów na znaki drukowalne (zamiast po prostu odrzucić około 3/4 bajtów, które otrzymujesz).

Coś takiego dałoby ci ok. 16 znaków i cyfr:

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

(to znaczy 16 mniej liczby +i /znaków na wyjściu base64. Szansa na jedno z nich wynosi 1/32 na postać, więc jeśli dobrze zrozumiem kombinatorykę, daje to około 99% szansy na pozostawienie co najmniej 14 znaków, i 99,99% szans na odejście co najmniej 12.)

ilkkachu
źródło
Twoje printfrozwiązanie nie robi tego, co robi mój oryginalny kod: zwraca wiele wierszy dla wielu nazw użytkowników, ale doceniam twoje uwagi na temathead urandom
Nils Werner
@NilsWerner, cóż, uważam, że rozszerzenie dla wielu użytkowników jest oczywiste. Ale bez względu na to, moglibyśmy stworzyć funkcję tworzenia pary nazwa użytkownika: hasło dla wielu użytkowników za jednym razem. (patrz edycja)
ilkkachu
3
Naprawdę nie możesz polegać na pierwszych 10 „liniach” z urandomu zawierających wystarczającą liczbę znaków, które chcesz. Po prostu uruchom zamiast tego urandom bezpośrednio przez tr.
Kusalananda
@Kusalananda, masz na myśli mój head -c 10 | base64rurociąg czy Nilsa head | tr? 10 „linii” losowych bajtów najprawdopodobniej będzie mieć setki bajtów (biorąc pod uwagę, że tylko 1 na 256 to znaki nowej linii), chociaż teoretycznie może to być dokładnie 10 bajtów, ale szanse na to są całkowicie znikome. Podobnie przy użyciu base64, jeśli przypadkowe bajty są powiedzmy po prostu \xff, to base64 będzie tylko ciągiem ukośników, ale to również mało prawdopodobne. Jeśli cię to obchodzi, możesz odczytać więcej niż 10 bajtów, zmniejszyć prawdopodobieństwo tego, a następnie skrócić długość wynikowego wyniku.
ilkkachu
1
@ilkkachu Mam na myśli oba kody. Jeśli czytasz losowe bajty i po odfiltrowaniu tego, czego nie chcesz, potrzebujesz czegoś o określonej długości, nie odcinaj źródła danych, dopóki nie upewnisz się, że masz to, czego potrzebujesz. Jak mówisz, jest bardzo mało prawdopodobne , że dostaniesz ciąg dziesięciu nowych linii /dev/urandom, ale ryzyko nie wynosi 0%.
Kusalananda