Seguridad en Unix y Redes
Libro en Formato HTML y PDF de seguridad informática realizado por Antonio Villalon Huerta

Los contenidos pueden estar desactualizados con respecto al original

Este documento se encuentra disponible también en formato PDF

Linux


next up previous contents
Siguiente: AIX Subir: Algunos sistemas Unix Anterior: Solaris   Índice General

Subsecciones

Linux

Introducción

A mediados de 1991 un estudiante finlandés llamado Linus Torvalds trabajaba en el diseño de un sistema operativo similar a Minix, que pudiera ejecutarse sobre plataformas Intel y compatibles, y sobre todo que fuera pequeño y barato; a raíz de un mensaje de este estudiante en comp.os.minix, algunas personas comenzaron a interesarse por el proyecto, y finalmente el 5 de octubre de ese año Linus Torvals hizo pública la versión 0.02 - la primera funcional - de lo que ya se denominaba Linux (Linus´ Unix). En esa versión, que aproximadamente utilizaron un centenar de usuarios, apenas se ofrecía soporte a hardware (excepto el que Linus tenía en su ordenador), no disponía de subsistema de red ni de sistema de ficheros propio, y las utilidades de espacio de usuario se podían contar con los dedos de las manos (un shell, un compilador, y poco más). Sin embargo, y a pesar de las duras críticas de pesos pesados en el mundo de los sistemas operativos como Andrew Tanenbaum, el proyecto era muy interesante, y poco a poco programadores de todo el mundo fueron aportando mejoras a este nuevo sistema.

A principios de 1994 apareció Linux 1.0, considerada la primera versión del operativo utilizable no sólo por hackers y programadores, sino por usuarios `normales'; de las aproximadamente 10000 líneas de la versión inicial se había pasado a unas 170000, y el centenar de usuarios se había multiplicado por mil. Linux 1.0 incorporaba subsistema de red (sin duda uno de los cambios que más ha contribuido a la expansión del operativo), entorno gráfico (arrastrado de versiones anteriores) y soporte a una gama de hardware relativamente amplia. La popularidad del operativo crecía mes a mes - especialmente en entornos universitarios y de investigación - gracias sobre todo a su filosofía: cualquiera podía (y puede) modificar una parte del núcleo, adaptarla, mejorarla, o incorporar nuevas líneas, con la única obligación de compartir el nuevo código fuente con el resto del mundo.

Sin embargo, no fué hasta 1996, con la aparición de Linux 2.0 (que incorporaba casi medio millón de líneas de código), cuando se produjo el gran boom de Linux que perdura hasta la actualidad. Esta nueva versión convertía a Linux en un sistema libre que, en algunos aspectos, no tenía nada que envidiar a entornos Unix comerciales; más de un millón de usuarios contribuían sin descanso a mejorar el sistema, y quizás por primera vez la arquitectura PC no era un mercado reservado casi en exclusiva a Microsoft. Muchas personas vieron que Linux podía llegar a ser rentable (a pesar de su filosofía free), y se comenzó a trabajar mucho en la facilidad de instalación y manejo para usuarios sin elevados conocimientos de informática; incluso llegaba a desbancar en muchas ocasiones al inamovible Minix a la hora de estudiar diseño de sistemas operativos en las universidades (algo poco comprensible, por otra parte, ya que cualquiera que le haya pegado un vistazo al código del kernel de Linux podrá comprobar que a diferencia de Minix no está diseñado para ser legible y didáctico, sino para ser rápido).

En la actualidad Linux cuenta con varios millones de usuarios, y se ha convertido en el Unix más user-friendly de todos los existentes, ya que no hacen falta conocimientos avanzados para instalarlo y manejarlo mínimamente; reconoce multitud de hardware (algo que siempre ayuda en el mercado de los ordenadores de sobremesa), y se puede utilizar para funciones tan diversas como servidores web, de bases de datos, de correo electrónico, o como una sencilla workstation. En muchas empresas medianas y pequeñas ha desplazado por completo a los sistemas Unix comerciales (caros y que generalmente corren sobre hardware que tampoco es barato), e incluso en grandes servidores se utiliza Linux como sistema operativo; aunque - y esto es una crítica, por si no queda claro - en algunas ocasiones se echan de menos mecanismos de seguridad que sí están disponibles en otros Unices, podemos decir que Linux proporciona un nivel de seguridad, fiabilidad y estabilidad adecuado a la mayor parte de aplicaciones genéricas que nos podamos imaginar (es decir, no es un operativo apto para controlar una central nuclear, pero sí para cualquier aplicación de criticidad baja o media que podamos utilizar día a día).

Al igual que hemos hecho en el capítulo anterior con Solaris, vamos a hablar en este de aspectos de seguridad específicos de Linux, aunque como siempre lo que hemos comentado para Unix en general es casi siempre aplicable a este clon. Sobre temas propios de Linux podemos obtener información adicional y gratuita a través de Internet, en cualquier documento del proyecto LDP (Linux Documentation Project); también existen numerosos libros sobre aspectos específicos de este operativo, desde la implementación de su núcleo ([BBD$^+$96], [CDM97]...) hasta su seguridad ([Tox00], [Ano01]...), pasando por supuesto por temas de administración genérica ([HN$^+$99], [BPB00]...).

Seguridad física en x86

Si cuando hemos hablado de Solaris hacíamos referencia al nivel de seguridad más bajo que ofrece una máquina SPARC, el correspondiente a su EEPROM, parece obligatorio comentar, en el capítulo dedicado a Linux, de la seguridad de más bajo nivel que ofrece este operativo ejecutándose sobre su principal plataforma: el PC.

Cuando arranca un ordenador personal se ejecuta un software denominado BIOS (Basic I/O System) cuya función principal es determinar una serie de parámetros de la máquina y proporcionar un sistema básico de control de dispositivos, como el teclado o los puertos de comunicaciones; este software se aloja típicamente en una memoria ROM (Read-Only Memory) o flash (estos últimos permiten actualizar la versión de la BIOS sin necesidad de cambiar el chip correspondiente), de forma que se permita autoarrancar a la máquina aunque el subsistema de almacenamiento tenga problemas y no se pueda iniciar el operativo. En el arranque de un PC se puede acceder a la configuración de su BIOS mediante la pulsación de una tecla o una secuencia de escape dependiente de cada modelo de chip; desde ese entorno de configuración se pueden asignar parámetros como la fecha y hora del sistema, la arquitectura de los discos duros, o la habilitación de memorias caché. Por supuesto, la BIOS de un PC es completamente independiente del operativo que se arranque después; esto implica que cuando a continuación comentemos la protección que ofrece una BIOS, lo que digamos será aplicable sin importar qué operativo se ejecute en la máquina: servirá tanto para Linux como para Solaris, FreeBSD, NetBSD...e incluso para las diferentes versiones de Windows. Si lo comentamos en este capítulo dedicado a Linux y no en otro, es únicamente porque la mayor parte de plataformas sobre las que se ejecuta este operativo son ordenadores personales.

Generalmente la mayor parte de las BIOS ofrecen la posibilidad de establecer dos contraseñas independientes. La primera de ellas es una clave que evita a usuarios que no la conozcan acceder a la configuración de la BIOS, ya que se solicitará al pulsar la secuencia de escape de la que hemos hablado antes para entrar en el entorno de configuración durante el arranque de una máquina. El esquema de esta contraseña en muchas ocasiones no es todo lo robusto que debiera, y en función del modelo y versión de cada chip de memoria - especialmente en los más antiguos - es posible que incluso se pueda romper ejecutando un simple programa desde línea de órdenes; a pesar de ello, puede ser útil en entornos de muy baja seguridad para prevenir que cualquiera se dedique a cambiar la configuración de la BIOS, pero más por comodidad (el administrador de la máquina debería restaurar dicha configuración de nuevo, algo bastante molesto sobre todo si el número de ordenadores es elevado, como en un laboratorio o un aula) que por seguridad.

La segunda de estas claves ofrece un nivel de protección algo más elevado; se trata de una contraseña que evita el arranque del PC sin que se teclee el password (en local, por supuesto, recordemos que el operativo aún no se ha inicializado). Con ella se consigue que nadie que no conozca la clave pueda arrancar un ordenador, pero una vez arrancado no sirve para nada más; puede ser útil para evitar que usuarios no autorizados puedan sentarse delante de una máquina y arrancar desde un diskette, aunque seguramente una solución menos agresiva es configurar la BIOS para que sólo arranque desde disco duro, o al menos no trate de hacerlo desde floppy antes que desde el disco primario. No obstante, poner una contraseña para arrancar el sistema, como muchas medidas de seguridad, puede tener efectos negativos en la funcionalidad o en la comodidad de administración: no podremos realizar reboots automáticos ni remotos de Unix, ya que cada vez que el sistema reinicie alguien deberá estar físicamente al lado de la máquina para teclear la clave correspondiente.

Antes de finalizar este punto dedicado a la seguridad física dentro de Linux debemos hablar de la protección ofrecida por LILO; ahora ya no se trata de algo genérico de los PCs, sino de mecanismos implantados en el cargador de Linux que sólo son aplicables a sistemas arrancados desde dicho cargador. LILO (LInux LOader) es un software que se instala en un sector de arranque - de una partición o de un diskette - o en el Master Boot Record (MBR) del disco duro y que permite de esta forma arrancar tanto Linux como otros sistemas operativos instalados en el PC.

LILO toma su configuración del archivo /etc/lilo.conf del sistema Linux; cada vez que modifiquemos ese archivo será necesario ejecutar la orden /sbin/lilo si queremos que los cambios tengan efecto en el siguiente encendido de la máquina:
luisa:~# /sbin/lilo
Added linux *
luisa:~#
Al arrancar el PC, LILO permite elegir una imagen para ser arrancada, así como especificar parámetros para el núcleo; aunque esto sea necesario para inicializar el sistema en ciertas ocasiones - principalmente cuando hay errores graves en un arranque normal -, el hecho es que los parámetros pasados a un kernel antes de ser arrancado pueden facilitar a un atacante un control total sobre la máquina, ya que algunos de ellos llegan incluso a ejecutar un shell con privilegios de root sin necesidad de ninguna contraseña.

En determinadas ocasiones, quizás nos interese proteger el arranque de una máquina, tanto a nivel de la elección de núcleo a arrancar como a nivel de las opciones que se pasan a dicho núcleo. Podemos habilitar desde LILO el uso de una contraseña que se solicitará antes de que LILO cargue cualquier sistema operativo instalado en el ordenador; para ello debemos hacer uso de la directiva `password' en /etc/lilo.conf:
luisa:~# cat /etc/lilo.conf
boot = /dev/hda
delay = 50
vga = normal
password = P,e+bqa
image = /vmlinuz
  root = /dev/hda1
  label = linux
  read-only 
