¿Cómo verificar que la contraseña ingresada es una contraseña válida para este usuario?


14

El escenario:

En un script bash, tengo que verificar si una contraseña dada por un usuario es una contraseña de usuario válida.

Supongo que tengo un usuario A con contraseña PA .. En el script le pedí al usuario A que ingrese su contraseña, entonces, ¿cómo verificar si la cadena ingresada es realmente su contraseña? ...


¿Qué quieres decir con una contraseña válida? ¿Quieres probar si realmente es la contraseña del usuario?
AB

@AB contraseña válida = contraseña de inicio de sesión como quieras llamar ... Quiero decir, es una contraseña para este usuario
Maythux

@AB Sé que va a ser un problema de seguridad, pero aquí ya conoce el nombre de usuario también ... En otras palabras, es sólo una prueba de si la contraseña es para este usuario ..
Maythux

3
Aquí está su respuesta: unix.stackexchange.com/a/21728/107084
AB

Una solución basada expecten stackoverflow.com/a/1503831/320594 .
Jaime Hablutzel

Respuestas:


14

Como desea hacer esto en un script de shell, ¿hay un par de contribuciones en Cómo verificar la contraseña con Linux? (en Unix.SE , sugerido por AB ) son especialmente relevantes:

Para verificar manualmente si una cadena es realmente la contraseña de algún usuario, debe hacer un hash con el mismo algoritmo hash que en la entrada de sombra del usuario, con la misma sal que en la entrada de sombra del usuario. Luego se puede comparar con el hash de contraseña almacenado allí.

He escrito un guión completo y funcional que demuestra cómo hacer esto.

  • Si lo nombra chkpass, puede ejecutarlo y leerá una línea de entrada estándar y comprobará si es la contraseña.chkpass useruser
  • Instale el paquete whois Instalar whois para obtener la mkpasswdutilidad de la que depende este script.
  • Este script debe ejecutarse como root para tener éxito.
  • Antes de usar este script o cualquier parte del mismo para hacer un trabajo real, consulte las Notas de seguridad a continuación.
#!/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 seguridad

Puede que no sea el enfoque correcto.

El hecho de que un enfoque como este se considere seguro y apropiado depende de los detalles sobre su caso de uso que no haya proporcionado (a partir de este escrito).

No ha sido auditado.

Aunque he tratado de tener cuidado al escribir este script, no ha sido auditado adecuadamente por vulnerabilidades de seguridad . Se pretende que sea una demostración, y sería un software "alfa" si se lanza como parte de un proyecto. Además...

Otro usuario que está "mirando" puede descubrir la sal del usuario .

Debido a las limitaciones en la forma en que mkpasswdacepta los datos de sal, este script contiene una falla de seguridad conocida , que puede considerar aceptable o no según el caso de uso. Por defecto, los usuarios de Ubuntu y la mayoría de los otros sistemas GNU / Linux pueden ver información sobre los procesos ejecutados por otros usuarios (incluida la raíz), incluidos sus argumentos de línea de comandos. Ni la entrada del usuario ni el hash de contraseña almacenado se pasan como argumento de línea de comandos a ninguna utilidad externa. Pero la sal , extraída de la shadowbase de datos, se proporciona como un argumento de línea de comandos mkpasswd, ya que esta es la única forma en que la utilidad acepta una sal como entrada.

Si

  • otro usuario en el sistema, o
  • cualquier persona que tenga la capacidad de hacer cualquier cuenta de usuario (por ejemplo, www-data) ejecutar su código, o
  • cualquiera que pueda ver información sobre procesos en ejecución (incluso mediante la inspección manual de entradas /proc)

es capaz de verificar los argumentos de la línea de comandos mkpasswdsegún lo ejecuta este script, luego pueden obtener una copia de la sal del usuario de la shadowbase de datos. Es posible que tengan que ser capaces de adivinar cuándo se ejecuta ese comando, pero eso a veces es posible.

Un atacante con su sal no es tan malo como un atacante con su sal y hachís , pero no es lo ideal. La sal no proporciona suficiente información para que alguien descubra su contraseña. Pero sí permite que alguien genere tablas de arco iris o hash de diccionario precalculados específicos para ese usuario en ese sistema. Inicialmente, esto no tiene valor, pero si su seguridad se ve comprometida en una fecha posterior y se obtiene el hash completo, se podría descifrar más rápidamente para obtener la contraseña del usuario antes de que tenga la oportunidad de cambiarla.

