Jaki jest najbezpieczniejszy i najprostszy sposób, aby hasło wpisane przez użytkownika w bash stało się częścią standardowego programu?

12

Szukam (1) najbezpieczniejszego i (2) najprostszego sposobu, aby użytkownik wpisał hasło w wierszu polecenia powłoki bash i aby hasło to stało się częścią standardowego programu.

Tak powinno wyglądać standardowe wejście:, {"username":"myname","password":"<my-password>"}gdzie <my-password>wpisane jest polecenie powłoki. Gdybym miał kontrolę nad programem stdin, mógłbym go zmodyfikować, aby bezpiecznie monitować o hasło i umieścić je na swoim miejscu, ale dalszy ciąg jest standardowym poleceniem ogólnego przeznaczenia.

Rozważyłem i odrzuciłem podejścia wykorzystujące:

  • użytkownik wpisujący hasło w wierszu polecenia: hasło zostanie wyświetlone na ekranie, a także widoczne dla wszystkich użytkowników za pośrednictwem „ps”
  • interpolacja zmiennej powłoki na argument do programu zewnętrznego (np. ...$PASSWORD...): hasło byłoby nadal widoczne dla wszystkich użytkowników za pośrednictwem „ps”
  • zmienne środowiskowe (jeśli pozostaną w środowisku): hasło będzie widoczne dla wszystkich procesów potomnych; nawet wiarygodne procesy mogą ujawnić hasło, jeśli zrzucą podstawowe lub środowiskowe zmienne w ramach diagnostyki
  • hasło przechowywane w pliku przez dłuższy czas, nawet plik z wąskimi uprawnieniami: użytkownik może przypadkowo ujawnić hasło, a użytkownik root może przypadkowo zobaczyć hasło

Podam moje obecne rozwiązanie jako odpowiedź poniżej, ale chętnie wybiorę lepszą odpowiedź, jeśli ktoś ją wymyśli. Myślę, że powinno być coś prostszego, a może ktoś zauważy obawy dotyczące bezpieczeństwa, za którymi tęskniłem.

