Como verificar a senha digitada é uma senha válida para este usuário?

14

O cenário:

Em um script bash, eu tenho que verificar se uma senha fornecida por um usuário é uma senha de usuário válida.

Ou seja, suponha que eu tenho um usuário A com senha PA .. No script, pedi ao usuário A para digitar sua senha. Então, como verificar se a sequência digitada é realmente a sua senha? ...

Maythux
fonte
O que você quer dizer com uma senha válida? Deseja testar se realmente é a senha do usuário?
AB
@AB senha password = login válido o que você quiser chamar ... Quero dizer a sua senha para este usuário
Maythux
@AB eu sei que vai ser um buraco de segurança, mas aqui você já sabe o nome de usuário também ... Em outras palavras é apenas um teste se a senha é para esse usuário ..
Maythux
3
Aqui está sua resposta: unix.stackexchange.com/a/21728/107084
AB
Uma solução com base em expectpelo stackoverflow.com/a/1503831/320594 .
Jaime Hablutzel

Respostas:

14

Como você deseja fazer isso em um script de shell, algumas contribuições em Como verificar a senha no Linux? (no Unix.SE , sugerido pela AB ) são especialmente relevantes:

Para verificar manualmente se uma sequência é realmente a senha de algum usuário, você deve fazer o hash com o mesmo algoritmo de hash que na entrada de sombra do usuário, com o mesmo sal que na entrada de sombra do usuário. Em seguida, pode ser comparado com o hash da senha armazenado lá.

Escrevi um script de trabalho completo demonstrando como fazer isso.

  • Se você nomear chkpass, poderá executar e ele lerá uma linha da entrada padrão e verificará se é a senha.chkpass useruser
  • Instale o pacote whois Instalar whois para obter o mkpasswdutilitário do qual esse script depende.
  • Este script deve ser executado como root para ter sucesso.
  • Antes de usar este script ou qualquer parte dele para fazer um trabalho real, consulte as Notas de segurança abaixo.
#!/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

Notas de segurança

Pode não ser a abordagem correta.

Se uma abordagem como essa deve ou não ser considerada segura e apropriada depende de detalhes sobre o seu caso de uso que você não forneceu (até o momento em que este artigo foi escrito).

Não foi auditado.

Embora eu tenha tentado tomar cuidado ao escrever esse script, ele não foi auditado corretamente para detectar vulnerabilidades de segurança . Pretende-se como uma demonstração e seria um software "alfa" se lançado como parte de um projeto. Além disso...

Outro usuário que está "assistindo" pode descobrir o sal do usuário .

Devido a limitações na mkpasswdaceitação de dados salt, este script contém uma falha de segurança conhecida , que você pode ou não considerar aceitável, dependendo do caso de uso. Por padrão, os usuários do Ubuntu e da maioria dos outros sistemas GNU / Linux podem visualizar informações sobre processos executados por outros usuários (incluindo raiz), incluindo seus argumentos de linha de comando. Nem a entrada do usuário nem o hash da senha armazenada são passados ​​como um argumento da linha de comandos para qualquer utilitário externo. Mas o salt , extraído do shadowbanco de dados, é fornecido como um argumento de linha de comando mkpasswd, pois essa é a única maneira pela qual o utilitário aceita um salt como entrada.

E se

  • outro usuário no sistema ou
  • qualquer pessoa que tenha a capacidade de criar uma conta de usuário (por exemplo, www-data) execute seu código ou
  • qualquer pessoa que possa visualizar informações sobre processos em execução (inclusive inspecionando manualmente as entradas /proc)

é capaz de verificar os argumentos da linha de comandos mkpasswdconforme são executados por esse script, e eles podem obter uma cópia do sal do usuário no shadowbanco de dados. Talvez eles precisem adivinhar quando esse comando é executado, mas isso às vezes é possível.

Um atacante com seu sal não é tão ruim quanto um atacante com seu sal e hash , mas não é o ideal. O sal não fornece informações suficientes para alguém descobrir sua senha. Mas permite que alguém gere tabelas arco-íris ou hashes de dicionário pré-calculados específicos para esse usuário nesse sistema. Inicialmente, isso é inútil, mas se sua segurança for comprometida posteriormente e o hash completo for obtido, poderá ser quebrado mais rapidamente para obter a senha do usuário antes que eles possam alterá-la.