Por lo tanto, esta falla de seguridad es un factor exacerbador en un escenario de ataque más complejo en lugar de una vulnerabilidad totalmente explotable. Y podría considerar la situación anterior exagerada. Pero soy reacio a recomendar cualquier método para uso general en el mundo real que filtre cualquier información no pública de /etc/shadowun usuario no root.

Puede evitar este problema por completo:

  • escribiendo parte de su script en Perl o algún otro lenguaje que le permite llamar a funciones de C, tal como se muestra en la respuesta de Gilles de la cuestión relacionada Unix.SE , o
  • escribir todo su script / programa en dicho lenguaje, en lugar de usar bash. (Según la forma en que ha etiquetado la pregunta, parece que prefiere usar bash).

Ten cuidado con cómo llamas a este script.

Si permite que un usuario no confiable ejecute este script como root o ejecute algún proceso como root que llame a este script, tenga cuidado . Al cambiar el entorno, pueden hacer que este script, o cualquier script que se ejecute como root, haga cualquier cosa . A menos que pueda evitar que esto ocurra, no debe permitir a los usuarios privilegios elevados para ejecutar scripts de shell.

Ver 10.4. Lenguajes de secuencias de comandos de Shell (derivados de sh y csh) en la programación segura de David A. Wheeler para Linux y Unix HOWTO para obtener más información al respecto. Si bien su presentación se centra en los guiones setuid, otros mecanismos pueden ser víctimas de algunos de los mismos problemas si no desinfectan correctamente el medio ambiente.

Otras notas

Solo admite hashes de lectura de la shadowbase de datos.

Las contraseñas deben estar sombreadas para que este script funcione (es decir, sus hashes deben estar en un /etc/shadowarchivo separado que solo la raíz pueda leer, no en /etc/passwd).

Este siempre debería ser el caso en Ubuntu. En cualquier caso, si es necesario, el script se puede extender trivialmente para leer hashes de contraseñas passwdy también shadow.

Tenga IFSen cuenta al modificar este script.

Lo configuré IFS=$al principio, ya que los tres datos en el campo hash de una entrada de sombra están separados por $.

  • También tienen un liderazgo $, por lo que el tipo de hash y la sal son "${ent[1]}"y en "${ent[2]}"lugar de "${ent[0]}"y "${ent[1]}", respectivamente.

Los únicos lugares en este script donde $IFSdetermina cómo el shell divide o combina palabras son

  • cuando estos datos se dividen en una matriz, inicializándolos desde la $( )sustitución del comando sin comillas en:

    set -f; ent=($(getent shadow "$1" | awk -F: '{print $2}')); set +f
  • cuando la matriz se reconstituye en una cadena para compararla con el campo completo shadow, la "${ent[*]}"expresión en:

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

Si modifica la secuencia de comandos y hace que divida las palabras (o la combinación de palabras) en otras situaciones, deberá establecer IFSdiferentes valores para diferentes comandos o diferentes partes de la secuencia de comandos.

Si no tiene esto en cuenta y asume que $IFSestá configurado en el espacio en blanco habitual ( $' \t\n'), podría terminar haciendo que su script se comporte de maneras bastante extrañas.


8

