Wie überprüfe ich, ob das eingegebene Passwort ein gültiges Passwort für diesen Benutzer ist?

14

Das Szenario:

In einem Bash-Skript muss ich überprüfen, ob ein von einem Benutzer angegebenes Kennwort ein gültiges Benutzerkennwort ist.

Ich nehme an, ich habe einen Benutzer A mit Passwort PA. In dem Skript habe ich Benutzer A gebeten, sein Passwort einzugeben. Wie kann man also überprüfen, ob der eingegebene String wirklich sein Passwort ist?

Maythux
quelle
Was meinst du mit einem gültigen Passwort? Möchten Sie testen, ob es sich wirklich um das Kennwort des Benutzers handelt?
AB
@AB gültiges Passwort = Login-Passwort, was auch immer Sie anrufen möchten ... Ich meine, es ist ein Passwort für diesen Benutzer
Maythux
@AB Ich weiß, es wird eine Sicherheitslücke sein, aber hier kennen Sie bereits auch den Benutzernamen ... Mit anderen Worten, es ist nur ein Test, ob das Passwort für diesen Benutzer ist ..
Maythux
3
Hier ist Ihre Antwort: unix.stackexchange.com/a/21728/107084
AB
Eine Lösung auf Basis expectbei stackoverflow.com/a/1503831/320594 .
Jaime Hablutzel

Antworten:

14

Da Sie dies in einem Shell-Skript tun möchten, finden Sie unter Wie überprüfe ich das Kennwort unter Linux? Einige Beiträge . (auf Unix.SE , vorgeschlagen von AB ) sind besonders relevant:

Um manuell zu überprüfen, ob es sich bei einer Zeichenfolge tatsächlich um ein Benutzerkennwort handelt, müssen Sie es mit demselben Hash-Algorithmus wie im Schatteneintrag des Benutzers und mit demselben Salt wie im Schatteneintrag des Benutzers hashen. Dann kann es mit dem dort gespeicherten Passwort-Hash verglichen werden.

Ich habe ein komplettes, funktionierendes Skript geschrieben, das zeigt, wie das geht.

  • Wenn Sie es benennen chkpass, können Sie es ausführen und es liest eine Zeile von der Standardeingabe und überprüft, ob es das Passwort ist.chkpass useruser
  • Installieren Sie das whois- Installieren Sie whois Paket, um das mkpasswdDienstprogramm zu erhalten, von dem dieses Skript abhängt.
  • Dieses Skript muss als root ausgeführt werden, um erfolgreich zu sein.
  • Bevor Sie dieses Skript oder einen Teil davon für echte Arbeit verwenden, lesen Sie bitte die Sicherheitshinweise unten.
#!/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

Sicherheitshinweise

Es könnte nicht der richtige Ansatz sein.

Ob ein solcher Ansatz als sicher und ansonsten angemessen angesehen werden sollte oder nicht, hängt von den Details Ihres Anwendungsfalls ab, die Sie (zum Zeitpunkt des Schreibens) nicht angegeben haben.

Es wurde nicht geprüft.

Obwohl ich versucht habe, beim Schreiben dieses Skripts Vorsicht walten zu lassen, wurde es nicht ordnungsgemäß auf Sicherheitslücken überprüft . Es ist als Demonstration gedacht und wäre "Alpha" -Software, wenn es als Teil eines Projekts veröffentlicht würde. Außerdem...

Ein anderer Benutzer, der "zusieht", ist möglicherweise in der Lage, das Salz des Benutzers zu entdecken .

Aufgrund von Einschränkungen beim mkpasswdAkzeptieren von Salt-Daten enthält dieses Skript eine bekannte Sicherheitslücke , die Sie je nach Anwendungsfall für akzeptabel halten oder nicht. Standardmäßig können Benutzer auf Ubuntu und den meisten anderen GNU / Linux-Systemen Informationen zu Prozessen anzeigen, die von anderen Benutzern (einschließlich root) ausgeführt werden, einschließlich ihrer Befehlszeilenargumente. Weder die Eingabe des Benutzers noch der gespeicherte Kennwort-Hash werden als Befehlszeilenargument an ein externes Dienstprogramm übergeben. Das aus der Datenbank extrahierte Salt wird jedoch als Befehlszeilenargument für angegeben , da das Dienstprogramm nur so ein Salt als Eingabe akzeptiert.shadowmkpasswd

