Jak sprawdzić, czy wprowadzone hasło jest poprawnym hasłem dla tego użytkownika?

15

Scenariusz:

W skrypcie bash muszę sprawdzić, czy hasło podane przez użytkownika jest prawidłowym hasłem użytkownika.

Przypuszczam, że mam użytkownika A z hasłem PA. W skrypcie poprosiłem użytkownika A o podanie hasła, więc jak sprawdzić, czy wprowadzony ciąg jest naprawdę jego hasłem? ...

Maythux
źródło
Co masz na myśli przez prawidłowe hasło? Chcesz sprawdzić, czy tak naprawdę jest to hasło użytkownika?
AB
@AB ważne hasło = hasło logowania cokolwiek chcesz zadzwonić ... Mam na myśli to hasło dla tego użytkownika
Maythux,
@AB wiem, że będzie to dziura bezpieczeństwa, ale tu już znasz nazwę użytkownika również ... Innymi słowy jej tylko test, czy hasło jest dla tego użytkownika ..
Maythux
3
Oto twoja odpowiedź: unix.stackexchange.com/a/21728/107084
AB
Rozwiązanie oparte na expectat stackoverflow.com/a/1503831/320594 .
Jaime Hablutzel

Odpowiedzi:

15

Ponieważ chcesz to zrobić w skrypcie powłoki, kilka uwag w Jak sprawdzić hasło w systemie Linux? (na Unix.SE , sugerowany przez AB ) są szczególnie istotne:

Aby ręcznie sprawdzić, czy ciąg znaków jest naprawdę hasłem użytkownika, należy go zaszyfrować przy użyciu tego samego algorytmu skrótu, jak we wpisie cienia użytkownika, z taką samą solą jak we wpisie cienia użytkownika. Następnie można go porównać z hasłem przechowywanym w nim.

Napisałem kompletny, działający skrypt pokazujący, jak to zrobić.

  • Jeśli go nazwiesz chkpass, możesz uruchomić, a on przeczyta wiersz ze standardowego wejścia i sprawdzi, czy to hasło.chkpass useruser
  • Zainstaluj pakiet whois Zainstaluj whois , aby uzyskać mkpasswdnarzędzie, od którego zależy ten skrypt.
  • Ten skrypt musi być uruchomiony jako root, aby odnieść sukces.
  • Przed użyciem tego skryptu lub jego części do prawdziwej pracy zapoznaj się z uwagami bezpieczeństwa poniżej.
#!/usr/bin/env bash

xcorrect=0 xwrong=1 enouser=2 enodata=3 esyntax=4 ehash=5  IFS=$
die() {
    printf '%s: %s\n' "$0" "$2" >&2
    exit $1
}
report() {
    if (($1 == xcorrect))
        then echo 'Correct password.'
        else echo 'Wrong password.'
    fi
    exit $1
}

(($# == 1)) || die $esyntax "Usage: $(basename "$0") <username>"
case "$(getent passwd "$1" | awk -F: '{print $2}')" in
    x)  ;;
    '') die $enouser "error: user '$1' not found";;
    *)  die $enodata "error: $1's password appears unshadowed!";;
esac

if [ -t 0 ]; then
    IFS= read -rsp "[$(basename "$0")] password for $1: " pass
    printf '\n'
else
    IFS= read -r pass
fi

set -f; ent=($(getent shadow "$1" | awk -F: '{print $2}')); set +f
case "${ent[1]}" in
    1) hashtype=md5;;   5) hashtype=sha-256;;   6) hashtype=sha-512;;
    '') case "${ent[0]}" in
            \*|!)   report $xwrong;;
            '')     die $enodata "error: no shadow entry (are you root?)";;
            *)      die $enodata 'error: failure parsing shadow entry';;
        esac;;
    *)  die $ehash "error: password hash type is unsupported";;
esac

if [[ "${ent[*]}" = "$(mkpasswd -sm $hashtype -S "${ent[2]}" <<<"$pass")" ]]
    then report $xcorrect
    else report $xwrong
fi

Uwagi bezpieczeństwa

To może nie być właściwe podejście.

To, czy takie podejście należy uznać za bezpieczne i w inny sposób odpowiednie, zależy od szczegółowych informacji na temat przypadku użycia, którego nie podałeś (od tego momentu).

Nie został poddany audytowi.

Chociaż starałem się zachować ostrożność podczas pisania tego skryptu, nie został on odpowiednio sprawdzony pod kątem luk w zabezpieczeniach . Ma to służyć jako demonstracja i byłoby oprogramowaniem „alfa”, gdyby zostało wydane w ramach projektu. Ponadto...

