Comment vérifier que le mot de passe entré est un mot de passe valide pour cet utilisateur?

15

Le scénario:

Dans un script bash, je dois vérifier si un mot de passe donné par un utilisateur est un mot de passe utilisateur valide.

Je suppose que j'ai un utilisateur A avec le mot de passe PA .. Dans le script, j'ai demandé à l'utilisateur A d'entrer son mot de passe, alors comment vérifier si la chaîne saisie est vraiment son mot de passe? ...

Maythux
la source
Que voulez-vous dire par mot de passe valide? Vous voulez tester s'il s'agit bien du mot de passe de l'utilisateur?
AB
@AB mot de passe valide = mot de passe de connexion comme vous voulez appeler ... Je veux dire son mot de passe pour cet utilisateur
Maythux
@AB Je sais que ce sera une faille de sécurité mais ici vous connaissez déjà le nom d'utilisateur aussi ... En d'autres termes, c'est juste un test si le mot de passe est pour cet utilisateur ..
Maythux
3
Voici votre réponse: unix.stackexchange.com/a/21728/107084
AB
Une solution basée sur expectau stackoverflow.com/a/1503831/320594 .
Jaime Hablutzel

Réponses:

14

Puisque vous voulez le faire dans un script shell, quelques contributions dans Comment vérifier le mot de passe avec Linux? (sur Unix.SE , suggéré par AB ) sont particulièrement pertinents:

Pour vérifier manuellement si une chaîne est vraiment le mot de passe d'un utilisateur, vous devez la hacher avec le même algorithme de hachage que dans l'entrée reflet de l'utilisateur, avec le même sel que dans l'entrée reflet de l'utilisateur. Ensuite, il peut être comparé au hachage de mot de passe qui y est stocké.

J'ai écrit un script complet et fonctionnel montrant comment procéder.

  • Si vous le nommez chkpass, vous pouvez l'exécuter et il lira une ligne à partir de l'entrée standard et vérifiera s'il s'agit du mot de passe.chkpass useruser
  • Installez le package whois Installer whois pour obtenir l' mkpasswdutilitaire dont dépend ce script.
  • Ce script doit être exécuté en tant que root pour réussir.
  • Avant d'utiliser ce script ou une partie de celui-ci pour effectuer un travail réel, veuillez consulter les notes de sécurité ci-dessous.
#!/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

Notes de sécurité

Ce n'est peut-être pas la bonne approche.

La question de savoir si une approche comme celle-ci doit être considérée comme sûre et appropriée dépend des détails de votre cas d'utilisation que vous n'avez pas fournis (au moment de la rédaction de ce document).

Il n'a pas été vérifié.

Bien que j'aie essayé d'être prudent lors de l'écriture de ce script, il n'a pas été correctement audité pour les failles de sécurité . Il est conçu comme une démonstration et serait un logiciel "alpha" s'il était publié dans le cadre d'un projet. En outre...

Un autre utilisateur qui "regarde" peut découvrir le sel de l'utilisateur .

En raison des limites dans la façon dont les mkpasswddonnées de sel sont acceptées , ce script contient une faille de sécurité connue , que vous pouvez ou non considérer comme acceptable selon le cas d'utilisation. Par défaut, les utilisateurs d'Ubuntu et de la plupart des autres systèmes GNU / Linux peuvent afficher des informations sur les processus exécutés par d'autres utilisateurs (y compris root), y compris leurs arguments de ligne de commande. Ni l'entrée de l'utilisateur ni le hachage de mot de passe stocké ne sont transmis en tant qu'argument de ligne de commande à un utilitaire externe. Mais le sel , extrait de la shadowbase de données, est donné comme argument de ligne de commande mkpasswd, car c'est la seule façon dont l'utilitaire accepte un sel en entrée.

Si

  • un autre utilisateur du système, ou
  • toute personne ayant la capacité de faire www-dataexécuter un code à n'importe quel compte d'utilisateur (par exemple, ), ou
  • toute personne qui autrement peut afficher des informations sur les processus en cours (y compris en inspectant manuellement les entrées dans /proc)

est en mesure de vérifier les arguments de la ligne de commande mkpasswdlorsqu'il est exécuté par ce script, puis ils peuvent obtenir une copie du sel de l'utilisateur à partir de la shadowbase de données. Ils peuvent devoir deviner quand cette commande est exécutée, mais c'est parfois réalisable.

Un attaquant avec votre sel n'est pas aussi mauvais qu'un attaquant avec votre sel et votre haschisch , mais ce n'est pas idéal. Le sel ne fournit pas suffisamment d'informations pour que quelqu'un découvre votre mot de passe. Mais cela permet à quelqu'un de générer des tables arc-en-ciel ou des hachages de dictionnaire pré-calculés spécifiques à cet utilisateur sur ce système. Cela ne vaut initialement rien, mais si votre sécurité est compromise à une date ultérieure et que le hachage complet est obtenu, il pourrait ensuite être craqué plus rapidement pour obtenir le mot de passe de l'utilisateur avant qu'il ne puisse le changer.

