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
Tags: Planeta Debian, Planet Lugfi