luisa:~#
Tras ejecutar /sbin/lilo, en el siguiente arranque de la máquina se solicitará la contraseña especificada antes de arrancar cualquier sistema operativo; es importante que /etc/lilo.conf tenga un permiso de lectura sólo para el root, ya que como podemos ver contiene contraseñas sin cifrar.

Evidentemente, si elegimos este tipo de protección nos podemos olvidar de cualquier cosa que implique un reinicio automático del ordenador, ya que como acabamos de decir siempre se solicitará una clave en el arranque; podemos relajar esta restricción de una forma muy útil: forzando el uso de un password sólo si se especifican parámetros adicionales para el núcleo que se quiere arrancar. Esto permite un equilibrio más que razonable entre seguridad y usabilidad, ya que cualquiera - con los privilegios necesarios - puede reiniciar el sistema, e incluso se pueden programar rearranques automáticos, pero siempre será necesaria una clave si alguien desea especificar parámetros adicionales al kernel.

Para conseguir esto utilizaremos la directiva `restricted' en conjunción con `password' en el archivo de configuración de LILO; basándonos en el ejemplo anterior, el contenido del nuevo fichero sería el siguiente:
luisa:~# cat /etc/lilo.conf
boot = /dev/hda
delay = 50
vga = normal
password = P,e+bqa
restricted
image = /vmlinuz
  root = /dev/hda1
  label = linux
  read-only