Ainsi, cette faille de sécurité est un facteur aggravant dans un scénario d'attaque plus complexe plutôt qu'une vulnérabilité pleinement exploitable. Et vous pourriez considérer la situation ci-dessus comme tirée par les cheveux. Mais je suis réticent à recommander une méthode pour une utilisation générale dans le monde réel qui fuit toutes les données non publiques d' /etc/shadowun utilisateur non root.

Vous pouvez éviter complètement ce problème en:

  • écrire une partie de votre script en Perl ou dans un autre langage qui vous permet d'appeler des fonctions C, comme indiqué dans la réponse de Gilles à la question Unix.SE connexe , ou
  • écrire tout votre script / programme dans un tel langage, plutôt que d'utiliser bash. (Selon la façon dont vous avez marqué la question, il semble que vous préfériez utiliser bash.)

Faites attention à la façon dont vous appelez ce script.

Si vous autorisez un utilisateur non fiable à exécuter ce script en tant que root ou à exécuter un processus en tant que root qui appelle ce script, soyez prudent . En modifiant l'environnement, ils peuvent faire en sorte que ce script - ou tout script qui s'exécute en tant que root - fasse n'importe quoi . À moins que vous ne puissiez empêcher cela, vous ne devez pas accorder aux utilisateurs des privilèges élevés pour exécuter des scripts shell.

Voir 10.4. Langages de script Shell (dérivés sh et csh) dans le guide de programmation sécurisée de David A. Wheeler pour Linux et Unix pour plus d'informations à ce sujet. Bien que sa présentation se concentre sur les scripts setuid, d'autres mécanismes peuvent être la proie de certains des mêmes problèmes s'ils ne désinfectent pas correctement l'environnement.

Autres notes

Il prend en charge la lecture des hachages à partir de la shadowbase de données uniquement.

