HOWTO originalmente publicado en el wiki del GlugCEN el 26/1/2007.

Introducción

En la vida del sysadmin, muchas veces nos piden que pongamos esas molestas políticas de cambio de claves tan comunes en ambientes de oficina. Personalmente, tenía la idea que era algo de complicado a imposible; recuerdo problemas varios debidos a distintas reacciones de los programas, como no poder hacer el cambio de clave forzado al entrar por ssh, etc.

Sin embargo, esta semana tuve que hacer exactamente esto, y resultó que todo funcionó a la perfección. Se ve que las cosas han mejorado.

Objetivo

Lo que tenía que implementar es lo usual en estos casos:

  • Forzar el uso de claves no triviales, con mezcla de letras y números.
  • Forzar el cambio de claves periódicamente.
  • Forzar el cambio de clave en el primer login.
  • Impedir reutilizar una clave ya usada.
  • Invalidar automáticamente las cuentas inactivas por mucho tiempo.
  • Bloquear la cuenta luego de poner mal la clave reiteradas veces.

Materiales

Felizmente, casi todo lo pude implementar sin problemas en Debian Etch, sin instalar más que un paquete extra (libpam-cracklib) y sin recurrir a cosas encontradas por ahí.

El mayor problema de PAM, es la documentación: es bastante mala e incompleta, dependiendo del módulo. Por lo demás, es un sistema elegante y bien hecho. Se puede consultar la documentación online en el sitio oficial: http://www.kernel.org/pub/linux/libs/pam/

Aunque en el mismo sitio recopilan módulos distribuídos por la net, yo no recomiendo andar bajando e instalando módulos de PAM: es el componente más crítico en la seguridad de un sistema GNU/Linux, y no estaría bueno que el súper módulo que bajamos tenga un bug de seguridad que comprometa toda la máquina.

La única cosa que utilicé, adicional a los módulos que vienen con Debian, fue un script para vencer inmediatamente la clave de un usuario, que pego más abajo.

Definiendo políticas

Para hacer este trabajo, primero hay que definir las políticas que se van a usar:

  • Tiempos mínimos y máximos entre cambios de clave. Los tiempos mínimos son absolutamente necesarios si se quiere evitar el reuso de claves, ya que un usuario podría cambiar la clave N veces, hasta llenar el historial de claves, y luego volver a la clave original.
  • Tiempo de aviso antes de forzar el cambio de clave.
  • Tiempo de gracia luego de que vence la clave hasta que se bloquea totalmente la cuenta.
  • Grado de complejidad de la clave: forzar o no distintas clases de carácteres (minúsculas, mayúsculas, números o signos de puntuación) o utilizar un sistema de puntuación según los tipos de carácteres usados y la longitud mínima de la clave.

Implementación

Forzar el uso de claves no triviales, con mezcla de letras y números

El módulo pam_cracklib está hecho específicamente para esta tarea, se inserta en la fase passwd de PAM y antes de aceptar una clave nueva le corre una serie de pruebas para determinar si es suficientemente fuerte:

  • puede derivarse de una palabra del diccionario?
  • es un palíndromo de la vieja clave?
  • es sólo un intercambio de mayúsculas y minúsculas de la clave anterior?
  • es muy similar (carácteres repetidos) a la anterior?
  • es muy simple (muy corta o no tiene suficientes clases de carácteres distintas)?
  • es una rotación de la vieja clave?
  • la clave ya fue usada en el pasado?

Para poder hacer los ataques de diccionario, es necesario que instalemos uno. Yo recomiendo instalar al menos wspanish y wbritish.

Los parámetros más importantes son:

  • difok: cantidad mínima de carácteres que deben ser distintos de la clave anterior.
  • minlen: longitud mínima de la clave (tomando en cuenta las puntuaciones que se explican luego).
  • lcredit, ucredit, dcredit y ocredit: configuración de las puntuaciones y exigencias de las distintas clases de carácteres.

El sistema de puntuaciones es un poco confuso. pam_cracklib puede operar de dos modos: con mínimos estrictos de longitud total y clases de carácteres usados o con un sistema que da puntos según los carácteres que se usan y luego se exige una cantidad mínima de puntos.

Las clases de caracteres son:

  • letras minúsculas (controlado con lcredit),
  • mayúsculas (ucredit),
  • dígitos (dcredit) y
  • otros carácteres (ocredit).

Un ejemplo del primer modo de operación sería:

  • al menos 8 carácteres,
  • al menos debe tener un número, y
  • al menos debe tener dos letras mayúsculas.