Wenn

  • ein anderer Benutzer im System oder
  • Jeder, der die Möglichkeit hat, ein Benutzerkonto zu www-dataerstellen (z. B. ), führt seinen Code aus, oder
  • Jeder, der ansonsten Informationen zu laufenden Prozessen anzeigen kann (auch durch manuelles Überprüfen von Einträgen in /proc)

kann die Befehlszeilenargumente so prüfen, mkpasswdwie sie von diesem Skript ausgeführt werden, und dann eine Kopie des Salt des Benutzers aus der shadowDatenbank abrufen. Sie müssen möglicherweise raten können, wann dieser Befehl ausgeführt wird, aber das ist manchmal erreichbar.

Ein Angreifer mit Ihrem Salz ist nicht so schlecht wie ein Angreifer mit Ihrem Salz und Hasch , aber es ist nicht ideal. Das Salz liefert nicht genug Informationen, damit jemand Ihr Passwort herausfinden kann. Aber es erlaubt jemandem, Regenbogentabellen oder vorberechnete Wörterbuch-Hashes zu generieren, die für diesen Benutzer auf diesem System spezifisch sind. Dies ist anfangs wertlos. Wenn Ihre Sicherheit jedoch zu einem späteren Zeitpunkt gefährdet wird und der vollständige Hash erhalten wird, kann das Kennwort des Benutzers möglicherweise schneller geknackt werden, bevor er die Möglichkeit hat, es zu ändern.

Daher ist diese Sicherheitslücke ein erschwerender Faktor in einem komplexeren Angriffsszenario und keine vollständig ausnutzbare Sicherheitsanfälligkeit. Und Sie könnten die obige Situation für weit hergeholt halten. Aber ich bin nur ungern jede Methode für die allgemeinen, realen Einsatz zu empfehlen , dass Lecks jegliche nicht-öffentliche Daten aus /etc/shadowzu einem Nicht-Root - Benutzer.

Sie können dieses Problem vollständig vermeiden, indem Sie:

  • Schreiben eines Teils Ihres Skripts in Perl oder einer anderen Sprache, mit der Sie C-Funktionen aufrufen können, wie in Gilles 'Antwort auf die zugehörige Unix.SE-Frage gezeigt , oder
  • Schreiben Sie Ihr gesamtes Skript / Programm in einer solchen Sprache, anstatt bash zu verwenden. (Basierend auf der Art und Weise, wie Sie die Frage markiert haben, scheint es, dass Sie Bash bevorzugen.)

Seien Sie vorsichtig, wie Sie dieses Skript aufrufen.

Seien Sie vorsichtig, wenn Sie einem nicht vertrauenswürdigen Benutzer erlauben, dieses Skript als Root oder einen Prozess als Root auszuführen, der dieses Skript aufruft . Durch Ändern der Umgebung können sie dieses Skript - oder jedes Skript, das als Root ausgeführt wird - zu beliebigen Aktionen veranlassen . Sofern Sie dies nicht verhindern können, dürfen Sie Benutzern keine erhöhten Berechtigungen für die Ausführung von Shell-Skripten gewähren.

Siehe 10.4. Weitere Informationen zu Shell-Skriptsprachen (sh- und csh-Derivate) finden Sie in David A. Wheelers Secure Programming für Linux und Unix HOWTO . Während sich sein Vortrag auf Setuid-Skripte konzentriert, können andere Mechanismen denselben Problemen zum Opfer fallen, wenn sie die Umgebung nicht ordnungsgemäß bereinigen.

Weitere Hinweise

Es unterstützt nur das Lesen von Hashes aus der shadowDatenbank.

Passwörter müssen mit einem Shadow versehen sein, damit dieses Skript funktioniert (dh ihre Hashes sollten sich in einer separaten /etc/shadowDatei befinden, die nur root lesen kann, nicht in /etc/passwd).

Dies sollte in Ubuntu immer der Fall sein. In jedem Fall kann das Skript , wenn nötig werden , um zu lesen Passwort - Hashes von trivialer Weise erweitert passwdsowie shadow.

Halten Sie IFSdaran , wenn Sie dieses Skript zu ändern.

