Come verificare che la password inserita sia una password valida per questo utente?


14

Lo scenario:

In uno script bash, devo verificare se una password fornita da un utente è una password utente valida.

Supponiamo che io abbia un utente A con password PA .. Nello script ho chiesto all'utente A di inserire la sua password, quindi come verificare se la stringa inserita è davvero la sua password? ...


Cosa intendi con una password valida? Vuoi provare se è davvero la password dell'utente?
AB

@AB password valida = password di accesso qualunque cosa tu voglia chiamare ... Voglio dire, è una password per questo utente
Maythux

@AB So che sarà un buco nella sicurezza ma qui conosci già anche il nome utente ... In altre parole è solo un test se la password è per questo utente ..
Maythux

3
AB

Una soluzione basata su expecta stackoverflow.com/a/1503831/320594 .
Jaime Hablutzel,

Risposte:


14

Dato che vuoi farlo in uno script di shell, un paio di contributi in Come controllare la password con Linux? (su Unix.SE , suggerito da AB ) sono particolarmente rilevanti:

Per controllare manualmente se una stringa è davvero la password di qualche utente, è necessario hash con lo stesso algoritmo di hash come nella voce di ombra per l'utente, con lo stesso sale come in ingresso ombra dell'utente. Quindi può essere confrontato con l'hash della password memorizzato lì.

Ho scritto uno script completo e funzionante che dimostra come farlo.

  • Se lo chiami chkpass, puoi eseguirlo e leggerà una riga dall'input standard e controllerà se è la password.chkpass useruser
  • Installare il pacchetto whois Installa whois per ottenere l' mkpasswdutilità da cui dipende questo script.
  • Questo script deve essere eseguito come root per avere successo.
  • Prima di utilizzare questo script o parte di esso per svolgere un lavoro reale, vedere le Note sulla sicurezza di seguito.
#!/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

Note sulla sicurezza

Potrebbe non essere l'approccio giusto.

Se un approccio come questo debba essere considerato sicuro e altrimenti appropriato dipende dai dettagli sul tuo caso d'uso che non hai fornito (al momento della stesura di questo documento).

Non è stato verificato.

Sebbene abbia provato a prestare attenzione durante la scrittura di questo script, non è stato verificato correttamente per le vulnerabilità di sicurezza . È inteso come una dimostrazione e sarebbe un software "alpha" se rilasciato come parte di un progetto. Inoltre...

Un altro utente che sta "guardando" potrebbe essere in grado di scoprire il sale dell'utente .

A causa delle limitazioni nel modo in cui mkpasswdaccetta i dati salt, questo script contiene un difetto di sicurezza noto , che può essere considerato accettabile a seconda del caso d'uso. Per impostazione predefinita, gli utenti su Ubuntu e la maggior parte degli altri sistemi GNU / Linux possono visualizzare informazioni sui processi eseguiti da altri utenti (incluso root), inclusi i loro argomenti della riga di comando. Né l'input dell'utente né l'hash della password memorizzata vengono passati come argomento della riga di comando a nessuna utility esterna. Ma il salt , estratto dal shadowdatabase, viene fornito come argomento della riga di comando mkpasswd, poiché questo è l'unico modo in cui l'utilità accetta un salt come input.

Se

  • un altro utente sul sistema, o
  • chiunque abbia la possibilità di creare un account utente (ad es. www-data) eseguire il proprio codice o
  • chiunque altrimenti possa visualizzare informazioni sui processi in esecuzione (anche ispezionando manualmente le voci in /proc)

è in grado di controllare gli argomenti della riga di comando mkpasswdmentre viene eseguito da questo script, quindi possono ottenere una copia del sale dell'utente dal shadowdatabase. Essi potrebbero devono essere in grado di indovinare quando quel comando viene eseguito, ma che a volte è realizzabile.

Un attaccante con il tuo sale non è cattivo come un attaccante con il tuo sale e hash , ma non è l'ideale. Il salt non fornisce abbastanza informazioni per qualcuno per scoprire la tua password. Ma consente a qualcuno di generare tabelle arcobaleno o hash di dizionari pre-calcolati specifici per quell'utente su quel sistema. Inizialmente non ha valore, ma se la tua sicurezza viene compromessa in un secondo momento e viene ottenuto l'intero hash, potrebbe essere crackato più rapidamente per ottenere la password dell'utente prima che abbia la possibilità di cambiarla.