En este modo, en minlen se pone la longitud mínima y en *credit se ponen las cantidades mínimas de cada clase, en valores negativos, o cero si no es obligatorio. Para exigir la política de ejemplo, se usaría:

 minlen=8 dcredit=-1 ucredit=-1 lcredit=0 ocredit=0

Nota: Recordar que los valores por default para *credit es 1.

Si usamos el sistema de puntuación, especificamos cuántos puntos agrega cada clase de carácter como máximo, a razón de uno por carácter. Un ejemplo sería:

  • al menos 14 puntos,
  • cada carácter cuenta como un punto,
  • por cada clase de carácter distinta utilizada se agrega uno o dos puntos extra, excepto minúsculas, que no dan puntos.

Esta regla podría cumplirse con una clave de 6 minúsculas, un número y una mayúscula, o con una clave de 10 minúsculas. Con esta regla, la clave "Is5Eutoh!#$" obtendría:

  • 11 puntos por longitud,
  • 2 puntos por usar letras mayúsculas,
  • 1 punto por usar un dígito,
  • 2 puntos por usar otros carácteres (aunque haya 3).
  • Total: 16 puntos.

Para la configuración, el valor de minlen es la cantidad de puntos requerida y en *credit la cantidad máxima de puntos dada por cada clase de carácteres (a razón de uno por carácter). Si utilizamos la configuración por defecto que otorga a lo sumo un punto por clase, la puntuación de la clave "Cachit0" sería:

  • 7 puntos por longitud,
  • 1 punto por usar letras mayúsculas,
  • 1 punto por usar letras minúsculas,
  • 1 punto por usar dígitos.
  • Total: 10 puntos.

Para implementar el ejemplo de política, usaríamos:

minlen=14 lcredit=0 ucredit=2 dcredit=2 ocredit=2

Con todo esto analizado, hacemos la configuración, cambiando en /etc/pam.d/common-password:

password   required   pam_unix.so nullok obscure min=4 max=8 md5

por

password required pam_cracklib.so minlen=X difok=X dcredit=X ucredit=X ocredit=X lcredit=X
password required pam_unix.so use_authtok nullok md5

Nota: los cambios en la línea de pam_unix son por las funcionalidades reemplazadas y para que pam_unix pueda leer la clave que recibió pam_cracklib.

Nota 2: usar siempre md5!!! El método tradicional (crypt) no es seguro desde el punto de vista criptográfico e ignora claves de más de ocho carácteres, sólo está por compatibilidad.

Forzar el cambio de claves periódicamente

Esto se realiza con las herramientas tradicionales del paquete shadow, modificando los campos usualmente ignorados de /etc/shadow a través del comando chage.

Si queremos que las claves deban cambiarse -por ejemplo- cada 80 días, con un aviso previo de 7 días, 10 días de gracia una vez vencida la clave y un día de espera antes de volver a cambiar la clave, usamos el siguiente comando por cada usuario existente:

# chage -M 80 -W 7 -I 10 -m 1 <usuario>

El calendario de sucesos será:

  • día 0: cambio de clave
  • día 73-79: Warning: your password will expire in X days
  • día 80-89: WARNING: Your password has expired.You must change your password now and login again!
  • día 90-: Your account has expired; please contact your system administrator

Claro que esto va a hacer que muchos usuarios queden automáticamente bloqueados porque cambiaron su clave hace más que 90 (80 + 10) días, entonces cambiamos la fecha de último cambio a una fecha reciente, idealmente justo para que estén obligados a cambiar la clave en el próximo login, por ejemplo:

# chage -d 2007-1-1 <usuario>

Para ver el estado de cada usuario, hacemos:

# chage -l <usuario>
Último cambio de contraseña                                     : ene 01, 2007
La contraseña caduca                                            : mar 22, 2007
Contraseña inactiva                                             : abr 01, 2007
La cuenta caduca                                                : nunca
Número de días mínimo entre cambio de contraseña                : 1
Número de días máximo entre cambio de contraseñas               : 80
Número de días de aviso antes de que expire la contraseña       : 7

Aquí vemos, que con los parámetros recién establecidos y el último cambio de clave realizado el primero de enero, la clave debe cambiarse a partir del 22 de marzo, y el primero de abril la cuenta se bloquea si la clave no se cambió.

Para que estos parámetros se utilicen automáticamente con cada usuario nuevo, debemos modificar dos archivos (herencia de estructuras antiguas, es medio caótico), en /etc/default/useradd, agregamos:

INACTIVE=10

y en /etc/login.defs:

PASS_MAX_DAYS   80
PASS_MIN_DAYS   1
PASS_WARN_AGE   7

Forzar el cambio de clave en el primer login

Para esto, aprovechamos un mecanismo que poseé el script /usr/sbin/adduser de Debian (pero no se utiliza con otras herramientas de creación de usuarios, como /usr/sbin/useradd!): si existe un archivo llamado /usr/local/sbin/adduser.local y es ejecutable, lo ejecuta luego de la creación de usuario. En dicho script hacemos el siguiente truco: definimos la fecha de último cambio de clave justo en PASS_MAX_DAYS + 1.

NOTA: no encontré manera de hacer esto si no se utiliza expiración automática de claves.

Código fuente de los scripts

/usr/local/sbin/adduser.local:

#!/bin/sh
#
#       If  the  file /usr/local/sbin/adduser.local exists, it will be executed after the
#       user account has been set up in order to  do  any  local  setup.   The arguments
#       passed to adduser.local are:
#       username uid gid home-directory
#       The environment variable VERBOSE is set according to the following rule:#
#       0 if --quiet is specified
#
#       1 if neither --quiet nor --debug is specified
#
#       2 if --debug is specified
#
#              (The  same applies to the variable DEBUG, but DEBUG is deprecated and will
#              be removed in a later version of adduser.)
#
/usr/local/sbin/expirar_clave "$1"

/usr/local/sbin/expirar_clave:

#!/usr/bin/perl
use POSIX;

my($username) = @ARGV;

my($pwdays, $debug);
$debug = defined($ENV{VERBOSE}) ? $ENV{VERBOSE} : 1;

open(SHADOW, "<", "/etc/shadow") or die $!;
while(<SHADOW>) {
        chomp;
        my @d = split(/:/);
        next unless($d[0] eq $username);
        $pwdays = $d[4];
        last;
}
close SHADOW;
die "El usuario $username no existe en /etc/shadow!\n" unless(defined $pwdays);

exit(0) if $pwdays > 9999;

my $fecha = time - ((1 + $pwdays) * 60 * 60 * 24);
my $sfecha = POSIX::strftime("%Y-%m-%d", localtime($fecha));

warn "Expirando clave de $username\n" if($debug > 1);
my $r = system("/usr/bin/chage", "-d", $sfecha, $username);
die "Comando retornó código de error, expirando clave\n" if($r > 0);
die "Error expirando clave: $!\n" if($r < 0);

exit(0);

Impedir reutilizar una clave ya usada

El módulo pam_unix, que es el utilizado por defecto, permite almacenar las claves que se van utilizando en un archivo, de modo que pam_cracklib pueda verificar que no se vuelvan a utilizar (esto lo hace automáticamente, sin configuración alguna). Para habilitar esto, debemos crear un archivo con los permisos adecuados:

# touch /etc/security/opasswd
# chmod 600 /etc/security/opasswd

La manera que almacena las claves no es un problema, ya que almacena el mismo hash que se guarda en /etc/shadow, de modo que las claves no quedan expuestas en caso de una intrusión en el sistema. Luego, configuramos la cantidad de claves que se van a guardar de cada usuario, en /etc/pam.d/common-password, agregando el parámetro remember:

password required pam_unix.so use_authtok nullok md5 remember=4

Invalidar automáticamente las cuentas inactivas por mucho tiempo

Esto viene gratis, ya que si un usuario no se conecta por más de (PASS_MAX_DAYS + INACTIVE) días, en nuestro ejemplo 90 días, la cuenta queda automáticamente bloqueada.

Bloquear la cuenta luego de poner mal la clave reiteradas veces

Para esto necesitamos el módulo pam_tally, que viene con la distribución estándar de PAM. Este módulo cuenta cada intento de poner una clave (ya sea por consola con login, por ssh, su, sudo, etc.) y vuelve el contador a cero cuando el usuario logra establecer una sesión.

Las opciones importantes son:

  • onerr: qué hacer cuando algo falla (por ejemplo, por disco lleno), los valores son succeed (seguir como si nada) o deny (rechazar al usuario).
  • deny: bloquear al usuario después de cuántos intentos.
  • unlock_time: si se incluye esta opción, la cuenta se desbloquea automáticamente después de la cantidad de segundos indicada.

Para bloquear a un usuario después de 3 intentos, y desbloquearlo automáticamente una hora después, ponemos en /etc/pam.d/common-auth la siguiente línea:

auth required pam_tally.so onerr=succeed deny=3 unlock_time=3600

Y en /etc/pam.d/common-account:

account required pam_tally.so onerr=succeed