Ich setze IFS=$am Anfang, da die drei Daten im Hashfeld eines Schatteneintrags durch getrennt sind $.

  • Sie haben auch einen führenden $, weshalb der Hash-Typ und das Salz eher "${ent[1]}"und "${ent[2]}"als "${ent[0]}"und "${ent[1]}"sind.

Die einzigen Stellen in diesem Skript, an denen $IFSfestgelegt wird, wie die Shell Wörter aufteilt oder kombiniert

  • Wenn diese Daten in ein Array aufgeteilt werden, initialisieren Sie sie anhand der nicht zitierten $( )Befehlsersetzung in:

    set -f; ent=($(getent shadow "$1" | awk -F: '{print $2}')); set +f
  • Wenn das Array in eine Zeichenfolge rekonstruiert wird, die mit dem vollständigen Feld von verglichen werden soll shadow, wird der folgende "${ent[*]}"Ausdruck verwendet:

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

Wenn Sie das Skript ändern und in anderen Situationen eine Wortteilung (oder Wortverbindung) durchführen lassen, müssen Sie IFSfür verschiedene Befehle oder verschiedene Teile des Skripts unterschiedliche Werte festlegen.

Wenn Sie dies nicht berücksichtigen und davon ausgehen $IFS, dass das übliche Leerzeichen ( $' \t\n') verwendet wird, kann dies dazu führen, dass sich Ihr Skript auf merkwürdige Weise verhält.

Eliah Kagan
quelle
8

Sie können dafür missbrauchen sudo. sudohat die -lOption, die sudo-Berechtigungen des Benutzers zu testen und -Sdas Kennwort von stdin einzulesen. Unabhängig von der Berechtigungsstufe sudokehrt der Benutzer bei erfolgreicher Authentifizierung mit dem Beendigungsstatus 0 zurück. Sie können also jeden anderen Beendigungsstatus als Hinweis darauf verwenden, dass die Authentifizierung nicht funktioniert hat (vorausgesetzt, dass sudoselbst keine Probleme vorliegen, z. B. Berechtigungsfehler oder ungültige sudoersKonfiguration).

Etwas wie:

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

Dieses Skript hängt stark von der sudoersKonfiguration ab. Ich habe das Standard-Setup übernommen. Dinge, die dazu führen können, dass es fehlschlägt:

  • targetpwoder runaspwgesetzt ist
  • listpw ist never
  • etc.

Andere Probleme sind (danke Eliah):

  • Die falschen Versuche werden angemeldet /var/log/auth.log
  • sudomuss als der Benutzer ausgeführt werden, für den Sie sich authentifizieren. Wenn Sie nicht über die sudoerforderlichen Berechtigungen verfügen , müssen Sie sudo -u foo sudo -lSdas Skript als Zielbenutzer ausführen.

Der Grund, warum ich hier-docs verwendet habe, ist, das Abhören zu behindern. Eine Variable, die als Teil der Befehlszeile verwendet wird, lässt sich leichter mit topoder psoder anderen Tools zum Überprüfen von Prozessen aufdecken.

muru
quelle
2
Dieser Weg sieht hervorragend aus. Obwohl es nicht auf Systemen mit ungewöhnlichen sudoKonfigurationen (wie Sie sagen) und /var/log/auth.logAufzeichnungen über jeden Versuch funktioniert , ist dies schnell, einfach und verwendet ein vorhandenes Dienstprogramm, anstatt das Rad neu zu erfinden (sozusagen). Ich schlage vor , Einstellung IFS=für readfalsche Erfolge für Leer gepolsterte Benutzereingaben und unwattierter Passwörter zu verhindern, sowie falsche Ausfälle für Leerzeichen gepolsterte Benutzereingabe und gepolsterte Passwörter. (Meine Lösung hatte einen ähnlichen Fehler.) Möglicherweise möchten Sie auch erläutern, wie sie als Benutzer ausgeführt werden muss, dessen Kennwort überprüft wird.
Eliah Kagan
@EliahKagan Jeder fehlgeschlagene Versuch wird protokolliert, aber in allen anderen Punkten hast du recht.
muru
Die erfolgreichen Authentifizierungen werden ebenfalls protokolliert. sudo -lbewirkt, dass ein Eintrag mit " COMMAND=list" geschrieben wird. Übrigens (ohne Bezug), obwohl dies objektiv nicht besser ist, erreicht die Verwendung einer Here-Zeichenfolge anstelle eines Here-Dokuments dasselbe Sicherheitsziel und ist kompakter. Da das Skript bereits die bashisms verwendet read -sund &>unter Verwendung von if sudo -lS &>/dev/null <<<"$PASSWD"; thennicht weniger tragbar. Ich schlage nicht vor, Sie sollten ändern, was Sie haben (es ist in Ordnung). Ich erwähne es eher, damit die Leser wissen, dass sie diese Option auch haben.
Eliah Kagan,
@EliahKagan ah. Ich dachte, dass sudo -letwas deaktiviert ist - vielleicht wurde gemeldet, wenn ein Benutzer versucht, einen Befehl auszuführen, für den er keine Berechtigung hat.
muru
@ EliahKagan In der Tat. Ich habe Herestrings schon einmal so benutzt . Ich hatte hier ein Missverständnis, das zu Heredocs führte, aber dann beschloss ich, sie trotzdem zu behalten.
muru
4