Pertanto, questo difetto di sicurezza è un fattore esacerbante in uno scenario di attacco più complesso piuttosto che una vulnerabilità completamente sfruttabile. E potresti considerare la situazione di cui sopra inverosimile. Ma sono riluttante a raccomandare qualsiasi metodo per un uso generale e reale che diffonda qualsiasi dato non pubblico da /etc/shadowun utente non root.

È possibile evitare completamente questo problema:

  • scrivere parte del tuo script in Perl o in qualche altra lingua che ti consenta di chiamare le funzioni C, come mostrato nella risposta di Gilles alla relativa domanda Unix.SE , oppure
  • scrivere l'intero script / programma in una tale lingua, piuttosto che usare bash. (In base al modo in cui hai taggato la domanda, sembra che tu preferisca usare bash.)

Fai attenzione a come chiami questo script.

Se si consente a un utente non attendibile di eseguire questo script come root o di eseguire qualsiasi processo come root che chiama questo script, fare attenzione . Modificando l'ambiente, possono fare in modo che questo script, o qualsiasi script eseguito come root, faccia qualsiasi cosa . A meno che non sia possibile evitare che ciò accada, non è necessario consentire agli utenti privilegi elevati per l'esecuzione di script di shell.

Vedi 10.4. Shell Scripting Languages ​​(sh and csh Derivatives) in Programmazione sicura di David A. Wheeler per Linux e Unix HOWTO per ulteriori informazioni al riguardo. Mentre la sua presentazione si concentra su script setuid, altri meccanismi possono cadere in preda ad alcuni degli stessi problemi se non disinfettano correttamente l'ambiente.

Altre note

Supporta la lettura di hash solo dal shadowdatabase.

Le password devono essere ombreggiate affinché questo script funzioni (vale a dire, i loro hash dovrebbero essere in un /etc/shadowfile separato che solo root può leggere, non in /etc/passwd).

Questo dovrebbe essere sempre il caso di Ubuntu. In ogni caso, se necessario, lo script può essere banalmente esteso per leggere gli hash delle password da passwdcosì come shadow.

Tenere IFSpresente quando si modifica questo script.

Ho impostato IFS=$all'inizio, poiché i tre dati nel campo hash di una voce shadow sono separati da $.

  • Hanno anche un vantaggio $, motivo per cui il tipo di hash e il sale sono "${ent[1]}"e "${ent[2]}"piuttosto che "${ent[0]}"e "${ent[1]}", rispettivamente.

Le uniche posizioni in questo script in cui si $IFSdetermina come la shell divide o combina le parole

  • quando questi dati vengono suddivisi in un array, inizializzandoli dalla $( )sostituzione di comando non quotata in:

    set -f; ent=($(getent shadow "$1" | awk -F: '{print $2}')); set +f
  • quando l'array viene ricostituito in una stringa da confrontare con l'intero campo da shadow, l' "${ent[*]}"espressione in:

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