Inny użytkownik, który „ogląda”, może odkryć sól użytkownika .

Ze względu na ograniczenia w sposobie mkpasswdakceptowania danych o soli, ten skrypt zawiera znaną lukę w zabezpieczeniach , którą możesz, lub nie, zaakceptować w zależności od przypadku użycia. Domyślnie użytkownicy Ubuntu i większości innych systemów GNU / Linux mogą przeglądać informacje o procesach uruchamianych przez innych użytkowników (w tym root), w tym argumenty wiersza poleceń. Ani dane wejściowe użytkownika, ani przechowywany skrót hasła nie są przekazywane jako argument wiersza polecenia do żadnego zewnętrznego narzędzia. Ale sól wyodrębniona z shadowbazy danych jest podawana jako argument wiersza polecenia mkpasswd, ponieważ jest to jedyny sposób, w jaki narzędzie przyjmuje sól jako dane wejściowe.

Gdyby

  • inny użytkownik w systemie lub
  • każdy, kto ma możliwość założenia dowolnego konta użytkownika (np. www-data) uruchomienia kodu, lub
  • każdy, kto w inny sposób może wyświetlić informacje o uruchomionych procesach (w tym ręcznie sprawdzając wpisy w /proc)

jest w stanie sprawdzić argumenty wiersza poleceń mkpasswduruchamiane przez ten skrypt, a następnie uzyskać kopię soli użytkownika z shadowbazy danych. Być może będą musieli zgadnąć, kiedy to polecenie zostanie uruchomione, ale czasami jest to możliwe.

Atakujący twoją solą nie jest tak zły jak atakujący twoją solą i haszem , ale nie jest idealny. Sól nie zapewnia wystarczającej ilości informacji, aby ktoś mógł odkryć twoje hasło. Ale pozwala komuś generować tęczowe tabele lub wstępnie obliczone skróty słownika właściwe dla tego użytkownika w tym systemie. Jest to początkowo bezwartościowe, ale jeśli twoje bezpieczeństwo zostanie później naruszone i zostanie uzyskany pełny skrót, można go później złamać, aby uzyskać hasło użytkownika, zanim będzie on miał szansę go zmienić.

Tak więc ta luka w zabezpieczeniach jest czynnikiem pogarszającym bardziej złożony scenariusz ataku, a nie w pełni podatną na atak luką. I możesz uznać, że powyższa sytuacja jest zbyt daleko posunięta. Ale niechętnie zalecam jakąkolwiek metodę do ogólnego, rzeczywistego użytkowania, która przecieka wszelkie niepubliczne dane /etc/shadowdo użytkownika innego niż root.

Możesz całkowicie uniknąć tego problemu poprzez:

  • pisanie część skryptu w Perlu lub jakiegoś innego języka, który umożliwia wywołanie funkcji C, jak pokazano w odpowiedzi Gilles jest do powiązanego Unix.SE pytanie , czy
  • pisanie całego skryptu / programu w takim języku, zamiast używania bash. (Na podstawie sposobu otagowania pytania wydaje się, że wolisz używać bash).

Uważaj, jak wywołać ten skrypt.

Jeśli zezwalasz niezaufanemu użytkownikowi na uruchomienie tego skryptu jako root lub na uruchomienie dowolnego procesu jako root, który wywołuje ten skrypt, bądź ostrożny . Zmieniając środowisko, mogą sprawić, że ten skrypt - lub dowolny skrypt uruchamiany jako root - zrobi wszystko . O ile nie można temu zapobiec, nie wolno zezwalać użytkownikom na podniesienie uprawnień do uruchamiania skryptów powłoki.

Zobacz 10.4. Shell Scripting Languages ​​(pochodne sh i csh) w David A. Wheeler Secure Programming for Linux and Unix HOWTO, aby uzyskać więcej informacji na ten temat. Podczas gdy jego prezentacja koncentruje się na skryptach setuid, inne mechanizmy mogą paść ofiarą tych samych problemów, jeśli nie zdezynfekują środowiska w odpowiedni sposób.

Inne notatki

Obsługuje odczytywanie skrótów tylko z shadowbazy danych.

Aby skrypt działał, hasła muszą być ukryte (tzn. Ich skróty powinny znajdować się w osobnym /etc/shadowpliku, który tylko root może czytać, a nie w nim /etc/passwd).

Tak powinno być zawsze w Ubuntu. W każdym razie, w razie potrzeby, skrypt można w prosty sposób rozszerzyć o odczyt skrótów haseł, passwdjak również shadow.