Eine andere Methode (wahrscheinlich interessanter für ihre theoretischen Inhalte als für ihre praktischen Anwendungen).

Benutzerpasswörter werden in gespeichert /etc/shadow.

Die hier gespeicherten Passwörter werden verschlüsselt, wobei in den neuesten Ubuntu-Versionen verwendet wird SHA-512.

Insbesondere wird bei der Kennworterstellung das Kennwort im Klartext gesalzen und verschlüsselt SHA-512.

Eine Lösung wäre dann, das gegebene Passwort zu salzen / verschlüsseln und es mit dem verschlüsselten Passwort des Benutzers abzugleichen, das im /etc/shadowEintrag des gegebenen Benutzers gespeichert ist .

Um einen schnellen Überblick über die Speicherung der Kennwörter in den einzelnen Benutzereinträgen zu erhalten /etc/shadow, finden Sie hier ein Beispiel /etc/shadowfür einen Benutzer foomit Kennwort bar:

foo:$6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/:16566:0:99999:7:::
  • foo: Nutzername
  • 6: Verschlüsselungstyp des Passworts
  • lWS1oJnmDlaXrx1F: Passworts Verschlüsselungssalz
  • h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/: SHA-512gesalzenes / verschlüsseltes Passwort

Um dem angegebenen Passwort barfür den angegebenen Benutzer foozu entsprechen, müssen Sie zunächst das Salt abrufen:

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

Dann sollte man das voll gesalzene / verschlüsselte Passwort bekommen:

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

Dann kann das angegebene Passwort gesalzen / verschlüsselt und mit dem Passwort des gesalzen / verschlüsselten Benutzers abgeglichen werden, das gespeichert ist in /etc/shadow:

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

Sie passen! Alles in ein bashSkript geschrieben:

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

Ausgabe:

$ ./script.sh 
Username >foo
Password >bar
Password matches
$ ./script.sh 
Username >foo
Password >bar1
Password doesn't match
kos
quelle
1
sudo getent shadow>> sudo grep .... Ansonsten schön. Das ist, woran ich ursprünglich gedacht habe und dann zu faul geworden bin.
muru
@muru Danke. Fühlt sich auf jeden Fall besser an, also aktualisiert, aber ich bin mir immer noch nicht sicher, ob in diesem Fall getent+ grep+ cut> grep+cut
kos
1
Eek. getentkann die Suche für Sie tun. Ich habe mir erlaubt, zu redigieren.
muru
@muru Ja, das hättest du in der Tat tun sollen, jetzt verstehe ich den Punkt!
Kos
2
Ich denke, das ist eigentlich ganz praktisch, obwohl ich voreingenommen sein kann: Es ist die gleiche Methode, die ich verwendet habe. Ihre Implementierung und Präsentation sind jedoch sehr unterschiedlich - dies ist definitiv eine wertvolle Antwort. Während ich früher mkpasswdeinen Hash aus Benutzereingaben und dem vorhandenen Salt generiert habe (und zusätzliche Funktionen und Diagnosen enthielt), haben Sie stattdessen ein einfaches Python-Programm implementiertmkpasswd das die wichtigsten Funktionen und diese verwendet. Ich empfehle Ihnen , gesetzt , IFS=wenn die Passworteingabe zu lesen, und Zitat ${password}mit ", so Kennwortversuche mit Leerzeichen nicht das Skript brechen.
Eliah Kagan