Jim Hoagland
źródło
(Pełny przypadek użycia polega na tym, że hasło musi być częścią treści POST do serwera HTTPS, ale nie chcę, aby to pytanie było zbyt szczegółowe. Stdin staje się treścią POST poprzez curl "-d @ - „.)
Jim Hoagland,

Odpowiedzi:

14

Z bashlub 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

Bez IFS=, readby rozebrać początkowe i końcowe spacje z hasłem podczas pisania.

Bez -rtego przetwarzałby ukośniki odwrotne jako znak cytowania.

Chcesz mieć pewność, że będziesz czytać tylko z terminala.

echonie można używać niezawodnie. W basha zsh, printfjest wbudowane tak, że wiersz polecenia nie pokaże na wyjściu ps.

W bashmusisz zacytować, $passwordponieważ w przeciwnym razie zostanie zastosowany operator split + glob .

To wciąż źle, ponieważ trzeba zakodować ten ciąg jako JSON. Na przykład problem polegałby na podwójnym cytowaniu i ukośniku odwrotnym. Prawdopodobnie musisz się martwić o kodowanie tych znaków. Czy twój program oczekuje ciągów UTF-8? Co wysyła twój terminal?

Aby dodać ciąg zachęty zsh:

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

Z bash:

IFS= read -rsp 'Please enter a password: ' password
Stéphane Chazelas
źródło
printf - to jest to, czego potrzebujemy, aby uniknąć wywołania perla - dobra wskazówka. Dobrze, że masz je tam unset passwordi set +atam, choć można je pominąć, jeśli możesz przyjąć więcej założeń na temat bieżącej powłoki. Dobra uwaga na temat opcji -r do czytania i stawiania IFS=przed nią w celu uzyskania większej ogólności przy użyciu różnych haseł. Dobrze jest przejść z / dev / tty, aby wymusić wejście z terminala.
Jim Hoagland,
1
tak, nadal mamy problem z podwójnym cudzysłowem i odwrotnym ukośnikiem oraz może z kilkoma innymi znakami. Musielibyśmy je zakodować przed umieszczeniem w polu podwójnego cudzysłowu w obiekcie blob JSON. Nie jestem pewien, czy curl oczekuje UTF-8.
Jim Hoagland,
1
python3 -c 'import json; print(json.dumps({"username": "myname", "password": input()}))', jeśli masz wokół siebie Pythona. W przypadku Python 2, s / input / raw_input /. Jeśli potokujesz hasło do tego polecenia, wypluje ono odpowiedni JSON.
Kevin,
Kevin, to byłaby dobra sugestia, jeśli możemy bezpiecznie wprowadzić hasło do standardowego wejścia i nie przeszkadza to wywoływanie Pythona. To buduje JSON w locie. Działałoby to dobrze, jeśli korzystasz z getpass.getpass () w Pythonie, chociaż tracisz możliwość wielokrotnego używania zapisanego hasła.
Jim Hoagland,
2

Oto rozwiązanie, które mam (zostało przetestowane):

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

Wyjaśnienie:

  1. read -sczyta linię standardowego (hasła) bez echa linii na ekranie. Przechowuje w zmiennej powłoki PASSWORD.
  2. echo -n $PASSWORDumieszcza hasło na stdout bez nowej linii. (echo to wbudowane polecenie powłoki, więc nie jest tworzony nowy proces, więc (AFAIK) hasło jako argument do echa nie będzie pokazywane na ps.)
  3. Perl jest wywoływany i odczytuje hasło ze standardowego na $_
  4. perl wstawia hasło $_do pełnego tekstu i wypisuje je na standardowe wyjście

Jeśli chodzi o test POST HTTPS, druga linia będzie wyglądać tak:

echo -n $PASSWORD | perl -e '$_=<>; print "{\"username\":\"myname\",\"password\":\"$_\"}"' | curl -H "Content-Type: application/json" -d@- -X POST <url-to-post-to>
Jim Hoagland
źródło
3
To wygląda całkiem nieźle. Po prostu zauważę, że nie ma powodu, aby w ogóle wywoływać Perla; możesz doskonale zrobić coś takiego printf '{"username":"myname","password":"%s"}' $PASSWORD, co zachowuje te same właściwości bezpieczeństwa i oszczędza proces zewnętrzny.
Tom Hunt
2
Jeśli chcesz uogólnić rozwiązanie readpoleceń, które nie obsługują flagi -s, możesz użyć „stty -echo; przeczytaj HASŁO; stty echo”
Jeff Schaller
2
Rura rozpoczyna dwa nowe procesy, po jednym dla każdej strony. Użyć tu-string zamiast: perl -e '...' <<< "$PASSWORD".
chepner
2
@chepner, <<<w bashprzechowuje zawartość w tymczasowym pliku, który jest gorszy.
Stéphane Chazelas,
1
Według TLDP tworzy plik, ale nie jest możliwy do odczytania przez żadne inne procesy. „Tutaj dokumenty tworzą pliki tymczasowe, ale pliki te są usuwane po otwarciu i nie są dostępne dla żadnego innego procesu.” tldp.org/LDP/abs/html/here-docs.html tuż po przykładzie 19-12.
dragon788
0

Jeśli twoja wersja readnie obsługuje -s, wypróbuj sposób zgodny z POSIX opisany w tej odpowiedzi .

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

Ma set +azapobiegać automatycznemu „eksportowaniu” zmiennej do środowiska. Powinieneś sprawdzić strony podręcznika stty, ponieważ dostępnych jest wiele opcji. <<<"$passwd"Jest cytowany z powodu dobrych hasła mogą mieć miejsca. Ostatnie echopo włączeniu stty echoma rozpocząć następną komendę / wyjście w nowym wierszu.

dragon788
źródło