Se modifichi lo script e esegui la suddivisione delle parole (o l'unione delle parole) in altre situazioni, dovrai impostare IFSvalori diversi per comandi diversi o parti diverse dello script.

Se non lo tieni a mente e presumi che $IFSsia impostato sul solito spazio bianco ( $' \t\n'), potresti finire per far comportare il tuo script in modi piuttosto strani.


8

Puoi abusare sudodi questo. sudoha l' -lopzione, per testare i privilegi sudo che l'utente ha e -Sper leggere la password da stdin. Tuttavia, indipendentemente dal livello di privilegi dell'utente, se autenticato con successo, sudoritorna con lo stato di uscita 0. Quindi, puoi prendere qualsiasi altro stato di uscita come indicazione che l'autenticazione non ha funzionato (supponendo sudoche non abbia problemi, come errori di autorizzazione o sudoersconfigurazione non valida ).

Qualcosa di simile a:

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

Questo script dipende abbastanza dalla sudoersconfigurazione. Ho assunto la configurazione predefinita. Cose che possono causarne il fallimento:

  • targetpwo runaspwè impostato
  • listpw è never
  • eccetera.

Altri problemi includono (grazie Eliah):

  • I tentativi errati verranno registrati /var/log/auth.log
  • sudodeve essere eseguito come l'utente per il quale ti stai autenticando. A meno che tu non abbia i sudoprivilegi per poter eseguire sudo -u foo sudo -lS, ciò significa che dovrai eseguire lo script come utente di destinazione.

Ora, il motivo per cui ho usato qui-docs è quello di ostacolare l'ascolto. Una variabile utilizzata come parte della riga di comando viene rivelata più facilmente utilizzando topo pso altri strumenti per ispezionare i processi.


2
In questo modo sembra eccellente. Anche se non funziona su sistemi con sudoconfigurazioni non comuni (come dici tu) e disordinati /var/log/auth.logcon registrazioni di ogni tentativo, questo è veloce, semplice e utilizza un'utilità esistente, piuttosto che reinventare la ruota (per così dire). Suggerisco di impostare IFS=per read, per evitare falsi successi per l'input dell'utente con spazi vuoti e password non imbottite, nonché falsi fallimenti per l'input dell'utente con spazi bianchi e password imbottite. (La mia soluzione aveva un bug simile.) Potresti anche voler spiegare come deve essere eseguito come utente la cui password viene controllata.
Eliah Kagan,

@EliahKagan viene registrato ogni tentativo fallito, ma sì, hai ragione su tutto il resto.
muru,

Vengono registrate anche le autenticazioni riuscite. sudo -lfa scrivere una voce con " COMMAND=list". A proposito (non correlato), sebbene non sia oggettivamente meglio farlo, l'uso di una stringa qui al posto di un documento qui raggiunge lo stesso obiettivo di sicurezza ed è più compatto. Poiché lo script utilizza già i bashismi read -se &>, l'utilizzo if sudo -lS &>/dev/null <<<"$PASSWD"; thennon è meno portatile. Non sto suggerendo che dovresti cambiare quello che hai (va bene). Piuttosto, lo menziono così i lettori sanno che hanno anche questa opzione.
Eliah Kagan,

@EliahKagan ah. Ho pensato di sudo -ldisabilitare qualcosa: forse stava segnalando quando un utente tenta di eseguire un comando che non gli è permesso.
muru,

@EliahKagan Infatti. Ho usato i herestring in quel modo prima . Stavo lavorando sotto un malinteso qui, che ha portato a heredocs, ma poi ho deciso di tenerli comunque.
muru,

4

Un altro metodo (probabilmente più interessante per i suoi contenuti teorici che per le sue applicazioni pratiche).

Le password dell'utente sono archiviate in /etc/shadow.

Le password memorizzate qui sono crittografate, utilizzando le ultime versioni di Ubuntu SHA-512.

In particolare, al momento della creazione della password, la password in chiaro viene salata e crittografata SHA-512.

Una soluzione sarebbe quindi salare / crittografare la password specificata e confrontarla con la password dell'utente crittografato memorizzata nella /etc/shadowvoce dell'utente specificato .

Per una rapida descrizione di come sono archiviate le password in ogni /etc/shadowvoce dell'utente , ecco una /etc/shadowvoce di esempio per un utente foocon password bar:

foo:$6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/:16566:0:99999:7:::
  • foo: nome utente
  • 6: tipo di crittografia della password
  • lWS1oJnmDlaXrx1F: salt della crittografia della password
  • h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/: SHA-512password salata / crittografata

Al fine di abbinare la password data barper l'utente specificato foo, la prima cosa da fare è ottenere il sale:

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

Quindi si dovrebbe ottenere la password salata / crittografata completa:

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

Quindi la password fornita può essere salata / crittografata e confrontata con la password dell'utente salata / crittografata memorizzata in /etc/shadow:

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

Si abbinano! Tutto messo in una bashsceneggiatura:

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

Produzione:

$ ./script.sh 
Username >foo
Password >bar
Password matches
$ ./script.sh 
Username >foo
Password >bar1
Password doesn't match

1
sudo getent shadow>> sudo grep .... Altrimenti, carino. Questo è quello che ho pensato inizialmente, poi sono diventato troppo pigro.
muru,

@muru Grazie. Sicuramente sembra più bello, così aggiornato, ma non sono ancora sicuro se in questo caso getent+ grep+ cut> grep+cut
kos

1
Eek. getentpuoi fare la ricerca per te. Mi sono preso la libertà di modificare.
muru,

@muru Sì, dovresti in effetti, ora vedo il punto!
kos,

2
Penso che questo sia in realtà abbastanza pratico, anche se potrei essere di parte: è lo stesso metodo che ho usato. L'implementazione e la presentazione sono piuttosto diverse, questa è sicuramente una risposta preziosa. Considerando che mkpasswdgeneravo un hash dall'input dell'utente e dal sale esistente (e includevo funzionalità e diagnostica aggiuntive), hai invece codificato un semplice programma Python implementando mkpasswdle funzionalità più importanti e usato quella. Vi consiglio di impostare IFS=durante la lettura di immissione della password, e citazione ${password}con ", in modo da tentativi di immissione password con gli spazi bianchi non si rompono lo script.
Eliah Kagan,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.