Les mots de passe doivent être masqués pour que ce script fonctionne (c'est-à-dire que leurs hachages doivent être dans un /etc/shadowfichier séparé que seul root peut lire, pas dans /etc/passwd).

Cela devrait toujours être le cas dans Ubuntu. Dans tous les cas, si nécessaire, le script peut être étendu de manière triviale pour lire les hachages de mot de passe passwdainsi que shadow.

Gardez IFSà l'esprit lorsque vous modifiez ce script.

J'ai défini IFS=$au début, car les trois données du champ de hachage d'une entrée fantôme sont séparées par $.

  • Ils ont également un leader $, c'est pourquoi le type de hachage et le sel sont "${ent[1]}"et "${ent[2]}"plutôt que "${ent[0]}"et "${ent[1]}", respectivement.

Les seuls endroits dans ce script où $IFSdétermine la façon dont le shell divise ou combine les mots sont

  • lorsque ces données sont divisées en un tableau, en l'initialisant à partir de la $( )substitution de commande non citée dans:

    set -f; ent=($(getent shadow "$1" | awk -F: '{print $2}')); set +f
  • lorsque le tableau est reconstitué en une chaîne à comparer au champ complet de shadow, l' "${ent[*]}"expression dans:

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

Si vous modifiez le script et faites-le effectuer la division (ou la jonction de mots) dans d'autres situations, vous devrez définir IFSdes valeurs différentes pour différentes commandes ou différentes parties du script.

Si vous ne gardez pas cela à l'esprit et supposez qu'il $IFSest défini sur l'espace blanc habituel ( $' \t\n'), vous pourriez finir par faire en sorte que votre script se comporte de manière assez étrange.

Eliah Kagan
la source
8

Vous pouvez abuser sudode cela. sudoa la -lpossibilité de tester les privilèges sudo de l'utilisateur et -Sde lire le mot de passe depuis stdin. Cependant, quel que soit le niveau de privilège de l'utilisateur, s'il est authentifié avec succès, sudoretourne avec l'état de sortie 0. Ainsi, vous pouvez prendre tout autre état de sortie comme indication que l'authentification n'a pas fonctionné (en supposant sudoqu'il n'y ait pas de problèmes, comme erreurs d'autorisation ou sudoersconfiguration invalide ).

Quelque chose comme:

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

Ce script dépend un peu de la sudoersconfiguration. J'ai supposé la configuration par défaut. Choses qui peuvent provoquer son échec:

  • targetpwou runaspwest défini
  • listpw est never
  • etc.

D'autres problèmes incluent (merci Eliah):

  • Les tentatives incorrectes seront connectées /var/log/auth.log
  • sudodoit être exécuté en tant qu'utilisateur pour lequel vous vous authentifiez. À moins que vous ne sudodisposiez de privilèges pour pouvoir exécuter sudo -u foo sudo -lS, cela signifie que vous devrez exécuter le script en tant qu'utilisateur cible.

Maintenant, la raison pour laquelle j'ai utilisé here-docs est d'entraver l'écoute. Une variable utilisée dans le cadre de la ligne de commande est plus facilement révélée en utilisant topou psou d'autres outils pour inspecter les processus.

muru
la source
2
Cette façon semble excellente. Bien que cela ne fonctionne pas sur les systèmes avec des sudoconfigurations inhabituelles (comme vous le dites) et des encombrements /var/log/auth.logavec des enregistrements de chaque tentative, cela est rapide, simple et utilise un utilitaire existant, plutôt que de réinventer la roue (pour ainsi dire). Je suggère de définir IFS=pour readéviter les faux succès pour les entrées utilisateur remplies d'espaces blancs et les mots de passe non remplis, ainsi que les faux échecs pour les entrées utilisateur remplies d'espaces blancs et les mots de passe remplis. (Ma solution avait un bogue similaire.) Vous pouvez également expliquer comment il doit être exécuté en tant qu'utilisateur dont le mot de passe est vérifié.
Eliah Kagan
@EliahKagan, chaque tentative échouée est enregistrée, mais oui, vous avez raison pour tout le reste.
muru
Les authentifications réussies sont également enregistrées. sudo -lentraîne l'écriture d'une entrée avec " COMMAND=list". Btw (sans rapport), bien qu'il ne soit pas objectivement préférable de le faire, l'utilisation d'une chaîne ici à la place d'un document ici atteint le même objectif de sécurité et est plus compacte. Puisque le script utilise déjà les bashismes read -set &>, l'utilisation if sudo -lS &>/dev/null <<<"$PASSWD"; thenn'est pas moins portable. Je ne suggère pas que vous devriez changer ce que vous avez (c'est très bien). Je le mentionne plutôt pour que les lecteurs sachent qu'ils ont aussi cette option.
Eliah Kagan
@EliahKagan ah. Je pensais que sudo -lquelque chose avait été désactivé - il s'agissait peut-être d'un rapport lorsqu'un utilisateur essayait d'exécuter une commande à laquelle il n'était pas autorisé.
muru
@EliahKagan En effet. J'ai utilisé des hérestrings de cette façon auparavant . Je travaillais sous une idée fausse ici, ce qui a conduit à des heredocs, mais j'ai décidé de les garder quand même.
muru
4

Une autre méthode (probablement plus intéressante pour son contenu théorique que pour ses applications pratiques).

Les mots de passe des utilisateurs sont stockés dans /etc/shadow.

Les mots de passe stockés ici sont cryptés, dans les dernières versions d'Ubuntu utilisant SHA-512.

Plus précisément, lors de la création du mot de passe, le mot de passe en texte clair est salé et chiffré SHA-512.

Une solution serait alors de saler / chiffrer le mot de passe donné et de le faire correspondre avec le mot de passe de l'utilisateur chiffré stocké dans l' /etc/shadowentrée de l'utilisateur donné .

Pour donner une ventilation rapide de la façon dont les mots de passe sont stockés dans chaque /etc/shadowentrée d' utilisateur , voici un exemple d' /etc/shadowentrée pour un utilisateur fooavec mot de passe bar:

foo:$6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/:16566:0:99999:7:::
  • foo: Nom d'utilisateur
  • 6: type de cryptage du mot de passe
  • lWS1oJnmDlaXrx1F: sel de cryptage du mot de passe
  • h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/: SHA-512mot de passe salé / crypté

Afin de faire correspondre le mot barde passe donné pour l'utilisateur donné foo, la première chose à faire est d'obtenir le sel:

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

Ensuite, on devrait obtenir le mot de passe complet salé / crypté:

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

Ensuite, le mot de passe donné peut être salé / chiffré et comparé au mot de passe de l'utilisateur salé / chiffré stocké dans /etc/shadow:

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

Ils correspondent! Tout est mis dans un bashscript:

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

Production:

$ ./script.sh 
Username >foo
Password >bar
Password matches
$ ./script.sh 
Username >foo
Password >bar1
Password doesn't match
kos
la source
1
sudo getent shadow>> sudo grep .... Sinon, c'est bien. C'est ce à quoi je pensais à l'origine, puis je suis devenu trop paresseux.
muru
@muru Merci. Je me sens vraiment mieux, donc mis à jour, mais je ne sais toujours pas si dans ce cas getent+ grep+ cut> grep+cut
kos
1
Eek. getentpeut faire la recherche pour vous. J'ai pris la liberté d'éditer.
muru
@muru Oui, vous devriez avoir en fait, maintenant je vois le point!
kos
2
Je pense que c'est en fait assez pratique, bien que je puisse être biaisé: c'est la même méthode que j'ai utilisée. Votre mise en œuvre et votre présentation sont cependant très différentes - c'est certainement une réponse précieuse. Alors que j'avais l'habitude mkpasswdde générer un hachage à partir des entrées utilisateur et du sel existant (et inclus des fonctionnalités et des diagnostics supplémentaires), vous avez plutôt codé un simple programme Python implémentant mkpasswdles fonctionnalités les plus importantes et utilisé cela. Je vous recommande de définir IFS=lors de la lecture de l'entrée de mot de passe et de citer ${password}avec ", afin que les tentatives de mot de passe avec des espaces ne cassent pas le script.
Eliah Kagan