luisa:~#
Los dos parámetros que acabamos de ver (`password' y `restricted' se pueden utilizar y combinar bien en la configuración general de LILO - como lo hemos visto aquí - o bien en la configuración particular de cada uno de los sistemas operativos a arrancar; si queremos esto último no tenemos más que incluir las directivas dentro de la configuración de las entradas `image' (imágenes del kernel de Linux) u `other' (arranque de otros sistemas operativos) en lugar de hacerlo antes de estas entradas:
luisa:~# cat /etc/lilo.conf
boot = /dev/hda
delay = 50
vga = normal
image = /vmlinuz
  root = /dev/hda1
  label = linux
  read-only
  password = 66n4k
  restricted
luisa:~#
De esta forma podemos particularizar claves para cada sistema o núcleo, definir entradas en las que se necesitará la clave sólo si pasamos parámetros en el arranque, entradas en las que se necesitará siempre, entradas en las que no se necesitará nunca, etc.

Para finalizar este punto, es necesario recordar una vez más que una correcta configuración del arranque en la BIOS es imprescindible para garantizar la seguridad física de la máquina; sin ir más lejos, si un pirata consigue arrancar el ordenador desde un diskette, poseerá un control total del sistema sin importar las claves que tengamos definidas en /etc/lilo.conf.

Usuarios y accesos al sistema

En un sistema Linux es posible controlar ciertos parámetros referentes al acceso de los usuarios a través de telnet o r- mediante el fichero /etc/login.defs; sus directivas no afectan directamente - aunque algunas de ellas sí de una forma indirecta - a las conexiones a través de otros mecanismos como SSH, que poseen sus propios ficheros de configuración. Como siempre, insistimos en la necesidad de sustituir todos los protocolos en claro por equivalentes cifrados, con lo que ni telnet ni r- deberían existir como servicio en una máquina Unix, pero de cualquier forma vamos a comentar algunas directivas del fichero anterior que pueden resultar interesantes para nuestra seguridad, tanto si afectan a las conexiones remotas como si no; para obtener información acerca del resto de directivas - y también de las comentadas aquí - podemos consultar la página del manual de login.defs(5).

La primera de las directivas que encontramos en /etc/login.defs es FAIL/SMALL>_DELAY, que marca el número de segundos (por defecto, 3) que el sistema introduce como retardo desde que se introduce un nombre de usuario o contraseña incorrectos hasta que se vuelve a solicitar el login de entrada al sistema; el número máximo de intentos antes de que se cierre la conexión viene determinado por el valor de LOGIN/SMALL>_RETRIES, por defecto a 5, y el tiempo máximo durante el que se permite la entrada antes de cerrar la conexión se especifica mediante la directiva LOGIN/SMALL>_TIMEOUT (60 segundos por defecto).

Cuando un usuario se equivoca en su nombre de entrada o su clave entran en juego un par de directivas más: FAILLOG/SMALL>_ENAB y LOG/SMALL>_UNKFAIL/SMALL>_ENAB. La primera de ellas, con un valor por defecto a `yes' (el adecuado para nuestra seguridad), se encarga de registrar los intentos fallidos de acceso al sistema en /var/log/faillog, así como de mostrar un mensaje con información acerca del último intento de acceso fallido cuando un usuario accede a la máquina. Por su parte, LOG/SMALL>_UNKFAIL/SMALL>_ENAB habilita o deshabilita el registro del nombre de usuario que ha fallado al tratar de entrar al sistema; es importante que su valor sea `no' (es decir, que ese nombre de usuario no se registre) por una sencilla razón: en muchas ocasiones los usuarios teclean su password en lugar de su login cuando tratan de acceder a la máquina, con lo que si el nombre de usuario incorrecto - la clave - se registra en un fichero en texto plano, y ese fichero tiene unos permisos inadecuados, cualquiera con acceso de lectura sobre el archivo de log podría deducir fácilmente el nombre y la clave del usuario. Adicionalmente, si la directiva FTMP/SMALL>_FILE define un archivo (por defecto, /var/log/btmp), en el mismo se registra cada intento de acceso fallido en formato utmp(5); dicha información se puede consultar mediante la orden lastb.

Si se produce la situación contraria a la que acabamos de comentar (es decir, el usuario teclea correctamente tanto su nombre como su contraseña), la directiva LOG/SMALL>_OK/SMALL>_LOGINS habilita o deshabilita el registro de este hecho en función de si su valor es `yes' o `no'; además, si LASTLOG/SMALL>_ENAB tiene un valor `yes' se registra la entrada en /var/log/lastlog y se muestra al usuario información acerca de su última conexión. En caso de que el usuario no pueda acceder a su directorio $HOME (bien porque no existe, bien por los permisos del mismo) entra en juego DEFAULT/SMALL>_HOME, y en caso de que su valor sea `no' no se permite el acceso; por el contrario, si su valor es `yes', el usuario entra al directorio raiz de la máquina.

En /etc/login.defs también se pueden definir líneas para las que no es necesaria ninguna contraseña, mediante la directiva NO/SMALL>_PASSWORD/SMALL>_CONSOLE: si alguien trata de conectar al sistema a través de ellas, se le solicitará su nombre de usuario pero no su clave; esto es aplicable para todos los usuarios de la máquina excepto para el administrador, al que siempre se le pide su password. Evidentemente, para incrementar la seguridad de nuestro sistema la directiva correspondiente ha de estar comentada.

El acceso a la cuenta del superusuario también viene determinado por ciertas directivas del archivo /etc/login.defs. En primer lugar, sólo se permiten accesos directos como root desde las líneas definidas en la entrada CONSOLE, que puede ser bien un archivo que contiene los nombres de dispositivos desde los que permitimos la entrada del administrador, o bien una relación de esos dispositivos separados por el carácter `:'; por defecto, en un sistema Linux esta entrada referencia al archivo /etc/securetty, que es un listado de terminales de la forma siguiente:
luisa:~# cat /etc/securetty
console
tty1
tty2
tty3
tty4
tty5
tty6
luisa:~#
Como hemos dicho, la función del anterior archivo es muy similar a la de la directiva `CONSOLE' en el fichero /etc/default/login de Solaris; si no existe, el administrador puede conectar en remoto desde cualquier lugar, mientras que si existe pero está vacío sólo se pueden alcanzar privilegios de root a través de la ejecución de `su'. En cualquier caso, el fichero no evita ni controla las conexiones remotas como superusuario a través de mecanismos como SSH o X Window, que poseen sus propios ficheros de configuración.

El contenido o la existencia de /etc/securetty tampoco evita de ninguna forma la ejecución de `su'; para ello existen otras directivas que controlan el acceso y el comportamiento de esta orden en el sistema. La primera de ellas es SU/SMALL>_WHEEL/SMALL>_ONLY, que si posee un valor `yes' indica que sólo los usuarios miembros del grupo `root'11.1 van a ser capaces de cambiar su identidad mediante `su' a un usuario con privilegios de administrador (UID 0); si este grupo no existe o está vacio, nadie podrá ejecutar `su' para convertirse en superusuario.

En el archivo /etc/suauth (completamente independiente de /etc/login.defs) se puede realizar un control más minucioso de la ejecución de `su', no sólo para acceder a cuentas con privilegios de administración, sino también para permitir o denegar cambios de identidad entre dos usuarios cualesquiera del sistema. Se trata de un fichero en el que cada línea es de la forma
                             to-id:from-id:ACCION
donde ACCION puede ser DENY (se deniega el cambio de identidad), NOPASS (se permite el cambio sin necesidad de ninguna clave) u OWNPASS (se solicita el password del usuario que ejecuta la orden en lugar del correspondiente al usuario al que se quiere acceder); se puede consultar la página del manual suauth(5) para conocer la sintaxis exacta del archivo. Por ejemplo, si queremos que el usuario `toni' no pueda ejecutar `su' para cambiar su identidad a `root', pero que para convertirse en `prova' sólo tenga que teclear su propia contraseña, tendremos un fichero similar al siguiente:
luisa:~# cat /etc/suauth 
root:toni:DENY
prova:toni:OWNPASS
luisa:~#
Cuando toni trate de hacer `su' a las cuentas anteriores, verá algo similar a:
luisa:~$ id
uid=1000(toni) gid=100(users) groups=100(users)
luisa:~$ su 
Access to su to that account DENIED.
You are not authorized to su root
luisa:~$ luisa:~$ su prova
Please enter your OWN password as authentication.
(Enter your own password.)
Password: 
luisa:/home/toni$ id
uid=1006(prova) gid=100(users) groups=100(users)
luisa:/home/toni$
Es importante destacar que el contenido del fichero /etc/suauth sólo afecta a usuarios regulares, no al root: no podemos definir reglas que eviten que el administrador de un sistema cambie su identidad a cualquier usuario del mismo. Esto es algo evidente, ya que si no se permitiera al root cambiar su identidad, este no tendría más que modificar el fichero para eliminar la regla que se lo impide.

Volviendo a /etc/login.defs, el registro de las ejecuciones de `su' también se controla desde este fichero; la directiva SULOG/SMALL>_FILE define el archivo donde se registran las ejecuciones de esta orden, tanto si son exitosas como si no. Además, si el valor de SYSLOG/SMALL>_SU/SMALL>_ENAB es `yes' se guarda un registro adicional a través de syslogd en el archivo correspondiente; existe una directiva análoga a esta última denominada SYSLOG/SMALL>_SG/SMALL>_ENAB, que registra también a través de syslogd los cambios de grupo que se producen en el sistema (por ejemplo, mediante la ejecución de la orden newgrp).

Antes de dejar de lado los aspectos relacionados con `su' vamos a comentar una directiva muy interesante: se trata de SU/SMALL>_NAME, que define el nombre de programa que un `ps' muestra cuando alguien ejecuta la orden `su -' (un cambio de identidad emulando un proceso de login directo) en el sistema. Su valor por defecto es `su', lo que indica que si alguien cambia su identidad de la forma que acabamos de ver, un listado de procesos mostrará algo similar a lo siguiente:
luisa:~$ su - prova
Password: 

Famous, adj.:
        Conspicuously miserable.
                -- Ambrose Bierce

luisa:~$ ps xuw
prova    19990  0.8  0.3  1708  984 pts/8    S    07:04   0:00 -su
prova    20001  0.0  0.2  2548  908 pts/8    R    07:04   0:00 ps xuw
luisa:~$
Como vemos, en lugar del shell que el usuario esté utilizando, aparece el nombre de programa `-su' en la última columna del listado; si la directiva no estuviera definida sí que aparecería el nombre del shell correspondiente. Puede resultar interesante redefinir la directiva SU/SMALL>_NAME para asignarle un valor que pueda resaltar más en el listado, de forma que el administrador pueda ver quien ha ejecutado la orden `su -' de una forma más directa:
luisa:~# grep ^SU_NAME /etc/login.defs 
SU_NAME         ***SU***
luisa:~# su - prova

f u cn rd ths, u cn gt a gd jb n cmptr prgrmmng.

luisa:~$ ps xuw
USER       PID %CPU %MEM   VSZ  RSS TTY      STAT START   TIME COMMAND
prova    20083  7.0  0.3  1712  988 pts/8    S    07:11   0:00 -***SU***
prova    20094  0.0  0.2  2548  912 pts/8    R    07:11   0:00 ps xuw
luisa:~$
A la hora de definir nuevos usuarios en el sistema también entran en juego ciertas directivas del archivo /etc/login.defs. Por ejemplo, el UID y GID máximo y mínimo para los usuarios regulares viene determinado por UID/SMALL>_MAX, GID/SMALL>_MAX, UID/SMALL>_MIN y GID/SMALL>_MIN. También existen entradas para especificar ciertas características relacionadas con las claves de los nuevos usuarios del sistema: se trata de PASS/SMALL>_MAX/SMALL>_DAYS, PASS/SMALL>_MIN/SMALL>_DAYS, PASS/SMALL>_MIN/SMALL>_LEN y PASS/SMALL>_WARN/SMALL>_AGE. Como sus nombres indican, estas directivas marcan los números máximo y mínimo de días que una contraseña puede ser usada, la longitud mínima que todo password ha de tener, y el número de días de antelación con que se avisará a los usuarios antes de que sus claves caduquen, respectivamente. Cada vez que se crea a un usuario nuevo en el sistema, se toman estos valores por defecto, aunque después es habitual particularizar para cada caso concreto.

Cuando un usuario ejecuta la orden passwd para cambiar su contraseña en el sistema entra en juego la directiva OBSCURE/SMALL>_CHECKS/SMALL>_ENAB (cuyo valor ha de ser `yes') para impedir que se elijan claves débiles (por ejemplo, las formadas únicamente por letras minúsculas); adicionalmente, si la orden está enlazada a cracklib (esto se realiza en tiempo de compilación) y la directiva CRACKLIB/SMALL>_DICTPATH está definida y tiene como valor la ruta de un directorio, se buscan en el mismo ficheros de diccionario contra los que comparar la nueva contraseña, rechazándola si se encuentra en alguno de ellos. En cualquier caso, se permiten tantos intentos de cambio como indica PASS/SMALL>_CHANGE/SMALL>_TRIES, y si se supera este número se devuelve al usuario a su shell y la clave permanece inalterada; si el usuario que trata de cambiar el password es el root - tanto la propia clave como la de cualquier usuario - se le permite elegir cualquier contraseña, sin importar su robustez, pero se le advierte de que la clave elegida es débil si el valor de directiva PASS/SMALL>_ALWAYS/SMALL>_WARN es `yes'.

Al ejecutar passwd para modificar valores del campo GECOS o para cambiar el shell de entrada al sistema (o equivalentemente, al ejecutar chfn o chsh) se solicita al usuario su clave si el valor de la directiva chfn_auth es `yes'. Además, la entrada CHFN/SMALL>_RESTRICT define los campos concretos que un usuario puede modificar: Full Name, Room Number, Work Phone y Home Phone (`frwh' si permitimos que modifique todos ellos); si la directiva no está definida, no se permite ningún tipo de cambio.

Relacionada también con las contraseñas de los usuarios, la directiva PASS/SMALL>_MAX/SMALL>_LEN marca el número de caracteres significativos en una clave (8 por defecto); no obstante, esta entrada es ignorada si el valor de MD5/SMALL>_CRYPT/SMALL>_ENAB es `yes', lo que indica que el sistema acepta passwords basados en MD5, que proporcionan una longitud para las claves ilimitada y salts más largos que el esquema clásico de Unix. La única razón para asignarle a esta última directiva un valor `no' en los Linux modernos es por razones de compatibilidad, ya que la seguridad que proporciona este tipo de claves es mucho mayor que la proporcionada por los mecanismos habituales de Unix.

Dejando ya de lado el archivo /etc/login.defs, pero siguiendo con la gestión de las contraseñas de usuario, para consultar el estado de las mismas en un sistema Linux hemos de ejecutar la orden `passwd -S' seguida del nombre del usuario correspondiente; por desgracia, en Linux no existe un parámetro `-a' similar al de Solaris, que muestre información de todos los usuarios, por lo que hemos de hacer la consulta uno a uno:
luisa:~# for i in `awk -F: '{print $1}' /etc/passwd`
> do
> passwd -S $i
> done
root P 12/28/2000 0 -1 -1 -1
bin L 10/28/1996 0 -1 -1 -1
daemon L 10/28/1996 0 -1 -1 -1
adm L 10/28/1996 0 -1 -1 -1
lp L 10/28/1996 0 -1 -1 -1
sync L 10/28/1996 0 -1 -1 -1
shutdown L 10/28/1996 0 -1 -1 -1
halt L 10/28/1996 0 -1 -1 -1
mail L 10/28/1996 0 -1 -1 -1
news L 10/28/1996 0 -1 -1 -1
uucp L 10/28/1996 0 -1 -1 -1
operator L 10/28/1996 0 -1 -1 -1
games L 10/28/1996 0 -1 -1 -1
ftp L 10/28/1996 0 -1 -1 -1
gdm L 10/28/1996 0 -1 -1 -1
nobody L 10/28/1996 0 -1 -1 -1
toni P 12/29/2000 0 99999 7 -1
prova NP 10/23/2001 0 99999 7 -1
luisa:~#
El segundo campo de cada línea del listado anterior proporciona el estado de la clave correspondiente: `P' si el usuario tiene contraseña, `L' si la cuenta está bloqueada, y `NP' si el usuario no tiene clave asignada; en este último caso es muy importante poner un password al usuario o bien bloquear su acceso:
luisa:~# passwd -S prova
prova NP 10/23/2001 0 99999 7 -1
luisa:~# passwd -l prova
Password changed.
luisa:~# passwd -S prova
prova L 10/23/2001 0 99999 7 -1
luisa:~#
El resto de campos del listado hacen referencia propiedades de envejecimiento de las claves: cuando se cambió la contraseña de cada usuario por última vez, cuales son sus periodos de validez máximo y mínimo, el periodo de aviso y el periodo de inactividad antes de bloquear el acceso de forma automática; también mediante passwd (o equivalentemente mediante chage) podemos - como root - modificar esta información para cada uno de nuestros usuarios. Por ejemplo, si queremos que la clave del usuario `prova' tenga un periodo de validez máximo de un mes y mínimo de 10 días, que se avise al usuario de que su clave va a caducar con una antelación de una semana, y que si una vez la clave ha caducado el usuario no entra al sistema en cinco días se bloquee su cuenta podemos conseguirlo con la siguiente orden:
luisa:~# passwd -S prova
prova L 10/23/2001 0 99999 7 -1
luisa:~# passwd -x 30 -n 10 -w 7 -i 5 prova
Password changed.
luisa:~# passwd -S prova
prova L 10/23/2001 10 30 7 5
luisa:~#
Como en este caso la cuenta está bloqueada, los cambios tendrán efecto cuando esta se desbloquee (o directamente se le asigne una nueva clave) y comience a ser utilizada; a diferencia de otros sistemas Unix, el desbloqueo de un acceso en Linux guarda una especie de estado: conserva la contraseña que el usuario tenía antes de que su cuenta fuera bloqueada.

El sistema de parcheado

A la hora de hablar de actualizaciones en Linux debemos distinguir entre la actualización del núcleo del operativo y la de las diferentes aplicaciones del sistema. Esta última no es en ningún momento algo tan estándar como lo pueda ser en Unices comerciales como Solaris o AIX debido a que no hay un único modelo de Linux en el mercado, sino que existen diferentes clones de este operativo, y a pesar de que todos son `Linux' en cada uno de ellos se trabaja de forma diferente. Por contra, si lo que se va a actualizar es el núcleo de Linux en cualquiera de ellos se procede de la misma forma, ya que el kernel es común a todas las distribuciones. Vamos a hablar primero brevemente de los diferentes métodos de actualización de aplicaciones en función del Linux utilizado y después comentaremos aspectos de actualización y parcheado del núcleo.

En Red Hat, y en todos los Linux basados en esta distribución (Mandrake, Caldera...) el software se suele descargar precompilado desde Internet en un formato especial denominado RPM (Red Hat Package Manager), que permite al administrador de un sistema instalar y desinstalar programas, así como verificar versiones del software instalado en la máquina; podemos encontrar una extensa descripción de este formato en [Bai97], aunque la idea más elemental acerca del mismo es que se trata de un sistema de gestión (instalación, actualización, borrado...) de software capaz de hacer comprobaciones de dependencia entre paquetes, ejecutar programas de pre y postinstalación y detectar y solventar cierto tipo de conflictos entre diferentes paquetes o diferentes versiones del mismo.

Actualizar versiones de software mediante rpm es una tarea sencilla: normalmente el administrador no tiene más que ejecutar `rpm -U', orden que se encargará de instalar la nueva versión y eliminar la antigua (si existe); es equivalente a ejecutar primero una instalación (`rpm -i') del paquete actualizado y después un borrado (`rpm -e') de la versión anteriormente instalada en la máquina. Lo más habitual es ver en primer lugar la versión concreta de un paquete soft instalado en la máquina, mediante `rpm -q' (`rpm -qa' nos mostrará un listado de todos y cada uno de los paquetes instalados):
rosita:~# rpm -q libpcap
libpcap-0.4-10
rosita:~#
Tras esto podemos conseguir una versión actualizada (el paquete en formato RPM del software que nos interese) e instalarla mediante las órdenes vistas anteriormente:
rosita:~# ls -l libpcap-0.4-19.i386.rpm 
-rw-r--r--   1 root    root      56554 Feb 18 03:54 libpcap-0.4-19.i386.rpm
rosita:~# rpm -U libpcap-0.4-19.i386.rpm 
rosita:~# rpm -q libpcap
libpcap-0.4-19
rosita:~#
Por su parte, en Debian y derivados, la actualización de software se puede llevar a cabo mediante `dpkg', que permite instalar, configurar y eliminar paquetes; no obstante, su uso - al menos de forma directa - hoy en día es poco habitual debido a la existencia de otra herramienta denominada APT (Advanced Package Tool), posiblemente el mecanismo de instalación y actualización de software más cómodo de cuantos existen en Linux. La principal interfaz entre este sistema y el usuario es `apt-get', herramienta de línea de órdenes cuyo funcionamiento se basa especialmente en el fichero /etc/apt/sources.list, que como su nombre indica es un registro de las fuentes desde las que se pueden obtener paquetes actualizados.

Los paquetes de software en Debian suelen tener un nombre finalizado en `.deb', que realmente es un `.ar' con algunas modificaciones; para obtener un listado actualizado de los paquetes disponibles en las diferentes ubicaciones indicadas en /etc/apt/sources.list no tenemos más que ejecutar la orden `apt-get update' (algo recomendable cada vez que se modifique el fichero de fuentes), que conectará a cada una de dichas ubicaciones y descargará la lista de paquetes actualizados; esta orden no instala esos paquetes, por lo que a continuación deberemos ejecutar `apt-get install' o, directamente `apt-get upgrade' para actualizar las versiones del software ya instalado:
rosita:~# apt-get upgrade
Reading Package Lists... Done
Building Dependency Tree... Done
0 packages upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
rosita:~#
Tanto Red Hat como Debian proporcionan mecanismos para verificar - hasta cierto punto - la integridad de cada paquete instalado; realmente, más que la integridad, podríamos hablar de las modificaciones sufridas por archivos instalados a partir de un paquete con respecto a los originales (qué ficheros han sido modificados desde la instalación), ya que no se trata de comprobaciones de integridad que nos permitan decir si un paquete ha sido troyanizado o no, sino simples verificaciones de presencia de ficheros modificados. Como casi siempre, para comprobar realmente la autenticidad del software debemos recurrir a funciones resumen tipo MD5.

El Linux más arcaico (pero no por ello el peor, ni mucho menos) a la hora de actualizar software es Slackware; en esta distribución de Linux el formato de paquete es sin duda el más estándar de todos: el software se distribuye en ficheros .tgz, que no son más que archivos .tar.gz compatibles con cualquier Unix, con unas pequeñas modificaciones para poderlos instalar mediante installpkg, un sencillo shellscript. En cualquier caso, ni siquiera suele ser necesaria esta utilidad para instalar ficheros .tgz: es suficiente con desempaquetarlos y descomprimirlos desde el directorio raíz de la máquina.

En Slackware podemos utilizar el comando upgradepkg para actualizar un paquete de software determinado; esta orden no es más que otro shellscript que instala el paquete nuevo y elimina los ficheros de la versión antigua que no existan en la nueva. Sin embargo, entre los administradores de Slackware - me incluyo en el grupo - es mucho más habitual descargar el código fuente de la aplicación a actualizar, compilarlo e instalarlo por encima de la versión antigua (o eliminar esta primero, de forma manual).

En cuanto al kernel de Linux y su actualización, como antes hemos comentado, si lo que queremos es actualizar o parchear el núcleo del sistema operativo la forma de hacerlo ya no es tan dependiente de la versión de Linux utilizada. Para actualizar la versión del kernel tenemos dos opciones: o descargamos el código fuente de la nueva versión, de forma completamente independiente de la que tengamos en estos momentos, o descargamos un parche que al ser aplicado nos modificará el código instalado ya en nuestro sistema para convertirlo en la nueva versión; en ambos casos debemos compilar y arrancar con la imagen generada si queremos que el sistema quede actualizado.

Si hemos descargado un nuevo kernel completo (generalmente un fichero .tar.gz) no tenemos más que descomprimirlo, desempaquetarlo y compilarlo para generar una nueva imagen con el nuevo núcleo; evidentemente no vamos a entrar ahora en como configurar o compilar el kernel de Linux: para eso hay excelentes documentos disponibles en la red.

Más interesante es la aplicación de parches al código fuente, bien para incrementar la versión del núcleo, como ya hemos dicho, o bien para aplicar una modificación `no oficial' distribuida como parche; en este último caso - cada vez menos utilizado, debido al desarrollo de los módulos cargables en tiempo de ejecución - hemos de tener cuidado, ya que al aplicar un parche no oficial es muy probable que si posteriormente deseamos incrementar la versión de nuestro kernel también mediante parcheado del código, este último no funcione correctamente.

Lo más habitual es que cualquier parche para el código fuente del núcleo, tanto oficial como `no oficial', se distribuya como un simple fichero de texto (en muchos casos comprimido con gzip) que contiene las diferencias entre el código actual y el modificado, generadas con diff; podemos verificarlo simplemente echando un vistazo al fichero que acabamos de descargar:
luisa:/usr/src# gzip -dc patch-2.2.14.gz|head -4
diff -u --recursive --new-file v2.2.13/linux/CREDITS linux/CREDITS
--- v2.2.13/linux/CREDITS       Tue Jan  4 11:24:09 2000
+++ linux/CREDITS       Tue Jan  4 10:12:10 2000
@@ -137,12 +137,9 @@
luisa:/usr/src#
Si este es nuestro caso, para aplicar el parche no tenemos más que utilizar la orden `patch':
luisa:/usr/src# gzip -dc patch-2.2.14.gz | /usr/bin/patch -p0
Si el proceso ha sido correcto, el código fuente que en nuestro ejemplo antes correspondía al núcleo 2.2.13 ahora corresponde al 2.2.14; como antes, no tenemos más que recompilar ese código y arrancar con la imagen generada para que nuestro kernel sea el nuevo:
luisa:~# uname -a
Linux luisa 2.2.14 #9 Sat Dec 30 03:34:32 CET 2000 i686 unknown
luisa:~#


El subsistema de red

Lamentablemente en Linux no existe una orden similar a ndd en Solaris o a no en AIX, que como hemos visto o veremos más adelante se utilizan para configurar en tiempo de ejecución, y de una forma sencilla, parámetros del operativo relativos al subsistema de red. En este caso necesitamos recurrir, en la mayoría de ocasiones, a escribir directamente en ficheros del directorio /proc/, un sistema de archivos `virtual' que en Linux y otros entornos Unix actúa como interfaz ente el espacio de usuario y el núcleo.

Con relación a la tabla ARP y sus timeouts (como ya dijimos al hablar de Solaris, importantes en nuestra seguridad para prevenir cierto tipo de ataques), su funcionamiento en Linux es quizás algo engorroso: en los directorios /proc/sys/net/ipv4/neigh// tenemos ciertos ficheros que indican parámetros de configuración de ARP; uno de ellos, gc_stale_time, define el tiempo (60 segundos por defecto) que una entrada de la tabla ARP se considera `viva'. Cuando este timeout ha vencido para cierta entrada, entra en juego el parámetro gc_interval, que marca la frecuencia de actuación del recolector de basura (garbage collector) en el sistema; este, que se ejecuta cada 30 segundos, es realmente el encargado de eliminar las entradas `caducadas' de nuestra tabla.

Respecto al protocolo ICMP, una buena idea - como en cualquier Unix - es ignorar las peticiones ICMP/SMALL>_ECHO dirigidas a direcciones de broadcast; esto lo conseguimos escribiendo un valor diferente de 0 en el archivo /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts. Si no nos conformamos con esto, y queremos ignorar todas las peticiones ICMP/SMALL>_ECHO, no tenemos más que hacer lo mismo en el archivo /proc/sys/net/ipv4/icmp_echo_ignore_all.

La gestión de tramas ICMP/SMALL>_REDIRECT también puede presentar ciertos riesgos para nuestra seguridad. Si en /proc/sys/net/ipv4/conf/*/accept_redirects indicamos un valor diferente de 0 estamos diciéndole al núcleo de Linux que haga caso de este tipo de paquetes que en principio nos puede enviar un enrutador; su valor por defecto (0, desactivado) es el correcto. Análogamente, en /proc/sys/net/ipv4/conf/*/send_redirects permitimos la emisión de estas tramas desde nuestra máquina escribiendo un valor diferente de 0; como sólo un router debería enviar estos paquetes, la opción más segura es especificar un 0 para este parámetro (su valor por defecto es 1). Una opción intermedia entre bloquear todas las tramas ICMP/SMALL>_REDIRECT y permitirlas puede ser el escribir en el archivo secure_redirects un valor diferente de 0, logrando que se acepten este tipo de paquetes pero sólo desde la lista de gateways válidos definida en /etc/gateways.

Pasando ya a hablar del protocolo IP, uno de los parámetros que más nos va a interesar es la habilitación o deshabilitación del IP Forwarding en el núcleo de Linux; como hemos dicho antes, el sistema de filtrado de paquetes sólo funciona cuando esta opción está habilitada, lo que se consigue con la orden
luisa:~# echo 1 > /proc/sys/net/ipv4/ip_forward
luisa:~#
Sin embargo, si no utilizamos las facilidades de firewalling del núcleo de Linux esta opción ha de estar desabilitada (introduciríamos un `0' en lugar de un `1' en el fichero anterior), ya que de lo contrario corremos el peligro de que nuestra máquina se convierta en un router.

Antes hemos hablado de las `SYN Cookies', y hemos comentado que aunque el soporte para esta característica se introduce al compilar el núcleo, realmente el mecanismo se ha de activar desde espacio de usuario, por ejemplo con una orden como la siguiente:
luisa:~# echo 1 >/proc/sys/net/ipv4/tcp_syncookies
luisa:~#
También es muy recomendable que el subsistema de red del kernel descarte las tramas con Source Routing o encaminamiento en origen activado. Este tipo de paquetes contienen el camino que han de seguir hasta su destino, de forma que los routers por los que pasa no han de examinar su contenido sino simplemente reenviarlo, hecho que puede causar la llegada de datos que constituyan una amenaza a nuestras políticas de seguridad. En los núcleos 2.0 esto se conseguía activando la opción CONFIG/SMALL>_IP/SMALL>_NOSR a la hora de compilar el kernel, mientras que en los 2.2 la forma más sencilla de ignorar estos paquetes es introduciendo un `0' en los diferentes ficheros accept_source_route del directorio /proc/sys/net/ipv4/; por ejemplo la siguiente orden descarta las tramas con encaminamiento en origen que llegan al dispositivo de red eth0:
luisa:~# echo 0 >/proc/sys/net/ipv4/conf/eth0/accept_source_route
luisa:~#
Hemos de recordar que las modificaciones que hacemos sobre el interfaz sysctl son dinámicas: se pueden efectuar con el sistema funcionando, sin necesidad de reiniciarlo, pero se pierden cuando la máquina se apaga para establecerse a unos valores por defecto al arrancar de nuevo el sistema operativo; seguramente nos interesará mantener los cambios realizados, por lo que en alguno de los ficheros de inicialización de la máquina hemos de incluir las órdenes que acabamos de explicar, obviamente después de haber montado el sistema de ficheros /proc/.


El núcleo de Linux

Opciones de compilación

A la hora de recompilar un nuevo núcleo de Linux hemos de tener en cuenta algunas opciones dentro del grupo `Networking Options' que pueden afectar a la seguridad de nuestra máquina (algunos de estos aspectos, para núcleos 2.0, pueden encontrarse en [Wre98]). Sin embargo, antes de entrar en detalles con opciones concretas, es muy conveniente que introduzcamos soporte para sistemas de ficheros proc en `Filesystems' (CONFIG/SMALL>_PROC/SMALL>_FS) y activemos el interfaz sysctl en `General Setup' (CONFIG/SMALL>_SYSCTL); con estos pasos habilitamos la capacidad de Linux para modificar ciertos parámetros del núcleo (en /proc/sys/) sin necesidad de reiniciar el sistema o recompilar el kernel.

Pasando ya a comentar algunas opciones que nos ofrece Linux, es bastante interesante para la seguridad configurar nuestro sistema como un cortafuegos a la hora de compilar el núcleo (CONFIG/SMALL>_IP/SMALL>_FIREWALL). Linux ofrece en su kernel facilidades para definir un firewall de paquetes en el sistema, que además permitirá el IP-Masquerading. Para que el subsistema de filtrado funcione es necesario que el IP-Forwarding esté activado de la forma que más tarde veremos.

Otra opción que nos puede ayudar a incrementar la seguridad de nuestro equipo es la defragmentación de paquetes (CONFIG/SMALL>_IP/SMALL>_ALWAYS/SMALL>_DEFRAG) que llegan a través de la red. Cuando un equipo situado entre el origen y el destino de los datos decide que los paquetes a enviar son demasiado grandes, los divide en fragmentos de longitud menor; sin embargo, los números de puerto sólamente viajan en el primer fragmento, lo que implica que un atacante puede insertar información en el resto de tramas que en teoría no debe viajar en ellas. Activando esta opción, en nuestra máquina estos fragmentos se reagruparán de nuevo incluso si van a ser reenviados a otro host.

Siguiendo con las diferentes opciones del subsistema de red, podemos habilitar el soporte para `SYN Cookies' (CONFIG/SMALL>_SYN/SMALL>_COOKIES) en el núcleo que estamos configurando. Una red TCP/IP habitual no puede soportar un ataque de negación de servicio conocido como `SYN Flooding', consistente básicamente en enviar una gran cantidad de tramas con el bit SYN activado para así saturar los recursos de una máquina determinada hasta que los usuarios no pueden ni siquiera conectar a ella. Las `SYN Cookies' proporcionan cierta protección contra este tipo de ataques, ya que la pila TCP/IP utiliza un protocolo criptográfico para permitir que un usuario legítimo pueda seguir accediendo al sistema incluso si este está siendo atacado. Aunque configuremos y ejecutemos un núcleo con esta opción soportada, hemos de activar las `SYN Cookies' cada vez que el sistema arranca (como veremos luego), ya que por defecto están deshabilitadas.

En ciertas situaciones es interesante analizar en espacio de usuario - es decir, sin sobrecargar al núcleo más de lo estrictamente necesario - un paquete o parte de él (típicamente, los 128 primeros bytes) que llega a través de la red hasta nuestra máquina; de esta forma, un analizador simple puede tomar ciertas decisiones en función del contenido del paquete recibido, como enviar un correo al administrador en caso de sospecha o grabar un mensaje mediante syslog. Justamente esto es lo que conseguimos si habilitamos la opción Firewall Packet Netlink Device (CONFIG/SMALL>_IP/SMALL>_FIREWALL/SMALL>_NETLINK).

Hasta ahora hemos hablado de la posibilidad que tiene Linux para modificar parámetros del núcleo sin necesidad de recompilarlo o de reiniciar el equipo, mediante el interfaz sysctl; esto implica por ejemplo que podemos modificar el comportamiento del subsistema de red simplemente modificando determinados ficheros de /proc/sys/ (recordemos que el sistema de ficheros /proc/ de algunos Unix es una interfaz entre estructuras de datos del núcleo y el espacio de usuario). Veremos en el punto 10.5 algunos de estos parámetros configurables que tienen mucho que ver con la seguridad Linux, en especial con el subsistema de red.

Dispositivos

Linux (no así otros Unices) proporciona dos dispositivos virtuales denominados /dev/random y /dev/urandom que pueden utilizarse para generar números pseudoaleatorios, necesarios para aplicaciones criptográficas. El primero de estos ficheros, /dev/random, utiliza lo que su autor denomina `ruido ambiental' (por ejemplo, temporizadores de IRQs, accesos a disco o tiempos entre pulsaciones de teclas) para crear una fuente de entropía aceptable y - muy importante - que apenas introduce sobrecarga en el sistema. El segundo archivo, /dev/urandom, crea un resumen de la entropía de /dev/random utilizando la función hash SHA (Secure Hash Algorithm), diseñada por el NIST y la NSA para su Digital Signature Standard ([oST84]). Por tanto, tenemos una fuente de entropía aceptable, /dev/urandom, y otra incluso mejor, pero de capacidad limitada, /dev/random. Para detalles concretos sobre su funcionamiento se puede consultar el fichero que las implementa dentro del núcleo de Linux, drivers/char/random.c.

Como en el propio código se explica, cuando un sistema operativo arranca ejecuta una serie de acciones que pueden ser predecidas con mucha facilidad por un potencial atacante (especialmente si en el arranque no interactua ninguna persona, como es el caso habitual en Unix). Para mantener el nivel de entropía en el sistema se puede almacenar el desorden que existía en la parada de la máquina para restaurarlo en el arranque; esto se consigue modificando los scripts de inicialización del sistema. En el fichero apropiado que se ejecute al arrancar (por ejemplo, /etc/rc.d/rc.M) debemos añadir las siguientes líneas:
echo "Initializing random number generator..."
random_seed=/var/run/random-seed
# Carry a random seed from start-up to start-up
# Load and then save 512 bytes, which is the size of the entropy pool
if [ -f $random_seed ]; then
      cat $random_seed >/dev/urandom
fi
dd if=/dev/urandom of=$random_seed count=1
chmod 600 $random_seed
Mientras que en un fichero que se ejecute al parar el sistema añadiremos lo siguiente:
# Carry a random seed from shut-down to start-up
# Save 512 bytes, which is the size of the entropy pool
echo "Saving random seed..."
random_seed=/var/run/random-seed
dd if=/dev/urandom of=$random_seed count=1
chmod 600 $random_seed
Con estas pequeñas modificaciones de los archivos de arranque y parada del sistema conseguimos mantener un nivel de entropía aceptable durante todo el tiempo que el sistema permanezca encendido. Si de todas formas no consideramos suficiente la entropía proporcionada por estos dispositivos de Linux, podemos conseguir otra excelente fuente de desorden en el mismo sistema operativo a partir de una simple tarjeta de sonido y unas modificaciones en el núcleo ([Men98]), o utilizar alguno de los generadores - algo más complejos - citados en [Sch94].

Algunas mejoras de la seguridad

En esta sección vamos a comentar algunos aspectos de modificaciones del núcleo que se distribuyen libremente en forma de parches, y que contribuyen a aumentar la seguridad de un sistema Linux; para obtener referencias actualizadas de estos códigos - y otros no comentados aquí - es recomendable consultar [Sei99]; para información de estructuras de datos, ficheros o límites del núcleo de Linux se puede consultar [BBD$^+$96] o [CDM97].

Límites del núcleo

En include/asm/resource.h tenemos la inicialización de algunas estructuras de datos del núcleo relacionadas con límites a la cantidad de recursos consumida por un determinado proceso; por ejemplo, el máximo número de procesos por usuario (RLIMIT/SMALL>_NPROC) se inicializa a
MAX/SMALL>_TASKS/SMALL>_PER/SMALL>_USER, valor que en include/linux/tasks.h podemos comprobar que se corresponde con la mitad de NR/SMALL>_TASKS (número máximo de procesos en el sistema); en arquitecturas i86 el valor del límite de procesos por usuario se fija a 256. De la misma forma, el número máximo de ficheros abiertos por un proceso (RLIMIT/SMALL>_NOFILE) se inicializa al valor NR/SMALL>_OPEN, que en el archivo include/asm/limits.h se define como 1024.

Estos límites se pueden consultar desde espacio de usuario con la llamada getrlimit(); esta función utiliza una estructura de datos rlimit, definida en include/linux/resource.h, que contiene dos datos enteros para representar lo que se conoce como límite soft o blando y límite hard o duro. El límite blando de un recurso puede ser modificado por cualquier proceso sin privilegios que llame a setrlimit(), ya sea para aumentar o para disminuir su valor; por el contrario, el límite hard define un valor máximo para la utilización de un recurso, y sólo puede ser sobrepasado por procesos que se ejecuten con privilegios de administrador.

En el fichero include/linux/nfs.h podemos definir el puerto máximo que los clientes NFS pueden utilizar (NFS/SMALL>_PORT); si le asignamos un valor inferior a 1024 (puertos privilegiados), sólo el administrador de otro sistema Unix podrá utilizar nuestros servicios NFS, de forma similar a la variable nfs_portmon de algunos Unices.

Para cambiar los límites de los parámetros vistos aquí la solución más rápida pasa por modificar los ficheros de cabecera del kernel, recompilarlo y arrancar la máquina con el nuevo núcleo; sin embargo, a continuación vamos a hablar brevemente de Fork Bomb Defuser, un módulo que permite al administrador modificar algunos de estos parámetros sin reiniciar el sistema. Más adelante hablaremos también de los límites a recursos ofrecidos por PAM (Pluggable Authentication Modules), un sistema de autenticación incorporado en la actualidad a la mayoría de Linux, así como en otros Unices.

Fork Bomb Defuser

El kernel de Linux no permite por defecto limitar el número máximo de usuarios y el número máximo de procesos por usuario que se pueden ejecutar en el sistema sin tener que modificar el código del núcleo; si no queremos modificarlo, casi no hay más remedio que utilizar un poco de programación (unos simples shellscripts suelen ser suficientes) y las herramientas de planificación de tareas para evitar que un usuario lance demasiados procesos o que conecte cuando el sistema ya ha sobrepasado un cierto umbral de usuarios conectados a él.

Mediante el módulo Fork Bomb Defuser se permite al administrador controlar todos estos parámetros del sistema operativo, incrementando de forma flexible la seguridad de la máquina. El código está disponible en http://rexgrep.tripod.com/rexfbdmain.htm.

Secure Linux

Por Secure Linux se conoce a una colección de parches para el núcleo de Linux programados por Solar Designer, uno de los hackers más reconocidos a nivel mundial en la actualidad (entendiendo hacker en el buen - y único - sentido de la palabra). Este software, disponible libremente desde http://www.false.com/security/linux/11.2, incrementa la seguridad que el núcleo proporciona por defecto, ofreciendo cuatro importantes diferencias con respecto a un kernel normal:
  • Área de pila no ejecutable
    En un sistema con el área de la pila no ejecutable los ataques de buffer overflow son más difíciles de realizar que en los sistemas habituales, ya que muchos de estos ataques se basan en sobreescribir la dirección de retorno de una función en la pila para que apunte a código malicioso, también depositado en la pila. Aunque Secure Linux no es una solución completa, sí que añade un nivel extra de seguridad en este sentido, haciendo que un atacante que pretenda utilizar un buffer overflow contra nuestro sistema tenga que utilizar código más complicado para hacerlo.
  • Enlaces restringidos en /tmp
    Con esta característica, Secure Linux intenta que los usuarios sin privilegios puedan crear enlaces en /tmp/ sobre ficheros que no les pertenecen, eliminando así ciertos problemas de seguridad que afectan a algunos sistemas Linux, relacionados principalmente con condiciones de carrera en el acceso a ficheros.
  • Tuberías restringidas en /tmp
    Esta opción no permite a los usuarios escribir en tuberías (fifos) que no le pertenezcan a él o al root en directorios con el bit de permanencia activo, como /tmp. De esta forma se evitan ciertos ataques de Data Spoofing.
  • /proc restringido
    Esta es quizás la característica más útil de este parche, aparte de la más visible para el usuario normal. Permite que los usuarios no tengan un acceso completo al directorio /proc/ (que recordemos permite un acceso a estructuras de datos del núcleo, como la tabla de procesos, desde el espacio de usuario) a no ser que se encuentren en un determinado grupo con el nivel de privilegio suficiente. De esta forma se consigue un aumento espectacular en la privacidad del sistema, ya que por ejemplo los usuarios sólo podrán ver sus procesos al ejecutar un ps aux, y tampoco tendrán acceso al estado de las conexiones de red vía netstat; así, órdenes como ps o top sólo muestran información relativa a los procesos de quién las ejecuta, a no ser que esta persona sea el administrador o un usuario perteneciente al grupo 0.

Auditd

El demonio auditd permite al administrador de un sistema Linux recibir la información de auditoría de seguridad que el núcleo genera, a través del fichero /proc/audit, filtrarla y almacenarla en ficheros. Esta información tiene el siguiente formato:
AUDIT_CONNECT pid ruid shost sport dhost dport
Conexión desde la máquina al host remoto dhost.
AUDIT_ACCEPT pid ruid shost sport dhost dport
Conexión desde el host remoto dhost a la máquina.
AUDIT_LISTEN pid ruid shost sport
El puerto indicado está esperando peticiones de servicio.
AUDIT_OPEN pid ruid file
Se ha abierto el fichero file.
AUDIT_SETUID pid old_ruid ruid euid
Se ha llamado con éxito a setuid(), modificando el UID de ruid a euid.
AUDIT_EXEC pid ruid file
Se ha ejecutado el fichero file.
AUDIT_MODINIT pid ruid file
Se ha insertado en el kernel el módulo file.
Al leer la información de /proc/audit, el demonio auditd lee las reglas de filtrado del fichero /etc/security/audit.conf, comparando los flags, PID y RUID (Real User IDentifier) recibidos con cada una de las reglas del archivo de configuración hasta encontrar la apropiada para tratar el evento. Una vez que el demonio auditd ha encontrado el fichero donde almacenar la información recibida, la guarda en él con un formato legible.
next up previous contents
Siguiente: AIX Subir: Algunos sistemas Unix Anterior: Solaris   Índice General
2002-07-15