0x02: Passwords en UNIX
Toda la información contenida en este texto esta escrita solo con fines educativos, el mal uso de esta es completa responsabilidad del lector.
© z0Rbas 2000
Los passwords en UNIX se guardan por lo general en el fichero /etc/passwd o si están shadowed en el fichero /etc/shadow (por lo general, podría ser tambien por ejemplo /etc/master.passwd).
El fichero /etc/passwd es un archivo compuesto por líneas, donde cada línea representa un registro, compuesto de siete campos separados entre sí por ' : '.
Por ejemplo una línea podría ser así:
Little:E6r68fgtl13mz:0:0:root:/root:/bin/bash
- 1° campo ('little'): es el nombre de usuario.
- 2° campo ('E6r68fgtl13mz'): es el password encriptado, donde 'E6', representa la semilla(como se le llama en criptologia, lo que es una clave en la que se basa para encriptar el password) y el resto es el password encriptado (todo esto sé vera un poco mas adelante). Si en vez de aparecer eso aparece un ' * ' o una ' X ' en todos los campos de password del fichero, quiere decir que los password están shadowed. Si al final del password encriptado hay una ' , ' y dos cifras mas, estas corresponden a la fecha de expiración del password.
- 3° campo (' 0 '): es el UID(User ID). UNIX no identifica a los usuarios por su nombre (o nick) sino que por un numero que se almacena en este campo. Este valor puede variar entre 0 y 60000, por lo general se usan n˙meros entre 100 y 60000 para usuarios normales y el ' 0 ' corresponde al super usuario(root).
- 4° campo (' 0 '): es el GID(Group ID). Es la identificación del grupo al que pertenece al usuario. También varia entre 0 y 60000 y el ' 0 ' también pertenece al grupo del root y tienen los mismos privilegios para aquellos elementos que pertenezcan a su dominio.
- 5° campo (' root '): este es un campo reservado para un comentario o información extra del usuario, como podría ser por ejemplo el nombre completo o algo así.
- 6° campo (' /root '): es el directorio home de ese usuario, o sea, su directorio de trabajo.
- 7° campo (' /bin/bash '): es la shell(interprete de comandos) que utiliza el usuario. Estas sirven también para restringir el uso de algunos comandos a algunos usuarios en especial, por ejemplo un usuario anónimo, por lo general tendrá una shell muy restringida que le permitirá ejecutar muy pocos comandos.
Las contraseñas en UNIX se encriptan usando una función del kernel (nucleo del sistema), crypt(3). Esta función es un algoritmo basado en el estándar de encriptación de datos(DES), desarrollado por el Instituto Nacional de Estándares y Tecnología(NIST).
En crypt(3) (no crypt(1), que es la mas antigua y mucho mas insegura), el texto llano se encripta en un grupo de ceros, posteriormente el texto cifrado resultante es encriptado de nuevo con el password del usuario como clave, repitiéndose este proceso 25 veces. Una vez finalizado se obtiene un resultado de 64 bits, los cuales se dividen en 11 caracteres, los cuales se guardan en el archivo /etc/passwd o /etc/shadow (el correspondiente).
El 'grano de sal' o 'semilla' como se le denomina en criptologia (los dos primeros caracteres del segundo campo en el fichero) , los cuales representan un valor de 12 bits, que se utilizan para alterar el resultado de la función, lo que hace que un password se pueda variar entre 0 y 4095, por lo que para cada password hay 4069 formas distintas de encriptación (mas adelante veremos un poco mejor la función crypt(3), junto con unos ejemplos).
Por ejemplo el programa /bin/passwd, que se usa para cambiar la contraseña de un determinado usuario, calcula la semilla de 12 bits, basándose en la hora del sistema.
UNIX jamas desencripta un password, cuando el sistema nos pide login y password, el programa encargado de hacer esto, encripta la palabra usando la semilla presente en el fichero de password y luego lo compara con el password encriptado del fichero.
La manera que se usa para descubrir los passwords ( no desencriptar ) es encriptar palabras presentes en un diccionario por ejemplo y compararlas con el password del fichero. Este método es muy lento y puede incluso llegar a ser muy difícil averiguar la clave. Los crackeadores de password que utilizan este sistema son fácilmente diseñables, incluso se pueden conseguir uno en 35 líneas aprox. ¿Por qué no desencriptarlos? Porque la función crypt(3) es una función muy fácil de ejecutar de ida pero extremadamente difícil de ejecutar de vuelta, o sea, revertirla, lo que todavía no se ha logrado.
El fichero /etc/shadow puede tener una forma un poco distinta(el fichero /etc/passwd siempre existe y solo varia si esta el password encriptado ahí o no). La forma seria mas o menos así:
Usuario : pass : ultima vez que cambio pass : días que deben pasar para poder cambiar pass : días después de los que debe cambiar pass : días antes de la expiración de la cuenta en que el usuario debe ser advertido : día de la expiración de la cuenta : días que lleva deshabilitada la cuenta : bloque reservado
Claro que en la realidad si cave en una línea, como por ejemplo:
Little:E6r68fgtl13mz:9753:0:1000::::
La estructura en c de una línea del fichero shadow seria:
Struct spwd
{
char sp_namp; /* nombre de usuario */
char sp_pwdp; /* password encriptado */
sptime sp_lstchg; /* ultima fecha de cambio */
sptime sp_min; /* mínimo de días antes del cambio */
sptime sp_max; /* días a los que debe ser cambiado el
password */
sptime sp_warn; /* días antes de la advertencia de la
expiración */
sptime sp_intact; /* numero de días a los que expira */
sptime sp_expire; /* días que lleva expirada la cuenta */
unsigned long sp_flag /* reservado para usos futuros */
};
Análisis de crypt(3)
La semilla en la función puede variar entre a-z, A-Z y 0-9 y con la combinación de estas dos opciones se crean 4096 posibilidades diferentes de encriptar un password.
Lo que este algoritmo hace es tomar los siete bits mas bajos del password tipiado por el usuario, con lo que se obtiene una clave de 56 bits. Esta clave de 56 bits, es usada para encriptar repetidamente una cadena(string) constante, la cual por lo general son solo NULLs( lo que en C corresponde a '\0'). El retorno de la función, apunta a una cadena de 13 caracteres ASCII imprimibles(incluida la semilla). El retorno apunta a una variable estática, la cual es sobrescrita en cada llamada. El espacio de la clave consiste en 5**56(7,2*10**16) posibles valores, lo que hace demasiado difícil de encontrar la combinación.
Ejemplos
Un ejemplo de programa de login seria por ejemplo el que usa el pppd-1.2.1d (Point-to-Point Protocol Server).
----------------------- inicio auth.c(traducido) ------------------------
/* este programa chequea un usuario y password, basado en el fichero de
password(/etc/passwd) para la autentificación, también chequea si el
usuario es valido.
Retornos de la función:
UPAP_AUTHNAK : logeo fallido.
UPAP_AUTHACK : logeo exitoso.
En algunos casos la variable msg apunta a un mensaje en especial.
(Traducido por z0Rbas)
*/
Static int login(usuario, passwd, msg, largomsg)
Char *usuario;
Char *password;
Char **msg;
Int *largomsg; // definición de los argumentos de la
función.
{
struct passwd *pw; // define puntero a la estructura del registro
char *epasswd; // puntero a lo que será la pass encriptada
char *tty; // puntero a la terminal que se
logeara
if((pw=getpwnam(usuario))==NULL) return(UPAP_AUTHNAK);
/* se asigna el valor a la estructura apuntada por pw, con el valor del
registro del usuario correspondiente, usando la función getpwnam() y se
compara al tiro con NULL, si es verdadero, quiere decir que el usuario
no se encontró y el login se rechaza. */
if(pw->pw_passwd == '\0') return(UPAP_AUTHACK);
/* se compara el campo de password con NULL, si es verdadero, no hay
password en el campo y el logeo es aceptado, no se necesita revisar el
password */
epasswd = crypt(password, pw->pw_passwd);
/* encripta el password con la semilla del password original. */
if(strcmp(epasswd, pw->pw_passwd)) return(UPAP_AUTHNAK);
/* si son distintos los dos string( epasswd, el password
encriptado(instrucción pasada) y el password original del fichero se
rechaza el login, si a estas alturas aun no se ha rechazado, el sistema
reconoce el password como correcto */
syslog(LOG_INFO, "user %s logged in", usuario);
/* logea la entrada del usuario en la constante definida por LOG_INFO */
tty = strrchr(devname,'/');
if(tty==NULL) tty=devname;
else tty++;
logwtmp(tty,usuario,"");
logged_in=TRUE;
return(UPAP_AUTHACK);
/* calcula la terminal de logeo y crea un log, luego retorna la función
diciendo que el logeo ha sido exitoso */
}
-------------------------------------- fin auth.c ------------------------------------------
Este programa no soporta password shadowed, pero es fácilmente modificable para que lo haga, como ya viene escrito en el pppd-2.2.0.
----------------------------------inicio auth.c(actualizado)--------------------------------
/* este programa chequea un usuario y password, basado en el fichero de
password(/etc/passwd) y soporta shadowed también para la
autentificación, también chequea si el usuario es valido.
Retornos de la función:
UPAP_AUTHNAK : logeo fallido.
UPAP_AUTHACK : logeo exitoso.
En algunos casos la variable msg apunta a un mensaje en especial.
(También traducido por z0Rbas) */
#ifdef HAS_SHADOW
#include <shadow.h>
#include <shadow/pwauth.h>
#endif
Static int login(usuario, passwd, msg, largomsg)
Char *usuario;
Char *password;
Char **msg;
Int *largomsg; // definición de los argumentos de la
función.
{
struct passwd *pw; // define puntero a la estructura del registro
char *epasswd; // puntero a lo que será la pass encriptada
char *tty; // puntero a la terminal que se
logeara
#ifdef USE_SHADOW
struct spwd *spwd;
struct spwd *getspnam();
#endif
if((pw=getpwnam(usuario))==NULL) return(UPAP_AUTHNAK);
/* se asigna el valor a la estructura apuntada por pw, con el valor del
registro del usuario correspondiente, usando la función getpwnam() y se
compara al tiro con NULL, si es verdadero, quiere decir que el usuario
no se encontró y el login se rechaza. */
#ifdef USE_SHADOW
spwd = getspnam(usuario);
if(spwd) pw->pw_passwd = spwd->sp_pwdp;
#endif
if(pw->pw_passwd == '\0') return(UPAP_AUTHNAK);
/* se compara el campo de password con NULL, si es verdadero, no hay
password en el campo y el logeo es rechazado ahora por problemas de
seguridad(en el archivo original, esto NO viene corregido) */
#ifdef HAS_SHADOW
if((pw->pw_passwd && pw->pw_passwd[0] == '@' && pw_auth(pw->
pw_passwd+1, pw->pw_name, PW_LOGIN, NULL)) || !valid(password,pw)) {
return(UPAP_AUTHNAK);
}
#else
epasswd = crypt(password, pw->pw_passwd);
/* encripta el password con la semilla del password original. */
if(strcmp(epasswd, pw->pw_passwd)) return(UPAP_AUTHNAK);
/* si son distintos los dos string( epasswd, el password
encriptado(instrucción pasada) y el password original del fichero se
rechaza el login, si a estas alturas aun no se ha rechazado, el sistema
reconoce el password como correcto */
#endif
syslog(LOG_INFO, "user %s logged in", usuario);
/* logea la entrada del usuario en la constante definida por LOG_INFO */
tty = strrchr(devname,'/');
if(tty==NULL) tty=devname;
else tty++;
logwtmp(tty,usuario,"");
logged_in=TRUE;
return(UPAP_AUTHACK);
/* calcula la terminal de logeo y crea un log, luego retorna la función
diciendo que el logeo ha sido exitoso */
}
------------------------------------------fin auth.c(corregido)----------------------------------
En este archivo se corrigió el que se aceptara el logeo sin password, porque sino seria muy fácil para usuarios conseguir una shell por el servidor de ppp. Para compilar este archivo se necesita editar el archivo 'Makefile' de la siguiente manera(agregar):
LIBS = -lshadow
Y se modifica la línea que dice:
COMPILE_FLAGS = -I.. D_linux_=1 DGIDSET_TYPE=gid_t
Por:
COMPILE_FLAGS = -I.. D_linux_=1 DGIDSET_TYPE=gid_t DUSE_SHADOW
Toda la información contenida en este texto esta escrita solo con fines educativos, el mal uso de esta es completa responsabilidad del lector.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ Saludos z0Rbas @
@ the-little@usa.net @
@ #systat_team irc.dal.net @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@