Portanto, essa falha de segurança é um fator exacerbador em um cenário de ataque mais complexo, em vez de uma vulnerabilidade totalmente explorável. E você pode considerar a situação acima exagerada. Mas eu estou relutante em recomendar qualquer método para geral, o uso do mundo real que vazamentos de quaisquer dados não-públicos de /etc/shadowque um usuário não-raiz.

Você pode evitar esse problema completamente:

  • escrevendo parte do seu script em Perl ou alguma outra linguagem que permite que você chamar funções C, como mostrado na resposta de Gilles para a questão Unix.SE relacionados , ou
  • escrevendo todo o script / programa em um idioma assim, em vez de usar o bash. (Com base na maneira como você marcou a pergunta, parece que você prefere usar o bash.)

Cuidado ao chamar esse script.

Se você permitir que um usuário não confiável execute esse script como root ou execute qualquer processo como root que chame esse script, tenha cuidado . Ao alterar o ambiente, eles podem fazer com que esse script - ou qualquer script que seja executado como root - faça qualquer coisa . A menos que você possa impedir que isso ocorra, você não deve permitir aos usuários privilégios elevados para executar scripts de shell.

Veja 10.4. Linguagens de script de shell (derivadas sh e csh) no HOWTO de programação segura de David A. Wheeler para Linux e UNIX para obter mais informações sobre isso. Embora sua apresentação se concentre em scripts setuid, outros mecanismos podem ser vítimas de alguns dos mesmos problemas se não higienizarem corretamente o ambiente.

Outras notas

Ele suporta hashes de leitura shadowapenas do banco de dados.

As senhas devem estar sombreadas para que esse script funcione (ou seja, seus hashes devem estar em um /etc/shadowarquivo separado no qual somente o root possa ler, não no /etc/passwd).

Este sempre deve ser o caso no Ubuntu. Em qualquer caso, se necessário, o script pode ser estendido trivialmente para ler os hashes de senha passwde também shadow.

Lembre- IFSse de modificar este script.

Defino IFS=$no início, pois os três dados no campo hash de uma entrada de sombra são separados por $.

  • Eles também têm uma liderança $, razão pela qual o tipo de hash e o sal são "${ent[1]}"e, em "${ent[2]}"vez de "${ent[0]}"e "${ent[1]}", respectivamente.

Os únicos lugares neste script onde $IFSdeterminam como o shell divide ou combina palavras são

  • quando esses dados são divididos em uma matriz, inicializando-os a partir da $( )substituição de comando não citado em:

    set -f; ent=($(getent shadow "$1" | awk -F: '{print $2}')); set +f
  • quando a matriz é reconstituída em uma sequência para comparar com o campo completo de shadow, a "${ent[*]}"expressão em:

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

Se você modificar o script e fazê-lo executar a divisão de palavras (ou junção de palavras) em outras situações, será necessário definir IFSvalores diferentes para comandos diferentes ou partes diferentes do script.

Se você não se lembrar disso e assumir que $IFSestá definido como o espaço em branco habitual ( $' \t\n'), você pode acabar fazendo seu script se comportar de maneiras bem estranhas.

Eliah Kagan
fonte
8

Você pode abusar sudodisso. sudotem a -lopção, para testar os privilégios sudo que o usuário possui e -Spara ler a senha do stdin. No entanto, não importa qual nível de privilégio o usuário tenha, se autenticado com êxito, sudoretornará com o status de saída 0. Portanto, você pode usar qualquer outro status de saída como indicação de que a autenticação não funcionou (supondo que sudoele não tenha problemas, como erros de permissão ou sudoersconfiguração inválida ).

Algo como:

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

Este script depende bastante da sudoersconfiguração. Eu assumi a configuração padrão. Coisas que podem causar falhas:

  • targetpwou runaspwestá definido
  • listpw é never
  • etc.

Outros problemas incluem (obrigado Eliah):

  • As tentativas incorretas serão registradas /var/log/auth.log
  • sudodeve ser executado como o usuário para o qual você está se autenticando. A menos que você tenha sudoprivilégios para poder executar sudo -u foo sudo -lS, isso significa que você precisará executar o script como usuário de destino.

Agora, a razão pela qual usei os documentos aqui é dificultar a escuta. Uma variável usada como parte da linha de comando é mais facilmente revelada usando topou psou outras ferramentas para inspecionar processos.