Puedes hacer un mal uso sudode esto. sudotiene la -lopción, para probar los privilegios de sudo que tiene el usuario y -Spara leer la contraseña de stdin. Sin embargo, no importa qué nivel de privilegio tenga el usuario, si se autentica correctamente, sudoregresa con el estado de salida 0. Por lo tanto, puede tomar cualquier otro estado de salida como indicación de que la autenticación no funcionó (suponiendo sudoque no tenga ningún problema, como errores de permiso o sudoersconfiguración no vá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 de la sudoersconfiguración. He asumido la configuración predeterminada. Cosas que pueden hacer que falle:

  • targetpwo runaspwestá configurado
  • listpw es never
  • etc.

Otros problemas incluyen (gracias Eliah):

  • Los intentos incorrectos se registrarán /var/log/auth.log
  • sudodebe ejecutarse como el usuario para el que se está autenticando. A menos que tenga sudoprivilegios para poder ejecutar sudo -u foo sudo -lS, eso significa que tendrá que ejecutar el script como el usuario objetivo.

Ahora, la razón por la que usé aquí-docs es para obstaculizar las escuchas. Una variable que se utiliza como parte de la línea de comandos es más fácilmente reveló mediante el uso topo psu otras herramientas para inspeccionar los procesos.


2
De esta manera se ve excelente. Aunque no funcionará en sistemas con sudoconfiguraciones poco comunes (como usted dice) y desorden /var/log/auth.logcon registros de cada intento, esto es rápido, simple y utiliza una utilidad existente, en lugar de reinventar la rueda (por así decirlo). Sugiero ajuste IFS=para read, para evitar falsas éxitos para la entrada del usuario por espacios en blanco acolchado y contraseñas no acolchada, así como fallos falsos para la entrada del usuario por espacios en blanco acolchado y contraseñas acolchadas. (Mi solución tenía un error similar). También puede explicar cómo debe ejecutarse como el usuario cuya contraseña se está verificando.
Eliah Kagan

@EliahKagan cada intento fallido se registra, pero sí, tienes razón en todo lo demás.
muru

Las autenticaciones exitosas también se registran. sudo -lhace que una entrada se escriba con " COMMAND=list". Por cierto (no relacionado), aunque no es objetivamente mejor hacerlo, el uso de una cadena here en lugar de un documento here logra el mismo objetivo de seguridad y es más compacto. Dado que el script ya usa los bashismos read -sy &>, el uso if sudo -lS &>/dev/null <<<"$PASSWD"; thenno es menos portátil. No estoy sugiriendo que debas cambiar lo que tienes (está bien). Más bien, lo menciono para que los lectores sepan que también tienen esta opción.
Eliah Kagan

@EliahKagan ah. Pensé que había sudo -ldeshabilitado algo, tal vez informaba cuando un usuario intentaba ejecutar un comando que no tenía permitido.
muru

@EliahKagan De hecho. He usado hereres así antes . Estaba trabajando bajo una idea errónea aquí, lo que condujo a heredocs, pero luego decidí mantenerlos de todos modos.
muru

4

Otro método (probablemente más interesante por sus contenidos teóricos que por sus aplicaciones prácticas).

Las contraseñas de los usuarios se almacenan en /etc/shadow.

Las contraseñas almacenadas aquí están encriptadas, en las últimas versiones de Ubuntu usando SHA-512.

Específicamente, después de la creación de la contraseña, la contraseña en texto claro es salada y encriptada SHA-512.

Una solución sería entonces sal / cifrar la contraseña dada y compararla con la contraseña del usuario cifrado almacenada en la /etc/shadowentrada del usuario dado .

Para obtener un desglose rápido de cómo se almacenan las contraseñas en cada /etc/shadowentrada de usuario , aquí hay una /etc/shadowentrada de muestra para un usuario foocon contraseña bar:

foo:$6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/:16566:0:99999:7:::
  • foo: nombre de usuario
  • 6: tipo de cifrado de contraseña
  • lWS1oJnmDlaXrx1F: sal de cifrado de contraseña
  • h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/: SHA-512contraseña salada / encriptada

Para coincidir con la contraseña dada barpara el usuario dado foo, lo primero que debe hacer es obtener la sal:

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

Entonces uno debe obtener la contraseña completa salada / encriptada:

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

Luego, la contraseña dada puede ser salada / encriptada y comparada con la contraseña del usuario salado / encriptado almacenada en /etc/shadow:

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

¡Ellos coinciden! Todo puesto en un bashguión:

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

Salida:

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

1
sudo getent shadow>> sudo grep .... De lo contrario, bien. Esto es lo que pensé originalmente, luego me volví demasiado flojo.
muru

@muru Gracias. Definitivamente se siente mejor, tan actualizado, pero aún no estoy seguro de si en este caso getent+ grep+ cut> grep+cut
kos

1
Eek getentpuede hacer la búsqueda por ti. Me tomé la libertad de editar.
muru

@muru Sí, deberías haberlo hecho, ¡ahora veo el punto!
kos

2
Creo que esto es bastante práctico, aunque puedo estar sesgado: es el mismo método que utilicé. Sin embargo, su implementación y presentación son bastante diferentes: esta es definitivamente una respuesta valiosa. Mientras que yo solía mkpasswdgenerar un hash a partir de la entrada del usuario y la sal existente (e incluía características y diagnósticos adicionales), en su lugar codificó un programa simple de Python que implementa mkpasswdla funcionalidad más importante y lo usé. Te recomiendo que establece IFS=la lectura de introducción de contraseña, y la cita ${password}con ", por lo que los intentos de contraseña con un espacio en blanco no rompen la secuencia de comandos.
Eliah Kagan
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.