Pamiętaj IFSo modyfikowaniu tego skryptu.

Ustawiłem IFS=$na początku, ponieważ trzy dane w polu mieszającym wpisu cienia są oddzielone $.

  • Mają również czołowym $, dlatego typ hash i sól są "${ent[1]}"i "${ent[2]}"zamiast "${ent[0]}"i "${ent[1]}", odpowiednio.

Jedynymi miejscami w tym skrypcie, w których $IFSokreśla się, jak powłoka dzieli lub łączy słowa

  • gdy dane te zostaną podzielone na tablicę, inicjując je z niecytowanego $( )podstawienia polecenia w:

    set -f; ent=($(getent shadow "$1" | awk -F: '{print $2}')); set +f
  • gdy tablica jest odtwarzana w ciąg znaków w celu porównania z pełnym polem shadow, "${ent[*]}"wyrażenie w:

    if [[ "${ent[*]}" = "$(mkpasswd -sm $hashtype -S "${ent[2]}" <<<"$pass")" ]]

Jeśli zmodyfikujesz skrypt i sprawisz, że będzie on dzielił słowa (lub łączy słowa) w innych sytuacjach, musisz ustawić IFSróżne wartości dla różnych poleceń lub różnych części skryptu.

Jeśli nie będziesz o tym pamiętać i zakładasz, że $IFSustawiono zwykłą białą spację ( $' \t\n'), możesz spowodować, że skrypt będzie zachowywał się w dziwny sposób.

Eliah Kagan
źródło
8

Możesz nadużyć sudodo tego. sudoma -lopcję do testowania uprawnień sudo, które posiada użytkownik, oraz -Sdo wczytywania hasła ze standardowego wejścia. Jednak bez względu na poziom uprawnień użytkownika, jeśli zostanie pomyślnie uwierzytelniony, sudopowraca ze statusem wyjścia 0. Możesz więc przyjąć dowolny inny status wyjścia jako wskazówkę, że uwierzytelnianie nie działa (zakładając sudo, że sam nie ma żadnych problemów, takich jak błędy uprawnień lub nieprawidłowa sudoerskonfiguracja).

Coś jak:

#! /bin/bash
IFS= read -rs PASSWD
sudo -k
if sudo -lS &> /dev/null << EOF
$PASSWD
EOF
then
    echo 'Correct password.'
else 
    echo 'Wrong password.'
fi

Ten skrypt zależy w dużej mierze od sudoerskonfiguracji. Przyjąłem domyślną konfigurację. Rzeczy, które mogą spowodować awarię:

  • targetpwlub runaspwjest ustawiony
  • listpw jest never
  • itp.

Inne problemy to (dzięki Eliah):

  • Niepoprawne próby zostaną zarejestrowane /var/log/auth.log
  • sudonależy uruchomić jako użytkownik, dla którego się uwierzytelniasz. Chyba że masz sudouprawnienia do uruchamiania sudo -u foo sudo -lS, oznacza to, że będziesz musiał uruchomić skrypt jako użytkownik docelowy.

Powodem, dla którego użyłem tutaj dokumentów, jest utrudnienie podsłuchu. Zmienna jest stosowany jako część linii poleceń jest łatwiej ujawnił za pomocą toplub pslub innych narzędzi do kontroli procesów.

muru
źródło
2
W ten sposób wygląda doskonale. Chociaż nie będzie działać w systemach z nietypowymi sudokonfiguracjami (jak to mówisz) i bałaganem /var/log/auth.logz zapisami każdej próby, jest to szybkie, proste i wykorzystuje istniejące narzędzie, zamiast wymyślać koło na nowo (że tak powiem). Proponuję ustawienie IFS=na read, aby uniknąć fałszywych sukcesów dla spacją wyściełane wprowadzanych przez użytkownika i nie wyściełane haseł, a także fałszywe awarie dla spacją wyściełane miękkimi wejścia użytkownika i haseł. (Moje rozwiązanie miało podobny błąd.) Możesz także wyjaśnić, jak należy go uruchomić jako użytkownik, którego hasło jest sprawdzane.
Eliah Kagan
@EliahKagan każda nieudana próba jest rejestrowana, ale tak, masz rację co do wszystkiego innego.
muru
Pomyślne uwierzytelnienia są również rejestrowane. sudo -lpowoduje, że wpis jest zapisywany za pomocą „ COMMAND=list”. Btw (niepowiązane), chociaż nie jest to obiektywnie lepsze, użycie ciągu tutaj zamiast dokumentu tutaj osiąga ten sam cel bezpieczeństwa i jest bardziej zwarty. Ponieważ skrypt korzysta już z bashism, read -sa &>używanie if sudo -lS &>/dev/null <<<"$PASSWD"; thenjest nie mniej przenośne. Nie sugeruję, że powinieneś zmienić to, co masz (w porządku). Raczej wspominam o tym, aby czytelnicy wiedzieli, że mają również tę opcję.
Eliah Kagan
@EliahKagan ah. Pomyślałem, że sudo -lcoś wyłączyłem - być może raportowanie, gdy użytkownik próbuje uruchomić polecenie, którego nie wolno.
muru
@EliahKagan Rzeczywiście. Wcześniej używałem herestrów . Pracowałem tutaj przy błędnym przekonaniu, które doprowadziło do heredoków, ale potem postanowiłem je zatrzymać.
muru
5