muru
fonte
2
Desta forma, parece excelente. Embora não funcione em sistemas com sudoconfigurações incomuns (como você diz) e /var/log/auth.logcom registros de cada tentativa, isso é rápido, simples e usa um utilitário existente, em vez de reinventar a roda (por assim dizer). Sugiro definir IFS=para read, para evitar falsos sucessos na entrada do usuário preenchida com espaço em branco e senhas não preenchidas, bem como falhas falsas na entrada do usuário preenchida com espaço em branco e senhas preenchidas. (Minha solução teve um bug semelhante.) Você também pode explicar como deve ser executado como o usuário cuja senha está sendo verificada.
Eliah Kagan
@EliahKagan, cada tentativa fracassada é registrada, mas sim, você está certo sobre todo o resto.
muru
As autenticações bem-sucedidas também são registradas. sudo -lfaz com que uma entrada seja gravada com " COMMAND=list". Btw (não relacionado), embora não seja objetivamente melhor fazê-lo, o uso de uma string here no lugar de um documento here atinge o mesmo objetivo de segurança e é mais compacto. Como o script já usa os bashisms read -se &>, using if sudo -lS &>/dev/null <<<"$PASSWD"; thennão é menos portátil. Não estou sugerindo que você mude o que tem (tudo bem). Em vez disso, mencionei para que os leitores saibam que também têm essa opção.
Eliah Kagan
@EliahKagan ah. Eu pensei que sudo -lalgo desativado - talvez estivesse relatando quando um usuário tenta executar um comando que ele não tem permissão.
muru
@EliahKagan Indeed. Eu usei herestrings dessa maneira antes . Eu estava trabalhando sob um equívoco aqui, o que levou a heredocs, mas então decidi mantê-los de qualquer maneira.
muru
4

Outro método (provavelmente mais interessante por seu conteúdo teórico do que por suas aplicações práticas).

As senhas do usuário são armazenadas em /etc/shadow.

As senhas armazenadas aqui são criptografadas, nas últimas versões do Ubuntu usando SHA-512.

Especificamente, após a criação da senha, a senha em texto não criptografado é salgada e criptografada SHA-512.

Uma solução seria saltar / criptografar a senha fornecida e compará-la com a senha do usuário criptografado armazenada na /etc/shadowentrada do usuário .

Para fornecer uma rápida descrição de como as senhas são armazenadas em cada /etc/shadowentrada do usuário , veja uma amostra de /etc/shadowentrada para um usuário foocom senha bar:

foo:$6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/:16566:0:99999:7:::
  • foo: nome do usuário
  • 6: tipo de criptografia da senha
  • lWS1oJnmDlaXrx1F: sal de criptografia da senha
  • h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/: SHA-512senha salgada / criptografada

Para corresponder à senha fornecida barpara o usuário foo, a primeira coisa a fazer é obter o sal:

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

Em seguida, deve-se obter a senha completa salgada / criptografada:

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

Em seguida, a senha fornecida pode ser salgada / criptografada e comparada com a senha do usuário salgada / criptografada armazenada em /etc/shadow:

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

Eles combinam! Tudo colocado em um 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"

Resultado:

$ ./script.sh 
Username >foo
Password >bar
Password matches
$ ./script.sh 
Username >foo
Password >bar1
Password doesn't match
kos
fonte
1
sudo getent shadow>> sudo grep .... Caso contrário, legal. Isso é o que eu pensava originalmente, então fiquei com preguiça.
muru
@muru Obrigado. Definitivamente parece mais agradável, tão atualizado, mas ainda não tenho certeza se, neste caso, getent+ grep+ cut> grep+cut
kos
1
Eek. getentpode fazer a busca por você. Tomei a liberdade de editar.
muru
@ muru Sim, você deve ter de fato, agora eu entendo o ponto!
kos
2
Eu acho que isso é realmente prático, embora eu possa ser tendencioso: é o mesmo método que usei. Sua implementação e apresentação são bem diferentes - essa é definitivamente uma resposta valiosa. Enquanto eu costumava mkpasswdgerar um hash a partir da entrada do usuário e do sal existente (e incluía recursos e diagnósticos adicionais), você codificou um programa simples em Python implementando mkpasswda funcionalidade mais importante e usou isso. Eu recomendo que você defina IFS=ao ler a entrada de senha e cite ${password}com ", para que tentativas de senha com espaço em branco não quebrem o script.
Eliah Kagan