Inna metoda (prawdopodobnie bardziej interesująca ze względu na treść teoretyczną niż praktyczne zastosowania).

Hasła użytkownika są przechowywane w /etc/shadow.

Hasła przechowywane tutaj są szyfrowane, w najnowszych wydaniach Ubuntu za pomocą SHA-512.

W szczególności, po utworzeniu hasła, hasło w postaci zwykłego tekstu jest solone i szyfrowane SHA-512.

Jednym z rozwiązań byłoby następnie zaszyfrowanie / zaszyfrowanie podanego hasła i dopasowanie go do hasła zaszyfrowanego użytkownika zapisanego we /etc/shadowwpisie danego użytkownika .

Aby szybko przedstawić sposób przechowywania haseł w każdym /etc/shadowwpisie użytkownika , oto przykładowy /etc/shadowwpis dla użytkownika fooz hasłem bar:

foo:$6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/:16566:0:99999:7:::
  • foo: Nazwa Użytkownika
  • 6: typ szyfrowania hasła
  • lWS1oJnmDlaXrx1F: sól szyfrująca hasło
  • h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/: SHA-512solone / szyfrowane hasło

Aby dopasować podane hasło bardla danego użytkownika foo, pierwszą rzeczą do zrobienia jest zdobycie soli:

$ sudo getent shadow foo | cut -d$ -f3
lWS1oJnmDlaXrx1F

Następnie należy uzyskać pełne solone / zaszyfrowane hasło:

$ sudo getent shadow foo | cut -d: -f2
$6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/

Następnie podane hasło może być solone / szyfrowane i dopasowane do hasła użytkownika solonego / szyfrowanego zapisanego w /etc/shadow:

$ python -c 'import crypt; print crypt.crypt("bar", "$6$lWS1oJnmDlaXrx1F")'
$6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/

Pasują! Wszystko umieszczone w bashskrypcie:

#!/bin/bash

read -p "Username >" username
IFS= read -p "Password >" password
salt=$(sudo getent shadow $username | cut -d$ -f3)
epassword=$(sudo getent shadow $username | cut -d: -f2)
match=$(python -c 'import crypt; print crypt.crypt("'"${password}"'", "$6$'${salt}'")')
[ ${match} == ${epassword} ] && echo "Password matches" || echo "Password doesn't match"

Wynik:

$ ./script.sh 
Username >foo
Password >bar
Password matches
$ ./script.sh 
Username >foo
Password >bar1
Password doesn't match
kos
źródło
1
sudo getent shadow>> sudo grep .... W przeciwnym razie miło. O tym myślałem pierwotnie, a potem stałem się zbyt leniwy.
muru
@muru Thanks. Zdecydowanie czuje się ładniejszy, tak zaktualizowany, ale nadal nie jestem pewien, czy w tym przypadku getent+ grep+ cut> grep+cut
kos
1
Eek. getentmogę cię wyszukać. Pozwoliłem sobie na edycję.
muru
@muru Tak, powinieneś mieć, teraz widzę sens!
Kos
2
Myślę, że jest to w rzeczywistości całkiem praktyczne, choć mogę być stronniczy: to ta sama metoda, której użyłem. Twoje wdrożenie i prezentacja są jednak zupełnie inne - jest to zdecydowanie cenna odpowiedź. Podczas gdy ja mkpasswdgenerowałem skrót z danych wprowadzanych przez użytkownika i istniejącej soli (i zawierałem dodatkowe funkcje i diagnostykę), zamiast tego kodowałeś prosty program Python implementujący mkpasswdnajważniejszą funkcjonalność i wykorzystałeś to. Zalecam ustawienie IFS=podczas czytania hasła i cytowanie za ${password}pomocą ", aby próby podania hasła za pomocą białych znaków nie spowodowały uszkodzenia skryptu.
Eliah Kagan