unix - linux - blyx.com : : Blog : : Toni de la Fuente
Transcription
unix - linux - blyx.com : : Blog : : Toni de la Fuente
PROBLEMAS DE SEGURIDAD EN EL MUNDO UNIX - LINUX 1.- Introducción 1.1.- Conceptos generales sobre seguridad 1.2.- Políticas de seguridad 1.3.- Seguridad por ocultación 2.- Clasificación de Sistemas operativos confiables: Libro Naranja 2.1.- Introducción 2.2.- Clases de seguridad 2.2.1.- D: seguridad mínima 2.2.2.- C1: protección mediante seguridad discrecional 2.2.3.- C2: protección mediante accesos controlados 2.2.4.- B1: protección mediante seguridad etiquetada 2.2.5.- B2: protección estructurada 2.2.6.- B3: dominios de seguridad 2.2.7.- A1: diseño verificado 2.2.8.- Tabla resumen 3.- Mecanismos de seguridad de UNIX 3.1.- Introducción 3.2.- Usuarios y grupos en UNIX 3.2.1.- Cuentas de usuario 3.2.2.- El archivo /etc/passwd 3.2.3.- Identificador de usuario (UID) 3.2.4.- Grupos 3.2.5.- El archivo /etc/group 3.2.6.- Identificador de grupo (GID) 3.2.7.- Contraseñas 3.2.8.- Usuarios especiales: el superusuario 3.2.9.- El comando su 3.3.- El sistema de ficheros de UNIX 3.3.1.- Introducción 3.3.2.-Tipos básicos de archivos 3.3.3.- Inodos o nodos indice 3.3.4.- Fechas de los archivos 3.3.5.- Permisos de un archivo 3.3.6.- SUID, SGID y los bits adhesivos 3.3.6.1.- Cambio de UID: setuid() 3.3.7.- Listas de control de acceso (ACLs) 3.3.8.- Archivos de dispositivo 3.3.9.- Sistemas de ficheros montados 4.- Mantenimiento de sistemas confiables 4.1.- Introducción 4.2.- Criptografía 4.3.- Defensa de cuentas 4.3.1.- Cuentas sin contraseña 4.3.2.- Cuentas predeterminadas 4.3.3.- Cuentas que ejecutan sólo una instrucción 4.3.4.- Cuentas abiertas 4.3.4.1.- interpretes de comandos restringidos 4.3.4.2.- Sistemas de archivos restringidos 4.4.- Auditoría 4.4.1.- Introducción 4.4.2.- El sistema de log en UNIX 4.4.3.- El demonio syslogd 4.4.4 Algunos archivos de log 4.5.- Amenazas programadas 4.5.1.-Introducción 4.5.2.-Base Fiable de Cómputo 4.5.3.-Tipos de amenazas programadas 4.5.4.-Programación segura 4.5.4.1.-Buffer overflow 4.5.4.2.- Recomendaciones para una programación segura 4.5.4.3.- Utilización segura de funciones de biblioteca 5.- Bibliografía 1.- Introducción. 1.1.- Conceptos generales sobre seguridad. Seguridad informática "Una computadora es segura si se puede confiar en que, junto con sus programas, funcione como se espera." Una computadora se considera segura (o confiable, introduciremos este concepto más adelante) si los datos contenidos en ella seguirán estando allí y nadie que no deba leerlos los lea. Según esto, los desastres naturales y los errores de programación son amenazas a la seguridad al igual que los usuarios no autorizados. Por lo tanto no existe diferencia si los datos se pierden por actos de un estudiante vengativo, por un virus, un error inesperado o un rayo: los datos se han perdido de igual forma. Seguridad y UNIX Dennis Ritchie dijo sobre UNIX: "No se diseñó para ser seguro. Se diseñó para que se pudiera usar la seguridad." UNIX es un sistema operativo multiusuario y multitarea. Una de las funciones naturales de estos sistemas es prevenir que distintos usuarios o programas que están usando la misma computadora se estorben. Si no existiera esta protección un programa podría afectar al funcionamiento de los programas de otros usuarios, borrar accidentalmente datos e incluso parar todo el sistema. Para evitar esto UNIX siempre ha contado con algún tipo de mecanismos de seguridad. Pero esta seguridad no se limita a la protección de la memoria. UNIX posee un sistema de archivos que controla la forma en que los usuarios acceden a los archivos y a los recursos del sistema. La mayor parte de los fallos de seguridad que se han encontrado se deben a problemas de configuración y programas con errores, y no a defectos en el diseño del sistema. Confiabilidad Al referirse al nivel de seguridad de un sistema operativo, no es habitual utilizar los términos "seguro" o "inseguro" , sino que se usa el término "confiable" para describir el nivel de confianza que se tiene en que un sistema se comporte como se espera. Esto implica que no se puede lograr la seguridad absoluta, sino que se trata de acercarse a ella intentando conseguir un grado de confianza suficiente para garantizar el funcionamiento correcto del sistema, dependiendo del contexto en el que se encuentre. 1.2.- Políticas de seguridad. A pesar de no ser el objetivo fundamental de este trabajo, es importante considerar el establecimiento de políticas de seguridad a la hora de estudiar la seguridad de un sistema informático. Debido a esto, resumiremos los conceptos más relevantes de las políticas de seguridad. La planificación de las políticas de seguridad se dividen en seis etapas diferentes: a.-Planificación de las necesidades de seguridad: Existen diferentes clases de seguridad, por lo que, dependiendo del tipo de sistema, habrá que dar mayor o menor importancia a las que tengan más relevancia: Confidencialidad: Impedir el acceso a la información a usuarios no autorizados. Integridad de los datos: Evitar el borrado o alteración indeseados de la información, incluidos los programas. Disponibilidad: Asegurar que los servicios esté siempre disponibles para un usuario autorizado. Consistencia: Asegurar que el sistema se comporta como esperan los usuarios autorizados; imagine lo que ocurriría si el comando ls borrara archivos de vez en cuando en lugar de listarlos. Control: Reglamentar el acceso al sistema, de forma que programas e individuos no autorizados y desconocidos no alteren el normal funcionamiento del sistema. Auditoría: Determinar qué se hizo, quién lo hizo y qué fue afectado. Para esto es necesario llevar un registro inexpugnable de todas las actividades realizadas en el sistema y que identifica de forma no ambigua a los usuarios que las llevaron a cabo. b.-Análisis de riesgos: Trata de responder a tres preguntas: ¿qué se debe proteger?, ¿contra qué debe protegieres?, ¿cuánto se está dispuesto a invertir para obtener una protección adecuada?. Para responder a estas preguntas, el análisis de riesgos se divide en tres etapas: Identificación de los activos. Identificación de las amenazas. Cálculo de los riesgos. c.- Análisis de costo-beneficio. Consiste en asignar un costo a cada riesgo, y determinar el costo de defenderse. De esta manera se puede decidir qué medidas hay que adoptar para proteger qué activos. Este aspecto no lo desarrollaremos por que no entra en el ámbito de este estudio. d.-Políticas de seguridad. Las políticas sirven para definir qué se considera valioso y especifican qué medidas hay que tomar para proteger esos activos. Deben aclarar qué se está protegiendo, establecer la responsabilidad de la protección y poner las bases para resolver e interpretar conflictos posteriores. No deben hacer una lista de riesgos específicos, computadoras o individuos por nombre. Deben ser generales y no variar mucho a lo largo del tiempo. e.-Implementación. f.- Auditoría y respuesta ante incidentes. Estos dos últimos apartados constituyen el resto de este trabajo. 1.3.- Seguridad por ocultación Esta costumbre proviene de las aplicaciones militares, donde la ocultación es una forma efectiva de protección. Pero trasladar este concepto a un ambiente de computación no resulta apropiado, pudiendo resultar incluso dañino para la seguridad. La ocultación presenta bastantes inconvenientes. Por ejemplo, al negar el acceso a los manuales a los usuarios, un administrador puede pensar que ha mejorado la seguridad al impedir a estos el conocimiento de comandos y opciones que pueden usarse para penetrar el sistema. Sin embargo, resulta sencillo conseguir esa documentación por diversos medios (universidades, Internet, librerías, ...), por lo que los administradores no pueden impedir que los usuarios accedan a la documentación. Además, esto redunda en una pérdida de eficacia de los usuarios, al no poder consultar los manuales. Esto también conlleva una actitud negativa porque da a entender que no se confía en los usuarios. Los errores de programación o características excepcionales son también objeto habitual de la ocultación, pero esto también es una mala política. Los desarrolladores colocan frecuentemente puertas traseras en sus programas que permiten obtener privilegios sin autentificarse debidamente. Otras veces esto permite que errores de programación que afectan gravemente a la seguridad del sistema persistan ya que se supone que nadie los conoce. El problema es que estos agujeros de seguridad tienden a ser descubiertos por accidente o por intrusos persistentes. Otra práctica habitual es mantener en secreto algoritmos desarrollados localmente, tales como algoritmos de cifrado. Esto también presenta algunos inconvenientes. Sin un estudio en profundidad estos podrían presentar graves fallos de seguridad, y este estudio no es posible si se mantienen en secreto. Desde el punto de vista de los sistemas operativos, mantener el código fuente en secreto no garantiza la seguridad. Si un intruso desea penetrar en el sistema, antes o después descubrirá algún fallo de seguridad (estos siempre existen), pero el resto de usuarios bienintencionados no podrá revisar el código en busca de estos fallos. Es mejor usar algoritmos y mecanismos robustos aunque sean conocidos por los enemigos, ya que esto además puede desalentar a un posible atacante consciente de la fiabilidad del mecanismo. "Poner dinero en una caja fuerte es mejor que esconderlo en un frasco de mayonesa en la cocina porque nadie sabe que está allí." 2.- Clasificación de Sistemas operativos confiables: Libro Naranja. 2.1.- Introducción El Libro Naranja (Trusted Computer System Evaluation Criteria), elaborado por el Ministerio de Defensa de los E.E.U.U en 1983, establece criterios para medir la fiabilidad de los sistemas informáticos en lo respectivo a la seguridad. Antes de describir los diferentes niveles de seguridad, es necesario conocer algunos conceptos relevantes : Base fiable de cómputo(TCB): Es el conjunto de mecanismos relevantes a efectos de la seguridad del sistema. En las clases con una fiabilidad elevada, la TCB se construye en torno a un monitor de referencias que impone las relaciones de acceso autorizadas entre los sujetos y objetos de un sistema. Control de accesos discrecional: Permite restringir el acceso a los objetos basándose en la identidad de los usuarios y/o grupos de usuarios a los que pertenecen. Los usuarios protegen sus objetos indicando quién puede acceder y el tipo de acceso permitido. Reutilización de objetos: Implica proteger ficheros, memoria y otros objetos de accesos por parte de un usuario tras su uso por otro. Por ejemplo: Un usuario crea un fichero en el que almacena información confidencial y después lo borra. A continuación otro usuario malicioso reserva espacio en el disco y el sistema le asigna esos mismos bloques. Si el sistema no borra físicamente la información del usuario anterior, el otro usuario podría leer la información borrada por el dueño original . Etiquetas: Las etiquetas de confidencialidad se asocian a cada sujeto (usuario, proceso) y a cada objeto (fichero, directorio, ...) e indican su nivel de autoridad asociado y se denomina habilitación. La etiqueta de confidencialidad de un fichero especifica el nivel de autoridad que un usuario debe tener para acceder al mismo. Identificación y autentificación: Es necesario que los usuarios se identifiquen antes de realizar cualquier actividad que implique una interacción con la TCB (ejecutar un programa, leer un fichero). En los sistemas UNIX la identificación se realiza mediante un nombre de conexión (login) y la autentificación mediante una contraseña (password). Vía fiable: En algunos sistemas se requiere que los usuarios puedan conectarse desde un terminal al sistema a través de lo que llamaremos una vía fiable. Para ello existe una secuencia de teclas, que al pulsarse elimina todos los procesos actuales y establece una conexión segura con la TCB permitiendo su autentificación. Esto evita ataques sistemáticos contra el sistema mediante programas marcadores y la introducción de caballos de Troya en los programas de conexión al sistema Auditoria: Es el registro y examen de la actividades relacionadas con la seguridad en un sistema fiable. Las actividades relacionadas con la seguridad son los accesos (y sus intentos) de un sujeto sobre un objeto y suelen denominarse sucesos. Cada vez que se produce un suceso, debe almacenarse en el registro de auditoria la siguiente información: fecha, hora, identificador del usuario, tipo, si ha tenido éxito, terminal, nombre de objeto, descripción de las modificaciones en la TCB y clases de seguridad del sujeto y del objeto. Arquitectura del sistema: Están relacionados con el diseño de un sistema para que sea posible la seguridad." Integridad del sistema: Se refiere al conjunto de pruebas de integridad que se ejecutan siempre que se arranca el ordenador, o periódicamente siguiendo un mantenimiento preventivo (en UNIX, se lleva a cabo un chequeo del sistema de ficheros cada vez que se monta un determinado numero de veces). Canales ocultos: Son rutas de información que habitualmente no se utilizan como medio de comunicación en el sistema y, por lo tanto, no están protegidos por sus mecanismos de seguridad. Utilidad para la administración de la fiabilidad: Implica asignar todas las actividades de seguridad a una persona diferente al administrador de sistema. Esto se basa en el principio de que es mejor asignar las actividades de seguridad a varias personas, para que el control total no recaiga sobre una sola persona, lo que podría comprometerlo. Gestión de configuración: protege a un sistema fiable mientras se diseña, desarrolla y mantiene. Implica controlar todos lo cambios realizados en la TCB. Así se mantiene un control del sistema durante su ciclo de vida, asegurando que el sistema que se utiliza no es una versión antigua del mismo. Distribución segura: Garantiza la protección del sistema mientras se envía a un cliente, asegurando que el sistema que recibe es idéntico al suministrado por el vendedor. Guía de usuario sobre las características de seguridad: Indica a los usuarios no privilegiados del sistema todo lo que deben saber sobre la seguridad. Manual para la administración de la seguridad: Proporciona a los administradores toda la información necesaria para establecer e implantar la seguridad del sistema Documentación de pruebas: Debe contener un plan de pruebas con el objeto de encontrar cualquier posible error en el diseño o implementación de la TCB, que permite a un usuario acceder a información no autorizada. Documentación de diseño: Permite conocer la construcción interna de la TCB, ayudando a los equipos de diseño y desarrollo a definir el modelo de seguridad del sistema y su construcción. 2.2.- Clases de seguridad El Libro Naranja divide su clasificación en cuatro niveles de seguridad. Los requisitos para un determinado nivel siempre lo son para el siguiente, pudiendo este restringir más aún los criterios, ya que se trata de una jerarquía de niveles: 2.2.1.- D: seguridad mínima En esta categoría están englobados todos los sistemas que han sido valorados y no han superado los requisitos mínimos para pertenecer a un nivel de seguridad superior. En esta categoría no existen requisitos de seguridad." En realidad ningún sistema pertenece a esta categoría, puesto que ningún vendedor evaluaría un sistema para obtener un nivel de seguridad "D". Ordenadores bajo MS-DOS o las versiones personales de Windows (familia 9x), además de otros sistemas antiguos son un ejemplo de sistemas que pertenecerían a esta categoría. 2.2.2.- C1: protección mediante seguridad discrecional Todos los usuarios manejan los datos al mismo nivel . En este nivel se procura evitar que los usuarios cometan errores y dañen al sistema. Las características mas importantes de este nivel son el control de autentificación mediante contraseñas y la protección discrecional de los objetos. El código del sistema debe estar protegido frente a ataques procedentes de programas de usuario (en UNIX, un proceso no puede salirse de su espacio virtual de direcciones ,y si lo intenta, morirá. Un sistema de este nivel no necesita distinguir entre usuarios individuales, Tan solo entre tipos de accesos permitidos o rechazados. En UNIX C1 hay que ser dueño de un objeto para ceder sus derechos de accesos y siempre se protege a los objetos de nueva creación. 2.2.3.- C2: protección mediante accesos controlados A partir de este nivel, el sistema debe ser capaz de distinguir entre los usuarios individuales. Generalmente el usuario debe ser dueño de un objeto para ceder los derechos de acceso sobre él. En la mayoría de los sistemas UNIX a partir de este nivel, existen listas de control de acceso (ACLs). Debe permitir que los recursos del sistema se protejan mediante accesos controlados. En UNIX el acceso a los periféricos (dispositivos de E/S) siguen un esquema de permisos idéntico al de los ficheros de los usuarios. Se aplican los requisitos de reutilización de objetos cuando esos mismos se reasignan. Se requiere a partir de este nivel que el sistema disponga de auditoria. Por ello cada usuario debe tener un identificador único que se utiliza para comprobar todas las acciones solicitadas. Se deben auditar todos los sucesos relacionados con la seguridad y proteger la información de la auditoria. El sistema debe ser capaz de auditar a nivel de usuario. La mayor parte de los UNIX comerciales pertenecen a este nivel, puesto que lo único que han tenido que añadir los fabricantes es un paquete de auditoria. 2.2.4.- B1: protección mediante seguridad etiquetada A partir de este nivel, los sistemas poseen un sistema de control de accesos obligatorio que implica colocar una etiqueta a los objetos (principalmente sobre los ficheros). Esto, junto con el nivel de habilitación de los usuarios es utilizado para reforzar la política de seguridad del sistema. En estos sistemas, el dueño no es el responsable de la protección del objeto, a menos que disponga de la habilitación necesaria. En cuanto a la auditoria, el sistema debe ser capaz de registrar cualquier cambio o anulación en los niveles de seguridad, y también hacerlo selectivamente por nivel de seguridad." Debe existir una documentación que incluya el modelo de seguridad soportado por el sistema. No es necesaria una demostración matemática, pero si una exposición de las reglas implantadas por las características de seguridad del sistema. 2.2.5.- B2: protección estructurada A partir de este nivel, los cambios en los requisitos no son visibles desde el punto de vista del usuario respecto de los niveles anteriores. En B2, todos los objetos del sistema están etiquetados, incluidos los dispositivos. Deben existir vías fiables que garanticen la comunicación segura entre un usuario y el sistema. Los sistemas deben ser modulares y utilizar componentes físicos para aislar las funciones relacionadas con la seguridad de las demás. Requieren una declaración formal del modelo de seguridad del sistema, y que haya una gestión de la configuración. También deben buscarse los canales ocultos. 2.2.6.- B3: dominios de seguridad Es necesario que exista un administrador de seguridad, que sea alertado automáticamente si se detecta una violación inminente de la seguridad. Deben existir procedimientos para garantizar que la seguridad se mantiene aunque el ordenador se caiga y luego rearranque. Es obligatoria la existencia de un monitor de referencia sencillo, a prueba de agresiones e imposible de eludir. La TCB debe excluir todo el código fuente que no sea necesario para proteger el sistema. 2.2.7.- A1: diseño verificado Esta es la clase de certificación más alta, aunque el Libro Naranja no descarta la posibilidad de exigir requisitos adicionales. Son sistemas funcionálmente equivalentes a B4. Tan solo se añade la distribución fiable que refuerza la seguridad. Los sistemas A1 tienen la confiabilidad adicional que ofrece el análisis formal y la demostración matemática de que el diseño del sistema cumple el modelo de seguridad y sus especificaciones de diseño. En la siguiente tabla se resumen las características principales de los diferentes niveles de seguridad definidos en el Libro Naranja: 2.2.8.- Tabla resumen Requisito C1 C2 B1 B2 B3 A1 Control de accesos discrecional nue mej sin sin mej sin Reutilización de objetos no nue sin sin sin sin Dispositivos mononivel/multinivel no no nue sin sin sin Control de accesos obligatorio no no nue mej sin sin Etiquetas no no no nue sin sin Identificación y autentificación nue mej mej sin sin sin Auditoria no nue mej mej mej sin Vías fiables no no no nue mej sin Arquitectura del sistema nue mej mej mej mej sin Integridad del sistema nue sin sin sin sin sin Pruebas de seguridad nue mej mej mej mej mej Especificación y verificación del diseño no no nue mej mej mej Canales ocultos no no no nue mej mej Facilidad para la administración de la fiabilidad no no no nue mej sin Gestión de configuración no no no nue sin mej Recuperación segura no no no no nue sin Distribución segura no no no no no nue Guía de usuario de seguridad nue sin sin sin sin sin Guía de administración de la seguridad nue mej mej mej mej sin Documentación de pruebas nue sin sin mej sin mej Documentación de diseño nue sin mej mej mej mej Leyenda: no: no existe criterio en esta clase. nue: criterio nuevo para esta clase mej: nuevos requisitos para el criterio en esta clase. sin: no existen requisitos adicionales para el criterio en esta clase. 3.- Mecanismos de seguridad de UNIX 3.1.- Introducción En este capítulo se destacan los principales mecanismos de seguridad de UNIX, es decir, aquellos recursos que el sistema operativo proporciona y que conforman la base sobre la que se establece la seguridad. El conocimiento de estos mecanismos por parte del administrador de seguridad es completamente imprescindible, puesto que, bien utilizados permiten que el sistema se muestre robusto ante cualquier ataque o fallo. Por otro lado, mal utilizados proporcionarán una puerta abierta a cualquier atacante o convertirán un programa inocente en un auténtico agujero de seguridad. 3.2.- Usuarios y grupos en UNIX Las cuentas de usuario son el primer objetivo de cualquier atacante, su finalidad suele ser casi siempre esta, penetrar en el sistema consiguiendo apoderarse de una cuenta de usuario (preferentemente la del root). Después pueden robar, destruir o falsificar datos o simplemente dejar una nota, pero el daño ya está hecho: la seguridad del sistema se ha visto comprometida. 3.2.1.- Cuentas de usuario Como se señala en el capítulo anterior, la identificación en los sistemas UNIX se realiza mediante un nombres de usuarios (login) y la autentificación mediante contraseñas (password). Los nombres de usuario se conocen también como nombres de cuenta. Para iniciar una sesión en un sistema UNIX, es necesario conocer tanto el nombre de usuario como la contraseña correspondiente. Por ejemplo, Manuel Rodríguez posee una cuenta en el servidor de practicas de su universidad. Su nombre de usuario es manurod y su contraseña es "as35FgS". Cuando Manuel desee iniciar una sesión de trabajo en el laboratorio, deberá autentificarse como usuario autorizado ante la máquina de la siguiente manera: Bienvenido al servidor X de la Universidad Y login: manurod password: as35FgS De esta manera, el usuario queda totalmente autentificado y obtendrá un interprete de comandos (shell) desde el que podrá realizar diferentes tareas. Otra posibilidad es que el servidor disponga de un entorno de trabajo en modo gráfico (X-Window), en ese caso el proceso de autentificación se realiza de idéntica manera, con la única diferencia de que el usuario obtendrá un entorno gráfico de trabajo en lugar del shell habitual. Los nombres de usuario estándar en UNIX tienen una longitud que puede ir de 1 a 8 caracteres. Estos nombres deben ser únicos en una misma computadora, puesto que deben identificar al usuario de forma inequívoca (después se vera que esto no es estrictamente cierto, puesto que dos usuarios pueden compartir el mismo UID). Las contraseñas en UNIX tradicionalmente tenían una longitud de entre 1 y 8 caracteres, aunque algunas versiones comerciales permiten contraseñas mas largas. El uso de contraseñas mas largas implica una mayor seguridad, por que son mas difíciles de adivinar. La contraseña no debe ser obligatoriamente única para cada usuario, varios usuarios pueden tener, de hecho, la misma contraseña, aunque de ser así, esto indicaría que estos usuarios han elegido una mala contraseña. La elección de las contraseñas, así como las posibles restricciones que se pueden establecer sobre ellas, es de vital importancia a la hora de evitar intrusiones en el sistema, por ello, se dedicará a esto otro apartado. 3.2.2.- El archivo /etc/passwd En los sistemas tipo UNIX , la información sobre las cuentas de usuario se almacenan en una base de datos localizada en el archivo /etc/passwd. Esta es un fichero de texto en el que los diferentes registros se encuentran separados por el carácter dos puntos (:). Se puede emplear el comando cat para visualizar el contenido del fichero passwd. A continuación se puede ver una muestra de un archivo típico como ejemplo : root:o8o7aSVh13nLD:0:0:root:/root:/bin/bash bin:*:1:1:bin:/bin: daemon:*:2:2:daemon:/sbin: adm:*:3:4:adm:/var/adm: lp:*:4:7:lp:/var/spool/lpd: mail:*:8:12:mail:/var/spool/mail: news:*:9:13:news:/var/spool/news: uucp:*:10:14:uucp:/var/spool/uucp: operator:*:11:0:operator:/root: ftp:*:14:50:FTP User:/home/ftp: nobody:*:99:99:Nobody:/: manurod:EH5/.mj7J5dFh:501:100:Manuel Rodríguez:/home/alumnos/manurod:/bin/bash maripet:aCq87MCñ03c9e:502:100:Mario Petru:/home/alumnos/maripet:/bin/bash javitup:md0mHM86yn3aW:503:100:Javier Tup:/home/alumnos/javitup:/bin/bash Algunas de las cuentas del ejemplo son cuentas del sistema como root,daemon o apm. El resto son cuentas de usuario regulares del sistema como manurod, javitup, maripet. Las cuentas que tienen un * en al campo de la contraseña no pueden ser utilizadas para iniciar una sesión desde un terminal, es necesario utilizar la orden su. Son cuentas de sistema (en ocasiones usuarios 'castigados'), que poseen archivos, a veces muy importantes, que realizan tares administrativas o dan servicios. Cada campo individual del archivo passwd posee un significado directo. En la siguiente tabla se explica el significado de una de las líneas del ejemplo: Campo Contenido manurod Nombre de usuario EH5/.mj7J5dFh Contraseña cifrada del usuario 501 Numero de identificación del usuario (UID) 500 Numero de identificación de grupo del usuario (GID) Manuel Rodríguez Nombre completo del usuario /home/alumnos/manurod/ Directorio base del usuario /bin/bash Interprete de comandos del usuario La contraseña se guarda cifrada. La contraseña en sí no se guarda tal cual en el sistema, si así se hiciera, esto representaría un grave riesgo para la seguridad, y solo es aceptable cuando la política de seguridad lo admita por razones particulares. En la actualidad, muchas organizaciones poseen grandes redes de tipo cliente-servidor que contienen muchos servidores y una gran cantidad de estaciones de trabajo. Normalmente es deseable que los usuarios entren en cualquiera de estas computadoras, y que lo hagan con el mismo nombre de usuario y la misma contraseña. Esto conlleva que cada usuario tenga una cuenta en cada estación de trabajo. Este requisito hace que sea extremadamente difícil mantener la coherencia entre las bases de datos de usuarios de todas las computadores. Para lograr esto se utilizan diversos paquetes software que proporcionan el contenido del fichero /etc/passwd a toda la red. Algunos de estos sistemas son: Network Information System (NIS:Sistema de Información para la Red) de Sun Microsystems. NIS+ de Sun Microsystems Distributed Computing Environment (DCE: Ambiente de Computación Distribuido) de Open Software Foundation. NetInfo de NeXT Computers Todos estos sistemas toman la información, que por lo general, está almacenada en cada estación de trabajo y la ponen en una o mas computadoras que se usan como servidores de red. Al usar estos sistemas, ya no se puede usar simplemente el comando cat, sino que hay que utilizar una instrucción especifica para cada sistema para ver el contenido del archivo /etc/passwd. El servicio NIS de Sun complementa la información almacenada en los archivos de las estaciones de trabajo. Por lo tanto, para ver la lista completa de las cuentas de usuario, es necesario listar el contenido del archivo passwd local y ademas utilizar la siguiente instrucción: % ypcat passwd El servicio NIS+, también de Sun, se puede configurar para complementar sustituir sus entradas sobre cuentas de usuario en lugar de las que están en el archivo passwd local, dependiendo del contenido del archivo /etc/nsswitch.conf. Para ver la lista de usuarios bajo NIS+ hay que utilizar el comando niscat y especificar el dominio de NIS+. Por ejemplo : % niscat -o passwd.dominio En las computadoras que ejecutan NetInfo, el archivo local no se toma en cuenta y en su lugar se usa la versión de red. Por ejemplo, para ver las cuentas de usuario, si se usa NetInfo, hay que escribir: % nidump passwd / Las computadoras que usan DCE emplean una base de datos de red cifrada como alternativa a las contraseñas cifradas y al archivo /etc/passwd. Muchos administradores no utilizan sistemas de administración de bases de datos en red porque temen que la seguridad se vea comprometida. Estos temores provienen del hecho de que la configuración de estos sistemas es, en ocasiones, muy complicada y los protocolos que utilizan pueden no ser particularmente resistentes a ataques. Es una practica habitual entre los administradores mantener simplemente un archivo central con la información de los usuarios y copiarlo de forma periódica en las computadoras remotas. El inconveniente que se presenta es que con frecuencia el administrador tiene que cambiar manualmente contraseñas de usuarios. En general, es preferible aprender a dominar la configuración de estos sistemas y luego colocar otras medidas defensivas como es el caso del cortafuegos (firewall). 3.2.3.- Identificador de usuario (UID) El UID es un número entero real de 16 bits (de 0 a 65535). Los primeros UID se usan principalmente para funciones del sistema. Para las personas, normalmente empiezan en el 20, el 100 o el 500. Es habitual asignar el UID dependiendo del grupo primario del usuario: los usuarios del grupo 500 tendrán los UID 501, 502, 503, y así sucesivamente. Algunas versiones de UNIX permiten ahora UID de 32 bits. En las versiones antiguas de UNIX los UID son enteros de 16 bits con signo (de -32768 a 32767). UNIX utiliza el archivo /etc/passwd para almacenar la correspondencia entre el nombre de cada usuario y su UID. El UID de cada usuario se guarda en el tercer campo, después de la contraseña cifrada. Esta es una línea del ejemplo anterior: manurod:EH5/.mj7J5dFh:501:100:Manuel Rodríguez:/home/alumnos/manurod:/bin/bash Aquí se puede ver que el UID de manurod es 501. El UID es la información real que utiliza el sistema operativo para identificar al usuario. Los nombre de usuario son solo una comodidad para nosotros. Si dos usuarios tienen el mismo UID, UNIX los trata como si fueran el mismo usuario, aunque tengan nombres y contraseñas distintos. Dos usuarios con el mismo UID pueden leerse y borrarse archivos libremente el uno al otro, así como suspenderse los programas que ejecuten. Asignar a dos usuarios el mismo UID es, por lo general, una mala idea, salvo algunas excepciones. 3.2.4.- Grupos Todos los usuarios de UNIX pertenecen a uno o más grupos. El administrador del sistema puede utilizar los grupos para definir conjuntos de usuarios que tendrán permiso de leer, escribir y/o ejecutar ciertos archivos, directorios o dispositivos. Cada usuario pertenece a un grupo primario, que se anota en el archivo /etc/passwd. El GID del grupo primario de un usuario aparece después del UID del usuario. Los grupos permiten manejar cómodamente a varios usuarios de alguna manera. Por ejemplo, tal vez se quiera abrir un grupo para un equipo de estudiantes que trabajan en un proyecto y permitirles a ellos y sólo a ellos leer y modificar los archivos del equipo. Los grupos también se usan para restringir el acceso a la información confidencial o aplicaciones con licencias especificas. Por ejemplo, en muchas computadoras que usan UNIX, solamente se permite examinar el contenido de la memoria del kernel del sistema a ls usuarios que pertenecen al grupo kmem. El grupo ingres se usa normalmente para quienes están registrados como usuarios del programa comercial del manejo de bases de datos Ingres. Algunas versiones especiales de UNIX permiten usar CAO o Controles de Acceso Obligatorio (MAC: Mandatory Access Controls), los cuales controlan el acceso mediante etiquetas en los datos, ademas o en lugar de los controles tradicionales CAV o Controles de Acceso Voluntario (DAC: Discretionary Access Controls) de UNIX . Los sistemas que se basan en CAO/CAV (MAC/DAC) no emplean los grupos tradicionales de UNIX. En su lugar, los valores de los GID y el archivo /etc/group se pueden usar para especificar etiquetas de seguridad para el control de acceso o bien para apuntar a listas de capacidades. 3.2.5.- El archivo /etc/group El archivo /etc/group contiene la base de datos con todos los grupos que hay en la computadora y sus GID correspondientes. Su formato es similar del del archivo /etc/passwd. He aquí una muestra de archivo /etc/group que pertenece a un sistema típico: root:*:0:root bin:*:1:root,bin,daemon daemon:*:2:root,bin,daemon sys:*:3:root,bin,adm adm:*:4:root,adm,daemon lp:*:7:daemon,lp kmem:*:9: wheel:*:10:root,javitup mail:*:12:mail news:*:13:news uucp:*:14:uucp ftp:*:50: users:*:100: floppy:*:19: cdwriter:*:500:manurod pppusers:*:44:maripet La primera línea define el grupo root. Los campos se detallan en la siguiente tabla: Campo Contenido root Nombre de grupo * Contraseña de grupo (cifrada) 0 Numero de identificación del grupo (GID) root Lista de usuarios miembros del grupo Los usuario que aparecen en el archivo /etc/group pertenecen a los grupos que se indican, además de pertenecer a sus grupos primario los cuales se indican en el archivo /etc/passwd. Por ejemplo, manurod, maripet y javitup pertenecen al grupo users a pesar de no aparecer explícitamente en el archivo /etc/group porque su grupo primario es el 100. En algunas versiones de UNIX, se puede ejecutar el comando groups o id para ver la lista de grupos a los que se pertenece. 3.2.6.- Identificador de grupo (GID) Todos los usuarios de UNIX pertenecen a uno o más grupos. Al igual que las cuentas de usuario, los grupos tienen un nombre de grupo y un número de identificación de grupo (GID). Usualmente, los valores de los GID también son enteros de 16 bits. Análogamente al UID, el GID representa al grupo en el sistema, y no su nombre. Los grupos permiten agrupar a varios usuario que posean el mismo grupo primario (campo GID del archivo /etc/passwd) o que aparezcan en el archivo /etc/group. Las versiones de UNIX de AT&T anteriores a SVR4 sólo permitían que un usuario estuviera en un grupo a la vez. Para cambiar de grupo había que usar el comando newgrp. Cuando un usuario intentaba acceder a un archivo sobre el que tenía permiso por pertenecer al mismo grupo, se le denegaba el acceso si, en ese instante, el usuario no estaba en ese mismo grupo. Por eso debía usar el comando newgrp. En la actualidad, los usuarios pertenecen a todos los grupos en los que aparecen en el archivo /etc/group a la vez. El sistema operativo chequea todos los grupos a los que pertenece el usuario para comprobar sus derechos de acceso. Aun así, el comando newgrp sigue teniendo cierta importancia. Si un usuario quiere que sus archivos tengan un grupo (GID) en especial de entre los que posee, debe utilizar el comando newgrp en cada archivo para cambiarlos de grupo. Esto puede ser un poco pesado, si está generando muchos archivos, así que puede cambiar de grupo con newgrp, de forma que todos los archivos que genere tengan el nuevo GID. 3.2.7.- Contraseñas Para autentificar a un usuario, este debe demostrar su identidad. Existen tres maneras por la que un usuario puede autentificarse ante el sistema. Puede usarse una o varias de estas a la vez: Se puede indicar algo que se sabe (por ejemplo, una contraseña) Se puede mostrar algo que se tiene (por ejemplo, una tarjeta) La computadora puede considerar alguna característica personal (por ejemplo, una huella dactilar). Ninguno de estos sistemas es infalible. Alguien puede robar la contraseña 'husmeando' la linea de un terminal, puede robar la tarjeta en un atraco, y si tiene un cuchillo, quizás pueda obtener una huella dactilar. En general, cuanto más confiable sea la forma de identificación, más complicada será de usar, y más agresivo deberá ser el agresor para violarla. Las contraseñas son el sistema de autentificación más simple: son un secreto que se comparte con la computadora. Al iniciar la sesión, se escribe la contraseña para demostrar a la computadora de quién se trata. La computadora se asegura de que la contraseña corresponde al nombre de usuario que se ha especificado. Si corresponde, se puede continuar. En el sistema UNIX no se despliega la contraseña, es decir, no se escribe en el terminal a medida que se teclea. Esto proporciona protección adicional si alguien está mirando por encima del hombro del que escribe. Esto puede parecer trivial, pero constituye la primera medida de seguridad. Las contraseñas son la primera linea de defensa de UNIX contra los extraños que quieren penetrar en el sistema. Aunque se puede penetrar en el sistema o robar información a través de la red sin abrir una sesión, muchas intrusiones se deben a contraseñas mal elegidas y mal protegidas. En los sistemas personales de escritorio no se usan contraseñas. La seguridad se basa en métodos físicos como paredes, puertas y cerraduras. En algunos ambientes de confianza tampoco se usan contraseñas, la confianza y el respeto pueden ser suficientes como medida de seguridad. Pero cuando una computadora está conectada a un modem y se puede acceder desde casi cualquier parte del mundo, o cuando está conectada a una red, sobre todo Internet, entonces las contraseñas son absolutamente necesarias. Si una cuenta de una computadora que pertenece a una red se ve comprometida, puede poner en peligro a toda la red. Las contraseñas convencionales han sido parte de UNIX desde sus primeros años. La ventaja de las contraseñas es que funcionan sin un equipo especial (como lectores de tarjeta y de huellas digitales). En la actualidad las contraseñas convencionales en sistemas de red (la mayoría) no son suficiente. Es necesario usar contraseñas descartables o criptografía, o ambas. En algunas versiones de UNIX, si alguien intenta iniciar una sesión varias veces seguidas de forma inválida se bloquea la cuenta. Sólo el administrados puede desbloquearla. El bloqueo protege al sistema de quienes intentan adivinar una contraseña y avisa de que alguien ha intentado penetrar en la cuenta. Esta táctica puede ser utilizada en ataques de negación de servicio, para bloquear a ciertos usuarios del sistema, o simplemente para fastidiar. En lugar del bloqueo, algunos sistemas (como Linux) introducen un retardo mayor cada vez que se falla una conexión desde un terminal, lo que limita los ataques de negación del servicio, cumpliendo el mismo efecto que el bloqueo. El cambio de contraseña es otro momento crítico. El comando passwd, que sirve para cambiar la contraseña, solicita primero la contraseña anterior antes de la nueva. Así se evita que alguien se siente en un terminal abierto y cambie la contraseña. Dejar un terminal abierto sin protección, es un fallo de seguridad bastante grave, pero, por lo general, bastante común. El comando passwd, también requiere que se repita la contraseña. Esto evita que, por un error tipográfico, se cambie la contraseña por una desconocida. Si se recibe un correo del administrador pidiendo que contraseña a una determinada, se debe ignorar administrador. Este tipo de mensajes se envía con usuarios novatos. Si se cumple la orden, puede tener devastadoras. se cambie la y avisar al frecuencia a consecuencias Si se comete un error, o se olvida la contraseña y se pierde es acceso a la cuenta, hay que recurrir al administrador. Este no puede descifrar la contraseña de ningún usuario. Pero puede eliminar la contraseña o cambiarla, (esta parece la mejor opción) sin dar la contraseña vigente. Uno de los fallos más habituales es asignar como contraseña el nombre de usuario. Esta es en algunos sistemas una práctica habitual cuando se crea un usuario. Se debe obligar al usuario a cambiar la contraseña en la primera sesión. Si no lo hace, es probable que cualquiera que intente entrar en la computadora no tarde más de diez minutos en conseguirlo. En general, se deben evitar las siguientes contraseñas: El nombre propio, el de la esposa o del socio En nombre de la mascota o del hijo Los nombre de amigos o compañeros de trabajo Los nombres de los personajes favoritos El nombre del jefe El nombre de cualquier persona El nombre del sistema operativo que se está utilizando El nombre de la computadora que se usa El número de teléfono o el de la matricula del coche Cualquier parte del dni o número de la Seguridad Social Cualquier cumpleaños Cualquier otra información que sea fácil de averiguar (dirección, universidad, etc.) Cualquier forma del nombre mayúsculas o con letras dobles) de usuario (por ejemplo, en Cualquier palabra que aparezca en un diccionario en cualquier idioma Nombre propios de lugares o personas Contraseñas que sean una repetición de la misma letra Patrones simples de letras del teclado: por ejemplo, qwerty Cualquiera de éstos escrito al revés Cualquiera de éstos con un dígito al principio o al final En general, cualquier combinación de cualquiera de las anteriores. El motivo de estas recomendaciones, es que uno de los ataques más habituales consiste en conseguir el archivo /etc/passwd de la computadora, y utilizar después un generador de contraseñas. Estos programas (como el programa Crack) utilizan un diccionario y un archivo de normas. En el archivo de normas se encuentran reglas para combinar las palabras del diccionario. Así se genera una lista de posibles contraseñas que se encriptan (el algoritmo es público) y se comparan con las contenidas en el archivo /etc/passwd. 3.2.8.- Usuarios especiales: el superusuario Además de los usuarios normales, UNIX tiene varios usuarios especiales para propósitos administrativos y contables. Ya se han mencionado algunos. El más importante es el root, el superusuario. Todos los sistemas UNIX tienen un usuario especial en el archivo /etc/passwd cuyo UID es 0. Este es realmente el único usuario realmente especial del sistema, los demás son especiales porque son propietarios de determinados archivos o pertenecen a determinados grupos (que a su vez tienen archivos importantes). La cuenta root es la identidad que usa el sistema operativo para llevar a cabo sus funciones básicas, tales como el inicio y la terminación de sesiones de usuario, el registro de la información contable y la administración de dispositivos de entrada/salida. Por esta razón, el superusuario tiene el control de casi todo el sistema operativo: cualquier programa ejecutado por root puede eludir casi todas las restricciones de seguridad y se desactivan casi todas las verificaciones y advertencias. Como se indicó en la sección sobre el identificador de usuario (UID), dos cuentas que tengan el mismo UID son la misma para el sistema operativo. De esta manera, cualquier cuenta que tenga el UID 0 tiene los privilegios del superusuario. El nombre de usuario root es simplemente convencional. Hay que sospechar inmediatamente de cualquier cuenta que aparezca en el sistema con UID 0 que no haya sido creada por el administrador. Estas cuentas se añaden con frecuencia por personas que penetran en las computadoras como una manera sencilla de obtener privilegios de superusuario en el futuro. La cuenta root no se ha diseñado para que el administrador la use como cuenta personal. Debido a que se inhabilitan todas las pruebas de seguridad para el superusuario, un error tipográfico fácilmente puede destruir todo el sistema. Con frecuencia el administrador de un sistema UNIX tendrá que convertirse en superusuario para llevar a cabo funciones administrativas. Este cambio de estado se puede lograr mediante el comando su para crear un intérprete de comandos privilegiado. Cuando de tiene la capacidad del superusuario se deben tomar precauciones extremas. Cuando cese la necesidad de tener este tipo de acceso, el administrador debe cerrar el intérprete de comandos privilegiado. Cualquier proceso que tiene UID efectivo 0 se ejecuta como si fuera el superusuario, es decir, cualquier proceso con UID 0 se ejecuta sin verificaciones de seguridad y puede hacer prácticamente lo que sea. Las verificaciones y controles de seguridad se ignoran en el caso del superusuario aunque la mayor parte de los sistemas sí registran en las bitácoras y auditan algunas de las acciones del superusuario. El usuario es la principal debilidad de seguridad del sistema operativo UNIX. Dado el privilegio del superusuario, las personas que penetran en un sistema UNIX tratan de convertirse en el superusuario. Para evitar este problema con el superusuario, se ha intentado varias veces diseñar un sistema UNIX seguro (que cumpla todos los requisitos para un sistema altamente confiable) adoptando la estrategia de dividir los privilegios del superusuario en muchas categorías. Lamentablemente estos intentos a menudo fallan. Casi siempre, muchos de los privilegios en los que se divide el superusuario pueden usarse para obtener los demás. Se cambia un gran fallo de seguridad por otros más pequeños que llevan al mismo final. 3.2.9.- El comando su A veces un usuario debe tomar la identidad de otro. Pos ejemplo si se desea acceder a archivos propios estando sentado delante el terminal de un amigo. En lugar de cerrar la sesión del amigo e iniciar una propia, UNIX permite cambiar temporalmente el número de identificación de usuario. El comando que lo permite se llama su,que son las iniciales de “substitute user” (sustituir usuario). su requiere que se use la contraseña del usuario al que se está cambiando. Los procesos en sistemas UNIX tienen al menos dos identidades en cada momento. Normalmente estas dos identidades son la misma. La primera identidad es el UID real. El UID real es la identidad verdadera y coincide, normalmente con el nombre de usuario con el que se inició la sesión. A veces se quiere asumir la identidad de otro usuario para acceder a algunos archivos o ejecutar algunos comandos. Esto se puede lograr iniciando una sesión con el otro nombre y obteniendo de esa forma un intérprete de comandos cuyo proceso subyacente tenga un UID igual al del usuario. Como alternativa, si sólo se desea ejecutar algunos comandos con la identidad de otro usuario, se puede emplear el comando su para crear nuevos procesos. Esto ejecutará otra copia del intérprete de comandos que tendrá la identidad (UID real) del otro usuario. Para emplear el comando su es necesario conocer la contraseña del otro usuario o ser en ese momento el superusuario. Si se escribe su sin un nombre de usuario, se indica a UNIX que se quiere convertir en superusuario. Entonces se solicita la contraseña del superusuario. Si la contraseña de root se escribe correctamente, se ejecuta un intérprete de comandos con UID 0. Al convertirse en superusuario, el prompt cambiará por defecto al carácter '#', lo que recordará los nuevos poderes que se han adquirido. Cuando se usa en comando su para convertirse en superusuario, siempre debe usarse la trayectoria completa del comando /bin/su. Al hacerlo así, se asegura la ejecución del comando su auténtico y de que no se ha ejecutado algún otro comando su que se encuentre en la trayectoria de búsqueda. Esto es una manera importante de protegerse y proteger la contraseña del superusuario contra algún caballo de Troya. Es recomendable invocar al comando su con un argumento simple en forma de guión cuando se quiere convertir en superusuario: $ /bin/su De esta forma, su invoca al intérprete de comandos de forma que este lea todos loa archivos de configuración necesarios y simule un inicio de sesión. Esto es importante porque así se evita que la trayectoria de búsqueda (PATH) sea la del usuario que invoco su y no la del superusuario. Algunas versiones del UNIX de tipo Berkeley no permiten usar su a ninguna cuenta que no sea miembro del grupo wheel. Sin embargo la versión de su de GNU no utiliza esta característica. Esta es la explicación de Richard Stallman sacada de la propia página de manual de su. “A veces, algunos listillos intentan hacerse con el poder total sobre el resto de usuarios. Por ejemplo, en 1984, un grupo de usuarios del laboratorio de Inteligencia Artificial del MIT decidieron tomar el poder cambiando el password de operador del sistema Twenex y manteniéndolo secreto para el resto de usuarios. (De todas maneras, hubiera sido posible desbaratar la situación y devolver el control a los usuarios legítimos parcheando el kernel, pero no sabría como realizar esta operación en un sistema UNIX.) Sin embargo, casualmente alguien contó el secreto. Mediante el uso habitual de su una vez que alguien conoce el password de root puede contárselo al resto de usuarios. El grupo "wheel" hará que esto sea imposible, protegiendo así el poder de los superusuarios. Yo estoy del lado de las masas, no de los superusuarios. Si eres de los que están de acuerdo con los jefes y los administradores de sistemas en cualquier cosa que hagan, al principio encontrarás esta idea algo extraña.” Muchas versiones registran los intentos fallidos del comando su. Las versiones más viejas de UNIX enviaban explícitamente a la consola los avisos de los intentos fallidos de su y también los colocaban en el archivo /var/adm/messages. Las versiones más modernas registran los intentos fallidos de usar su a través del programa syslog, el cual permite enviar los mensajes que se quieran a un archivo específico o anotarlos en bitácoras que estén en computadoras remotas a través de la red. 3.3.- El sistemas de ficheros de UNIX 3.3.1.- Introducción El sistema de ficheros de UNIX controla el modo en el que la información se almacena en el disco y otras formas de almacenamiento secundarias. Dentro del sistema UNIX todo son archivos: desde la memoria física del equipo hasta el ratón, pasando por modems, teclado, impresoras o terminales. Por esto, una correcta utilización de los permisos, atributos y otros controles sobre ficheros es vital para la seguridad de un sistema. El sistema de ficheros proporciona las herramientas básicas para el administrador, pero también para el atacante, sea este voluntario, un fallo de un usuario o de un programa. Ya que en UNIX todos los objetos son elementos del sistema de ficheros (a partir de aquí archivos o ficheros, indistintamente), un error en un permiso puede facilitar la escritura de cualquier usuario en un disco o en un directorio importante, la consecución de un shell privilegiado, o la posibilidad de que un usuario 'vea' la memoria de todos los demás usuarios(y la del sistema). 3.3.2.-Tipos básicos de archivos. En un sistema UNIX existen tres tipos básicos de archivos: ficheros planos, directorios y ficheros especiales (dispositivos). También existen otros tipos de archivos como los enlaces simbólicos, los sockets o las tuberías, pero no se tratarán aquí. Ficheros planos: Son secuencias de bytes, que a priori no poseen ni estructura interna ni contenido significante para el sistema, su significado depende de las aplicaciones que interpretan su contenido. Directorios: Son archivos cuyo contenido son otros ficheros de cualquier tipo (planos, otros directorios y otros ficheros especiales). Ficheros especiales: Son archivos que representan dispositivos del sistema. Se dividen en dos grupos: los dispositivos orientados a carácter y los orientados a bloque. La principal diferencia entre ambos es la forma de realizar operaciones de entrada/salida:mientras que los dispositivos orientados a carácter la realizan byte a byte (esto es, carácter a carácter ), los orientados a bloque la realizan en bloques de caracteres . 3.3.3.- Inodos o nodos indice Para cada objeto en el sistema de ficheros, UNIX guarda información administrativa en una estructura llamada inodo. Los inodos residen en el disco y no tienen nombre, en vez de ello, tienen indices (números) que indican su posición en un arreglo de inodos. Un inodo es una estructura de datos que relaciona un grupo de bloques de un dispositivo con un determinado nombre del sistema de ficheros. Cada inodo generalmente contiene lo siguiente: La ubicación del contenido del ítem en el disco, si existe . El tipo del ítem (es decir, archivo, directorio, enlace simbólico, etc.) El tamaño total del ítem, en bytes, si esto se aplica. La fecha de la última modificación del archivo del inodo (el ctime). La fecha de la ultima modificación del contenido del archivo del inodo (el mtime). La fecha del ultimo acceso al archivo (el atime) para read(), exec(),etc. El numero de enlaces físicos que tiene ese archivo. Tamaño de bloque para el sistema de ficheros de E/S Numero de bloques asignados. Tipo de dispositivo El dueño del archivo (UID). El grupo del archivo (GID). Los bits de protección. Estos tres últimos, que se guardan para cada archivo, junto con la información UID/GID del proceso que se ejecuta, son los datos fundamentales que se emplean en UNIX para controlar el acceso de los usuarios a los archivos, y por lo tanto, prácticamente para toda la seguridad del sistema operativo. 3.3.4.- Fechas de los archivos. Las fechas que aparecen al ejecutarse la instrucción ls -l son las fechas de modificación de los archivos (mtime). Se puede obtener la fecha del ultimo acceso (atime) usando la opción -u (escribiendo, por ejemplo, ls -lu). Ambas fechas se pueden cambiar usando una llamada a una rutina de la biblioteca del sistema. Por lo tanto, los administradores deben adquirir el habito de verificar la fecha de cambio del inodo (ctime) usando la opción -c. Por ejemplo, ls -lc. Bajo circunstancias normales, no se puede cambiar el ctime de un archivo. El sistema operativo lo actualiza cada vez que hay un cambio en el inodo del archivo. Puesto que el inodo cambia cuando se modifica un archivo, ctime indica la fecha de la ultima escritura, cambio de protección o cambio de dueño. Un agresor puede cambiar el mtime o el atime de un archivo, pero normalmente el ctime será correcto. Un agresor listo, que adquiere privilegios de superusuario, puede cambiar el reloj del sistema y luego aplicar touch al inodo, forzando así que aparezca un ctime engañoso en el archivo. Ademas el agresor puede cambiar el ctime escribiendo directamente al disco, evitando el uso de las verificaciones del sistema operativo completamente. Si usa Linux, con el sistema de archivos ext2, un agresor puede modificar el contenido del inodo directamente usando la instrucción debugfs. Si la cuenta del superusuario del sistema ha sido comprometida no se puede asumir que ninguna de las tres fechas que se almacenan para cada archivo o directorio sean correctas. Por esta razón, no se puede basar el control de archivos en las fechas. 3.3.5.- Permisos de un archivo Los permisos de un archivo pueden verse en cada línea (los diez primeros caracteres) del listado al ejecutar la orden ls -l. Indican qué es el archivo y qué tipo de acceso otorga (es decir, la posibilidad de leer, escribir o ejecutar ) a los diversos usuarios del sistema. He aquí dos ejemplos de permisos de archivo: drwxr-xr-x -rwsr-sr-x El primer símbolo del campo de modo del archivo indica el tipo del archivo, como se explica en la siguiente tabla: Contenido Significado - Archivo plano d Directorio c Dispositivo de tipo carácter (tty,impresora, ...) b Dispositivo de bloque (disco, CD-Rom, etc.) l Enlace simbólico s Socket =op FIFO Los siguientes nueve símbolos tomados en grupos de tres indican quién y qué tipo de operaciones se pueden hacer con los archivos en la computadora. Hay tres tipos de permisos: r w x permiso de lectura permiso de escritura permiso de ejecución De manera análoga, existen tres clases de permisos: owner group other propietario del archivo usuarios que están en el grupo del propietario todo el mundo (excepto el superusuario) En el siguiente gráfico se muestra como interpretar los distintos privilegios: Los permisos de lectura,escritura y ejecución tienen los siguientes significados específicos: r (READ): El permiso de lectura quiere decir exactamente eso: el archivo se puede abrir con la llamada de sistema open() y su contenido puede leerse con read(). w (WRITE): El permiso de escritura permite sobreescribir con uno nuevo o modificar su contenido. También se puede usar write() para hacerlo mas grande y truncate() o ftruncate() para hacerlo más corto. x (EXECUTE):Este permiso sólo tiene sentido en el caso de programas. Si un archivo tiene los bits de ejecución habilitados, se puede ejecutar escribiendo su trayectoria (o ejecutándolo con algún miembro de la familia de llamadas del sistema exec()). La manera en que se ejecuta el programa depende de los dos primeros bytes del archivo. Se supone que los dos primeros bytes de un archivo ejecutable son un números mágico que indica la naturaleza del archivo. Algunos números significan que el archivo es algún tipo de archivo binario en lenguaje maquina. La secuencia especial de dos bytes “#!” significa que es un guión ejecutable de algún modo. Si los valores son desconocidos se supone que es un guión de interprete de instrucción y se ejecuta de esa manera. Los permisos de un archivo se aplican a los dispositivos y a los sockets con nombre del mismo modo que a los archivos normales. Si se tiene permiso de escritura se puede escribir información al archivo o al objeto. Si se tiene permiso de lectura se puede leer el contenido. Y si no se tienen ninguno de estos permisos, mala suerte. Los permisos de archivo no se aplican a los enlaces simbólicos. El que se pueda o no leer de un archivo al que apunta un enlace simbólico depende de los permisos propios del archivo, y no de los permisos del enlace. De hecho, los enlaces simbólicos casi siempre se crean con los permisos “rwxrwxrwx” (en modo 0777, en octal) y el sistema operativo no los usa para nada. Las siguientes consideraciones sobre los permisos de un archivo son importantes: Se puede tener permiso de ejecución sin tener permiso de lectura. En ese caso se puede ejecutar un programa pero no leerlo. Esto es útil si se desea ocultar el contenido de un programa. Otra forma de usar esto es permitir a los usuarios emplear el programa pero no copiarlo (salvo en servidores de ficheros NFS, que no distinguen este caso, pues para ejecutar un archivo este debe ser enviado al cliente). Si se tiene permiso de lectura pero no de ejecución, se puede hacer una copia del archivo y ejecutarla. No obstante, la copia será distinta principalmente en dos maneras. Tendrá una trayectoria absoluta diferente y será propiedad de quien la haya copiado y no del dueño original del programa (esto se importante en los archivos con el bit SUID o SGID activado). En algunas versiones de UNIX (incluyendo Linux) un guión de instrucciones ejecutable debe tener los bits de lectura y ejecución habilitados para que sea posible ejecutarlo. Los permisos de un archivo se pueden cambiar con la instrucción chmod o con las llamadas al sistema chmod() y fchmod(). Sólo el dueño de un archivo puede cambiar los permisos. La única excepción a esta regla es el superusuario: si alguien está en una sesión de superusuario puede cambiar los permisos de cualquier archivo. Los administradores expertos, prefieren usar chmod especificando los permisos en octal. Al usuario inexperto puede parecerle incómodo, pero una vez que se aprende, es mucho más rápido. Los permisos de un archivo están representados por doce bits, un bit por cada permiso, estos permisos son la interpretación en octal del número binario resultante de poner a uno el permiso deseado y a cero los demás. La siguiente tabla, resume los diferentes permisos en octal: Octal Permiso 4000 SUID --S------ 100000000000 2000 SGID -----S--- 010000000000 1000 “Bit adhesivo”(sticky) --------T 001000000000 0400 Lectura por el dueño r-------- 000100000000 0200 Escritura por el dueño -w------- 000010000000 0100 Ejecución por el dueño --x------ 000001000000 0040 Lectura por el grupo ---r----- 000000100000 0020 Escritura por el grupo ----w---- 000000010000 0010 Ejecución por el grupo -----x--- 000000001000 0004 Lectura por otros ------r-- 000000000100 0002 Escritura por otros -------w- 000000000010 0001 Ejecución por otros --------x 000000000001 El cálculo del valor octal que representa los permisos que se desean, se realiza efectuando un OR o suma (en este caso es lo mismo) de los valores de los permisos, o convirtiendo a octal el binario correspondiente a esos permisos, si se está acostumbrado. Dada la importancia de los permisos, UNIX provee una utilidad, llamada umask, que permite especificar los permisos de creación por defecto. Realmente, el argumento que acepta umask es una máscara en octal con los permisos que no se quieren asignar a los archivos nuevos. El valor más usual es 022, que elimina el permiso de escritura para el grupo y otros. Cuando los permisos de archivo se aplican sobre un directorio, estos tienen significados especiales: r: Se permite usar las funciones opendir() y readdir() (o el comando ls) para buscar qué archivos están en el directorio. w:Se puede añadir, renombrar o quitar entradas en ese directorio. x: Se puede emplear stat al contenido del directorio (es decir, se puede determinar quiénes son los dueños y cuáles son los tamaños de los archivos que están en el directorio). También se requiere permiso de ejecución a un directorio para convertirlo en directorio actual o para abrir archivos que estén dentro de ese directorio (o en cualquiera de sus subdirectorios). 3.3.6.- SUID, SGID y los bits adhesivos Muchos programas, como su, basan su funcionamiento en el uso de los bits SUID y SGID. Para entender completamente como maneja UNIX esta técnica es imprescindible conocer los conceptos de UID real, UID efectivo y UID guardado. UNIX almacena para todos los procesos que ejecuta dos identificadores básicos de usuario: UID real y UID efectivo. Para un proceso normal, resultante de la ejecución de un programa que no es SUID, los dos identificadores son el mismo e iguales al UID del proceso que lo generó. Esto resulta más sencillo con un ejemplo: cuando un usuario inicia una sesión, el proceso login verifica la identidad del usuario en el fichero /etc/passwd y genera un shell con UIDs real y efectivo iguales al del usuario. Cuando el usuario ejecuta cualquier programa que no sea SUID, sea suyo o no, sobre el que por supuesto debe tener permiso de ejecución, el proceso creado hereda los UIDs del shell. Este comportamiento se mantiene si este proceso crea otros procesos, que a su vez pueden crear otros procesos, y así sucesivamente. Pensemos, por ejemplo, en un usuario que teclea bash continuamente, todos los shell creados tendrán los mismos UIDs. De esta manera, si no tenemos en cuenta la existencia del bit SUID, bastaría con un UID que identificara al usuario durante toda la sesión, puesto que no hay forma de que el usuario cambie el UID de un proceso. El problema es que UNIX utiliza el UID efectivo del proceso (Linux utiliza un cuarto identificador, el FSUID) para analizar los privilegios que este tiene sobre el sistema de ficheros. Pero un usuario necesita escribir en archivos que no le pertenecen: para cambiar su contraseña necesita escribir en /etc/passwd, para imprimir debe dejar sus archivos en un directorio de spool, y multitud de tareas más. Si no se hubiera implementado ninguna manera de alterar esto, sólo habría una forma de que el usuario escribiera en directorios y archivos que no le pertenecen: darle permiso sobre estos y, por extensión, a todos los usuarios del sistema. Pero esto es imposible, acabaría con el sentido de la protección en el sistema de ficheros y con la seguridad de UNIX. Para evitar estos problemas, UNIX permite que los programas tengan privilegios. Los procesos que ejecutan estos programas pueden adquirir un UID o GID distinto al propio cuando se están ejecutando. Un programa que cambia su UID se llama programa SUID, y un programa que cambia su GID se llama programa SGID. Un programa puede ser SUID y SGID al mismo tiempo. Cuando se ejecuta un programa SUID, su UID efectivo se convierte en el del dueño del archivo, en lugar del UID del usuario que lo está ejecutando. Este concepto es tan ingenioso que AT&T (Dennis Ritchie) lo patentó, aunque después la patente ha sido transferida al dominio público. En el siguiente gráfico se muestra como interpretar los distintos privilegios: SUID: Un proceso que ejecuta un programa SUID obtiene un UID efectivo que es el del dueño del programa. SGID: Un proceso que ejecuta un programa SGID tiene su GID efectivo cambiado al GID del programa. Los archivos creados por el proceso también pueden tener su grupo primario establecido a este GID, dependiendo de los permisos del directorio donde se crean los archivos. En las versiones de UNIX derivadas de Berkeley (también en Linux), un proceso que ejecuta un programa SGID adquiere también el GID del programa temporalmente, el cual se coloca en la lista de GID del proceso. Solaris y otros UNIX derivados de System V usan el bit de GID de los archivos de datos para habilitar el bloqueo obligatorio de archivos. Adhesivo: Está obsoleto en lo que se refiere a archivos, pero se usa para directorios. Cuando un archivo ejecutable tenía el bit adhesivo activado, significaba que debía permanecer en memoria principal el mayor tiempo posible. Esto mejoraba el rendimiento en computadores con una cantidad muy limitada de memoria RAM (sobre 64K, incluso). Pero esto ya no tiene mucho sentido. Sin embargo, si un directorio tiene activado el sticky bit (bit adhesivo o de permanencia), significa que aunque varios usuarios tengan permiso de escritura en él, sólo el propietario del archivo y el superusuario pueden borrar un archivo. Esto es útil en directorios donde pueden escribir muchos usuarios, pero no se desea que los usuarios se borren archivos unos a otros,como en /tmp o en un directorio de spool. El bit SGID en directorios también tiene un comportamiento particular. Si está activado, los archivos de usuario que se crean en ese directorio, tendrán el mismo GID que el directorio, si el usuario también está en ese grupo. Si el usuario no está en el grupo del directorio, los archivos se crean con el GID del usuario (por lo general el primario), como es normal. Es importante señalar que para activar estos permisos, también debe darse el permiso de ejecución correspondiente, es decir, ejecución para el propietario para el bit SUID, ejecución para el grupo para el bit SGID y ejecución para otros para el bit adhesivo. Si no se hace así, el bit correspondiente aparecerá en mayúsculas (S o T) y no en minúsculas (s o t), lo que indica que el bit está establecido pero no tendrá efecto. Esta característica que hace que UNIX sea tan versátil, es también uno de los mayores problemas de seguridad. Cualquier usuario puede convertirse en superusuario simplemente ejecutando una copia SUID de bash que sea propiedad del root. Afortunadamente, sólo el superusuario puede realizar esta copia. Por lo tanto, uno de los objetivos del administrador será evitar que existan este tipo de archivos en la computadora. Si se deja un terminal sin vigilancia, cualquiera puede apoderarse de la cuenta simplemente introduciendo las siguientes instrucciones: % cp /bin/bash /tmp/trampa % chmod 4755 /tmp/trampa % Estas instrucciones crean una copia SUID del programa bash. Cada vez que el agresor ejecute este programa se convierte en el usuario, con pleno acceso a los archivos y privilegios. Se supone que el usuario tiene algún archivo interesante para el agresor, privilegios que le permitan acceder al superusuario, o quiere suplantarle para alguna actividad delictiva, en el peor caso el usuario será el propio root. El agresor podría copiar ese programa en un directorio oculto que sólo el superusuario, buscando archivos de este tipo, podría encontrar. Pero no todos los administradores realizan esta tarea con regularidad. Es importante notar que el programa no tiene porque ser un interprete de comandos, un simple editor de textos SUID puede permitir al agresor modificar archivos o incluso crear un interprete de comandos que se ejecute con el UID del usuario (vi lo permite con el comando shell). La mayor parte de los programas SUID de un sistema son SUID de root, es decir, se convierten en el superusuario cuando se ejecutan. Teóricamente, esto no es un fallo de seguridad, porque un programa compilado únicamente puede llevar a cabo las funciones para las que fue compilado (se puede cambiar la contraseña propia usando passwd, pero no se puede cambiar la contraseña de otro usuario). Pero se han encontrado muchos fallos de seguridad creados por gente que se las ha ingeniado para lograr que un programa SUID haga algo para lo que no fue diseñado. En muchos casos programas que son UID de root podrían haberse diseñado para ser SUID de otra cuenta (como daemon). Con demasiada frecuencia se usa SUID de root cuando sería suficiente con privilegios menores. Un ejemplo de programa SGID con un fallo de seguridad es el programa write. Como write permite escribir en el terminal de otro usuario, este programa es SGID de tty. Esto permite que write escriba nuestro texto en el otro terminal. El fallo es que write permitía que el usuario ejecute cualquier instrucción escribiendo el carácter '!' al principio de una línea. El programa ejecutado heredaría el GID efectivo de tty, lo que permitiría al usuario, por ejemplo, 'escuchar' los terminales de otros usuarios. En la actualidad, el programa write sigue siendo SGID de tty, pero cambia el GID de tty al del usuario antes de ejecutar el shell (más adelante se verá como se consigue esto) y luego lo restablece al de tty. En algunas versiones, esta característica está deshabilitada. Otro ejemplo, esta vez de SUID, era el programa /usr/lib/preserve, que realizaba las copias de seguridad para los editores vi y ex. Para poder hacer copias de seguridad en el directorio, el programa era SUID de root, de forma que el directorio estaba protegido contra la lectura por parte de los usuario (quizás alguien estuviera editando algo importante y no se podía permitir que lo leyera cualquiera). Había tres detalles que hicieron que el programa fuera un fallo de seguridad: Era SUID de root Ejecutaba /bin/mail como root para avisar a los usuarios de que su archivo estaba copiado Ejecutaba el programa mail con la llamada al sistema system() El problema era que system() usa sh para leer la cadena que ejecuta. Hay una variable del intérprete de comandos, llamada IFS (separador interno de campos), que sh utiliza para ubicar las separaciones entre las palabras de cada línea que lee. Normalmente IFS es el espacio en blanco, pero si se establecía IFS como '/' entonces, al llamar a /bin/mail se estaba llamando en realidad a un programa llamado bin del directorio actual, pero con privilegio de root. El resto es fácil, haciendo una copia de un shell en el directorio actual y llamándola bin, bastaba con ejecutar vi para convertirse en root. El problema era que el programa preserve tenía más privilegios de los necesarios. La norma del privilegio mínimo establece que los programas (y usuarios) tengan los privilegios imprescindibles para realizar su labor, y no más. En este caso, habría bastado con hacer a preserve SGID del grupo preserve, creado expresamente. Esto no hubiera eliminado el fallo, pero lo habría minimizado. El agresor sólo podría leer las copias de los trabajos de otros usuarios. Las versiones actuales de sh no toman en cuenta IFS si se ejecuta en modo root o si el UID efectivo es diferente del real. El problema de los bits SUID y SGID está muy unido al ataque basado en la técnica del desbordamiento de pila (buffer overflow), que se tratará más adelante. 3.3.6.1.- Cambio de UID: setuid() Con el objeto de facilitar la programación de utilidades seguras, UNIX proporciona una serie de funciones que permiten cambiar entre los UID real, efectivo y guardado. Como se ha visto en el ejemplo de write, para que la aplicación pueda ejecutar una orden del usuario sin peligro para la seguridad, esta debe cambiar al GID del usuario antes de ejecutarla y luego restablecer el GID anterior. Una solución para esto podría ser permitir el cambio entre los UID efectivo y real (o entre los GID efectivo y real, el funcionamiento es el mismo) de forma que durante la ejecución de esa parte del proceso (o la de sus hijos, si es el caso) se perdieran los privilegios que se tenían. Veamos un ejemplo: Si el usuario 501 ejecuta el programa write, éste tiene GID real 501(usuario) y GID efectivo 5(tty). Al introducir una línea que comienza por '!', el programa write detecta que tiene que ejecutar un comando: para ello primero intercambia los GID, quedando GID real 5(tty) y GID efectivo 501(usuario) y después ejecuta el comando. Ahora, el comando ha heredado los GID (y los UID) de write, pero ya no tiene el GID efectivo de tty sino 501, el del usuario. Después, write puede volver a intercambiar los GID y continuar su ejecución normalmente. Esto tiene un fallo: nada impide al proceso del usuario volver a intercambiar los GID, y recuperar los privilegios anteriores. Para solucionar definitivamente este problema, UNIX utiliza la familia de funciones setuid() y setgid(). Estas funciones utilizan un tercer UID llamado UID guardado o salvado. El cambio a un UID/GID real o efectivo sólo está permitido si el proceso posee ese UID/GID como UID/GID efectivo, real o guardado. Sólo un proceso con UID 0(root) puede cambiar a cualquier otro UID/GID. En el ejemplo anterior (nuevamente, el funcionamiento es igual con el UID), write puede establecer el GID efectivo a 501(usuario) dejando el GID real como estaba (501(usuario)), quedando como GID guardado el 5(tty). De esta forma, el proceso generado hereda estos dos GID, pero no el guardado, y no puede cambiar otra vez al GID 5(tty) puesto que no lo tiene. Pero write sí puede restablecer el GID efectivo a 5(tty), puesto que ése es el GID guardado. Esta vez todo se ha hecho de forma segura, y el agujero de seguridad de write ha desaparecido. Este es el funcionamiento de las diferentes funciones que permiten realizar estos cambios (por lo menos, en su versión Linux): getuid(void): Devuelve el UID real del proceso actual. geteuid(void): Devuelve el UID efectivo del proceso actual. setuid(uid_t uid): (POSIX) Establece el UID efectivo del proceso en curso. Si el UID efectivo del proceso que las llama es 0(root) o si el proceso es SUID de root, se establecen también los UID real y guardado. Esto impide a un proceso del root que deja sus privilegios que los recupere más tarde. setreuid(uid_t ruid,uid_t euid) (BSD): Establece el UID real y el efectivo del proceso en curso. Un valor de -1 para el UID real o efectivo fuerza al sistema a dejar dicho ID sin cambios. Si el UID real es cambiado, o el UID efectivo se pone a un valor distinto del UID real previo, el UID guardado tomará en valor del nuevo UID efectivo. seteuid(uid_t euid) (BSD): Es funcionálmente equivalente a setreuid(-1, euid). getgid(void): Devuelve el GID real del proceso actual. getegid(void): Devuelve el GID efectivo del proceso actual. setgid(gid_t gid) (POSIX): Establece el GID efectivo del proceso en curso. Si el GID efectivo del proceso que las llama es 0(root) o si el proceso es SGID de root, se establecen también los GID real y guardado. Esto impide a un proceso del root que deja sus privilegios que los recupere más tarde. setregid(gid_t rgid, gid_t egid) (BSD):Establece el GID real y el GID efectivo del proceso en curso. Un valor de -1 para el GID real o efectivo fuerza al sistema a dejar dicho ID sin cambios. Si el GID real es cambiado, o el GID efectivo se pone a un valor distinto del GID real previo, el GID guardado tomará el valor del nuevo GID efectivo. setegid(gid_t egid) (BSD): Es funcionálmente equivalente a setregid(-1, egid). 3.3.7.- Listas de control de acceso (ACLs) Algunas versiones de UNIX permiten usar listas de control de acceso (ACL, Access Control Lists). Éstas se ofrecen normalmente como extensiones a los modos de permisos de archivo estándar de UNIX. Usando ACLs se pueden especificar derechos adicionales de acceso para cada archivo y directorio a muchos usuarios en individual, en lugar de tener que hacerlo colocando a todos en un grupo nuevo o en la categoría 'otros'. También se pueden asignar permisos distintos a grupos diferentes. Se trata de una característica que se popularizará en los próximos años. Lamentablemente cada proveedor las ha implementado de forma diferente. Las ACLs permiten refinar la capacidad de otorgar permisos de archivo que tiene el UNIX estándar. Permiten la especificación completa del acceso a subconjuntos completamente arbitrarios de usuarios y/o grupos de usuarios. Tanto AIX como HP-UX ofrecen listas de control de acceso. Solaris y Linux supuestamente las ofrecerán en versiones futuras. Para muchos propósitos las ACLs son mejores que el mecanismo de grupos que ofrece UNIX a proyectos de colaboración pequeños. Si Ana quiere darle a Miriam, y sólo a Miriam, acceso a un archivo en particular, puede modificar la ACL del archivo para otorgárselo. Sin ACLs Ana tendría que acudir al administrador del sistema, pedirle que cree un grupo nuevo que tuviera como miembros tanto a Ana como a Miriam (y sólo a ellas) y luego cambiar el grupo del archivo para otorgar privilegios al nuevo grupo. 3.3.8.- Archivos de dispositivo Como ya se ha comentado antes, todos los dispositivos en UNIX se representan como archivos normales. En el inodo se especifica que se trata de un dispositivo, el tipo de dispositivo, y su números de dispositivo principal y secundario que permiten al sistema identificar al dispositivo. El hecho de que los dispositivos sean archivos, da a UNIX una gran flexibilidad y unifica el acceso a los dispositivos, de forma que los programadores pueden desarrollar sus programas sin saber el tipo de dispositivo que se va a utilizar. Pero esto también representa un problema de seguridad. Si un usuario consigue acceso sin restricciones a un archivo de dispositivo, podrá hacer lo que quiera con él. Si es un terminal, podrá leer lo que otro usuario escribe, si es un disco, podrá escribir y leer en cualquier sitio, si es el dispositivo kmem, podrá cambiar su UID o bloquear el sistema escribiendo basura en el área de memoria reservada para éste. UNIX agrupa todos los archivos de dispositivo en el directorio /dev. Por esta razón es muy importante que el administrador revise periódicamente los permisos de estos archivos. Unos pocos dispositivos deben permitir el acceso a todos los usuarios, como /dev/null o /dev/tty. Pero casi todos los demás no deben permitir lectura ni escritura por los usuarios normales. También se debe revisar el sistema en busca de archivos de dispositivo creados por un intruso en cualquier parte del sistema de ficheros, y que podría darle acceso a este dispositivo. 3.3.9.- Sistemas de ficheros montados Para poder acceder de forma uniforme a distintos tipos de sistemas de ficheros, UNIX incorpora una capa superior de abstracción denominada VFS, Virtual File System o Sistema de Ficheros Virtual, que se ocupa de proporcionar este acceso uniforme a diferentes tipos de sistemas de ficheros. Montar un sistema de ficheros consiste en asociar un determinado nombre de directorio (punto de montaje) con el sistema de ficheros en cuestión. A partir de ese momento el sistema de ficheros montado podrá ser utilizado de forma normal por los usuarios. Es habitual en grandes sistemas UNIX montar diferentes partes de la estructura de directorios estándar (por ejemplo /home o /usr) en distintos dispositivos. Esto no presenta ningún problema de seguridad, puesto que estos sistemas se montan cada vez que se inicia el sistema, normalmente son necesarios para el funcionamiento normal del sistema. Pero para utilizar discos removibles, CD-ROMs o sistemas de ficheros en red (NFS) no queda más remedio que montarlos como parte del sistema de ficheros. Esto si que representa un problema de seguridad. Estos sistemas de ficheros pueden contener archivos ejecutables (por ejemplo un shell con SUID de root) que pueden comprometer la seguridad del sistema. Para evitar estos problemas, el archivo de configuración fstab, que almacena la forma en que se montan los distintos dispositivos, y el comando mount, que se encarga de montarlo, aceptan una serie de opciones que protegerán nuestro sistema. Estas opciones son: auto/noauto: Puede o no montarse con la opción -a (se montan todos automáticamente) dev/nodev: Interpreta o no dispositivos especiales exec/noexec: Permite o no la ejecución de binarios rw/ro: El sistema de ficheros de monta como de lectura/escritura o como sólo lectura. suid/nosuid: Permite o no el efecto de los bits SETUID y SGID user/nouser: Permite o no que los usuarios ordinarios monten el sistema de ficheros. La opción user implica noexec, nosuid y nodev a menos que se indique lo contrario explícitamente. sync: Toda E/S ha de realizarse de forma síncrona. Esta opciones bien utilizadas impiden que un usuario monte un sistema de ficheros que pueda contener peligros como un dispositivo que apunte a nuestro disco (y sobre el que tenga permisos), un shell SUID de root y peligros similares. En muchos sistemas es habitual que sólo el superusuario pueda montar sistemas de ficheros, incluso que sólo él tenga acceso físico a los dispositivos. 4.- Mantenimiento de sistemas confiables 4.1.- Introducción En este tema, se tratan las principales labores a realizar por parte del administrador, para mantener un sistema seguro, así como las técnicas mas utilizadas para evitar la pérdida de datos y para responder a los ataques de los intrusos. 4.2.- Criptografía Aunque la misión principal de la seguridad en un sistema informático es mantener la información confidencial, en ocasiones, no podemos confiar sólo en la seguridad del sistema. Cuando la información es demasiado importante, se envía a través de medios de comunicación poco confiables o es imposible mantenerla en secreto, no queda más remedio que recurrir a la criptografía. En la actualidad, existen multitud de métodos y aplicaciones criptográficas. Como no es el objetivo de este trabajo, sólo haremos una breve mención de los diferentes tipos de sistemas criptográficos y su relación con el cifrado de contraseñas. Hasta la década de los 70, todos los criptosistemas conocidos, funcionaban con una clave de cifrado igual a la de descifrado o, si eran diferentes, una podía obtenerse de la otra en un tiempo y con unos recursos razonables. La invulnerabilidad de tales sistemas dependía, por tanto, del mantenimiento en secreto de la clave. Consiguientemente, debía transmitirse del emisor al receptor a través de un canal seguro, obviamente diferente del canal del criptosistema, que por hipótesis está amenazado por posibles interceptores. Estos criptosistemas se denominan de clave secreta, de clave única y también simétricos. Sin embargo, en 1976, Diffie y Hellman demostraron la posibilidad de conseguir criptosistemas que no precisaban transferir una clave secreta entre el emisor y el receptor, evitando así los problemas inherentes a la búsqueda de canales seguros para tal transferencia, previamente al establecimiento de una transmisión cifrada. En estos criptosistemas la clave de cifrado, denominada clave pública, se hace de general conocimiento. Sin embargo la clave de descifrado, denominada clave privada, se mantiene en secreto. Obviamente ambas claves no son independientes, pero del conocimiento de la pública no se infiere la privada, a no ser que se tenga algún dato adicional que también habrá que mantenerse en secreto o, mejor, destruirse una vez generado el par clave pública-clave privada. Cuando un usuario desea recibir informaciones cifradas, da a conocer a todos los potenciales remitentes su clave pública. Así, éstos cifrarán los mensajes destinados al citado usuario con la misma clave, la pública del destinatario. Sin embargo, no conociendo nadie más la clave privada, ni pudiéndola obtener a partir de la pública, sólo el destinatario podrá descifrar la información a él remitida. Estos criptosistemas se denominan, de clave pública o asimétricos, pues del conocimiento de una clave no se deduce la otra. Todavía se puede considerar un último tipo de sistemas de cifrado denominados criptosistemas irreversibles, basados en algoritmos no invertibles. En estos criptosistemas, dado el criptograma no es posible obtener el mensaje en claro. Figuradamente, se puede decir que sólo trabajan en un sentido, el que lleva del mensaje en texto claro al texto cifrado, pero no en el contrario, que conduce de este último a aquél. Estos cifrados se emplean en aplicaciones que no requieren descifrar los criptogramas. Una de estas aplicaciones consiste en la determinación de si un cierto mensaje se corresponde con un determinado texto cifrado almacenado en un sistema. Para ello el algoritmo de cifrado produce un criptograma, que se compara con el guardado en la memoria. Para esta aplicación se requiere, además de que el algoritmo sea no invertible, que sea unívoco, es decir, que dos mensajes distintos produzcan dos cifrados diferentes. Un ejemplo de este tipo de aplicaciones se tiene en el almacenamiento de las contraseñas de usuarios de un sistema. El mantenimiento en memoria de las mismas sin cifrar representa un grave riesgo, pues alguien puede averiguarlas y suplantar a los legítimos usuarios. Almacenarlas cifradas mediante criptosistemas de los tipos vistos hasta ahora también es inseguro, pues la clave descifrada puede ser conocida por el administrador del sistema (y posiblemente por más profesionales informáticos que trabajen sobre el ordenador), quien con el conocimiento del algoritmo de cifrado y de las contraseñas criptografiadas es capaz de averiguar las contraseñas en claro. Por lo anterior, para el almacenamiento de contraseñas se emplean algoritmos no invertibles. Cada vez que el usuario introduce su contraseña, se cifra y compara con la contraseña cifrada almacenada en la tabla correspondiente. El acceso se permite sólo si coinciden ambas. Esto implica que una contraseña olvidada no se puede recuperar, debiéndose generar otra que será nuevamente almacenada como un criptograma. 4.3.- Defensa de cuentas Cada una de las cuentas en la computadora, es una puerta al exterior, un portal a través del cual usuarios autorizados y no autorizados pueden entrar. Algunos de los portales están bien protegidos, mientras que otros pueden no estarlo. El administrador del sistema debe buscar puntos débiles y sellarlos. 4.3.1.- Cuentas sin contraseña La contraseña de cada una de las cuentas es la primera línea de defensa de un sistema. Una cuenta sin contraseña es una puerta sin cerradura, cualquiera que conozca el nombre de la cuenta puede entrar. Muchos de los intrusos tienen éxito, sólo por que son buenos para encontrar cuentas sin contraseña o cuentas que tienen contraseñas fáciles de adivinar. En la versión SVR4 de UNIX, se pueden rastrear cuentas sin contraseña usando la instrucción: # logins -p También se pueden rastrear las cuentas sin contraseña usando la instrucción: % cat-passwd | awk -F: 'length($2)<1 {print $1}' manurod maripet En este ejemplo, manurod y maripet no tienen contraseña no tienen contraseña. Obsérvense los registros en el archivo /etc/passwd. manurod:EH5/.mj7J5dFh:501:100:Manuel Rodríguez:/home/alumnos/manurod:/bin/bash maripet:aCq87MCñ03c9e:502:100:Mario Petru:/home/alumnos/maripet:/bin/bash Puede que el archivo /etc/passwd no sea el más indicado para buscar cuentas sin contraseña en los sistemas que tienen instalados archivos de contraseñas ocultas. Diferentes sistemas de contraseñas ocultas almacenan las contraseñas cifradas reales en diferentes lugares. También es el caso de sistemas que usan NIS, NIS+ o DCE. 4.3.2.- Cuentas predeterminadas Muchos sistemas se entregan al usuario final con una o más cuentas predeterminadas. Estas cuentas generalmente tienen contraseñas estándar. Por ejemplo, muchas computadoras UNIX vienen con una cuenta root que no tiene contraseña. Los distribuidores aconsejan a los usuarios asignar contraseñas a esas cuentas, pero con mucha frecuencia los usuarios no lo hacen. Una forma de solucionar este problema que han utilizado muchos distribuidores es hacer que el sistema operativo solicite contraseñas para cuentas especiales como root cuando se instala por primera vez. Algunos programas de aplicación instalan automáticamente cuentas con nombres como demo (que se usa para demostrar los programas). Es necesario asegurarse de borrar o inhabilitar esas cuentas después de instalar el programa 4.3.3.- Cuentas que ejecutan sólo una instrucción UNIX permite al administrador crear cuentas que pueden ejecutar simplemente una sola instrucción o programa de aplicación, en lugar del intérprete de comandos, cuando un usuario entra en ellas. A menudo estas cuentas no tienen contraseña. Si se tienen estas cuentas instaladas en la computadora, alguien puede usarlas para encontrar la hora o determinar quién está conectado a la computadora al teclear simplemente el nombre de la instrucción en el indicador login:. Si se decide configurar una cuenta de este tipo, se debe asegurar de que la instrucción que se ejecuta no reciba ninguna entrada desde teclado, y que no pueda forzarse a dar al usuario un proceso interactivo. Específicamente, estos programas no deben tener escapes al interprete de comandos. 4.3.4.- Cuentas abiertas Muchos sistemas proporcionan cuentas en las cuales los visitantes pueden acceder a juegos, a un modem o a una conexión a la red. Estas cuentas reciben nombres como play, open o guest y no suelen precisar de contraseña. Debido a que los nombres de estas cuentas son conocidos o fáciles de adivinar, representan potencialmente violaciones a la seguridad, ya que un agresor puede entrar inicialmente a una de estas cuentas para, rastrear desde allí fallos de seguridad de mayor índole . 4.3.4.1.- interpretes de comandos restringidos Un intérprete de instrucciones restringido se puede usar para minimizar los peligros que representa una cuenta abierta. Este modo ocurre cuando se invoca al intérprete de instrucciones en la línea de comandos con la opción -r, o con la instrucción llamada rsh (interprete de instrucciones restringido). Cuando rsh se inicia, ejecuta las instrucciones en el archivo $HOME/.profile. Una vez que el .profile se procesa, la siguientes restricciones tienen efecto: El usuario no puede cambiar el directorio actual. El usuario no puede cambiar el valor de la variable PATH. El usuario no puede usar nombres de instrucciones que contengan '/'. El usuario no puede redirigir la salida con '>' o '>>'. Como una medida de seguridad adicional, si el usuario trata de interrumpir el rsh mientras esté procesando el archivo $HOME/.profile , el rsh sale inmediatamente. 4.3.4.2.- Sistemas de archivos restringidos Otra forma de restringir a los usuarios del sistema es poniéndolos en un sistema de archivos restringidos. La forma de hacer esto es con la llamada del sistema chroot(), que cambia la vista que un proceso tiene del sistema de archivos, de tal manera que el directorio raíz que ve no es el del sistema de archivos real sino uno de sus descendientes. En SVR4 esto se puede conseguir poniendo un '*' en el campo de intérprete de instrucciones del usuario en el archivo /etc/passwd , entonces el sistema hará una llamada a chroot()con el campo de directorio HOME del usuario. El sistema de archivos restringido debe contener todos los archivos e instrucciones necesarios para que el programa login se ejecute y para poder ejecutar programas, incluyendo bibliotecas compartidas. Esto es útil para mantener cuentas de usuario limitadas, para que estos usuarios sólo puedan realizar un conjunto limitado de tareas y para la evaluación de programas nuevos de funcionamiento dudoso 4.4.- Auditoría 4.4.1.- Introducción Casi todas las actividades realizadas en un sistema UNIX son susceptibles de ser monitorizadas: desde las horas de acceso de cada usuario al sistema hasta las paginas web más visitadas, pasando por los intentos fallidos de conexión, los programas ejecutados o incluso el tiempo de CPU que cada usuario consume. Obviamente esta facilidad de UNIX para recoger información tiene unas ventajas inmediatas para la seguridad: es posible detectar un intento de ataque nada mas producirse el mismo, así como detectar usos indebidos de los recursos o actividades sospechosas; sin embargo, existen desventajas, ya que la gran cantidad de información que potencialmente se registra puede ser aprovechada para crear negaciones de servicio o esa cantidad de información puede hacer difícil detectar problemas por el volumen de datos a analizar. La mayoría de los log en UNIX son simples ficheros de texto, que se pueden visualizar con un cat. Por una parte esto es bastante cómodo para el administrador del sistema, ya que no necesita de herramientas especiales para poder revisar los logs e incluso puede programar shellscripts para comprobar los informes generados de forma automática, con ordenes como awk, grep o sed. No obstante, el hecho de que estos ficheros sean texto plano hace que un atacante lo tenga muy fácil para ocultar ciertos registros modificando los archivos con cualquier editor de textos. 4.4.2.- El sistema de log en UNIX Una desventaja añadida al sistema de auditoría en UNIX puede ser la complejidad que puede alcanzar una correcta configuración; por si la dificultad del sistema no fuera suficiente, en cada UNIX el sistema de logs tiene peculiaridades que pueden propiciar la perdida de información interesante de cara al mantenimiento de sistemas seguros. Aunque muchos de los ficheros de log de los que hablaremos a continuación son comunes en cualquier sistema, su localización, o incluso su formato, pueden variar entre diferentes sistemas. La localización de ficheros y ciertas ordenes relativas a la auditoría varían de unas maquinas a otras, por lo que es muy recomendable consultar las paginas del manual antes de ponerse a configurar el sistema de auditoría en un equipo concreto. La principal diferencia entre los diferentes sistemas es el denominado process accounting o simplemente accounting, consistente en registrar todos los programas ejecutados por cada usuario; evidentemente, los informes generados en este proceso pueden llegar a ocupar muchísimo espacio en disco (dependiendo del numero de usuarios en nuestro sistema) por lo que solo es recomendable en situaciones muy concretas, por ejemplo para detectar actividades sospechosas en una maquina o para cobrar por el tiempo de CPU consumido. En los sistemas System V el process accounting esta desactivado por defecto; se puede iniciar mediante /usr/lib/acct/startup, y para visualizar los informes se utiliza la orden acctcom. En la familia BSD los equivalentes a estas ordenes son accton y lastcomm; en este caso el process accounting esta inicializado por defecto. Con el modelo clásico se genera un registro tras la ejecución de cada proceso, en UNIX C2 se proporciona una pista de auditoría donde se registran los accesos y los intentos de acceso de una entidad a un objeto, así como cada cambio en el estado del objeto, la entidad o el sistema global. Esto se consigue asignando un identificador denominado Audit ID a cada grupo de procesos ejecutados (desde el propio login), identificador que se registra junto a la mayoría de llamadas al sistema que un proceso realiza, incluyendo algunas tan comunes como write(), open(), close() o read(). Registrar todo esto conlleva un gran cantidad de espacio y de CPU, por lo que en la mayoría de sistemas, el modelo de auditoría C2 es innecesario y en muchas ocasiones se convierte en una monitorizacion inútil si no se dispone de mecanismos para interpretar o reducir la gran cantidad de datos registrados: el administrador guarda tanta información que es casi imposible analizarla en busca de actividades sospechosas. 4.4.3.- El demonio syslogd El demonio syslogd (Syslog Daemon) se lanza automáticamente al arrancar un sistema UNIX, y es el encargado de guardar informes sobre el funcionamiento de la maquina. Recibe mensajes de las diferentes partes del sistema (núcleo, programas. . . ) y los envía y/o almacena en diferentes localizaciones, tanto locales como remotas, siguiendo un criterio definido en el fichero de configuración /etc/syslog.conf, donde especificamos las reglas a seguir para gestionar el almacenamiento de mensajes del sistema. Las líneas de este archivo que comienzan por `#' son comentarios, con lo cual son ignoradas de la misma forma que las líneas en blanco; si ocurriera un error al interpretar una de las líneas del fichero, se ignoraría la línea completa. Un ejemplo de fichero /etc/syslog.conf es el siguiente: # Log all kernel messages to the console. # Logging much else clutters up the screen. #kern.* /dev/console # Log anything (except mail) of level info or higher. # Don't log private authentication messages! *.info;mail.none;authpriv.none /var/log/messages # The authpriv file has restricted access. authpriv.* /var/log/secure # Log all the mail messages in one place. mail.* /var/log/maillog # Everybody gets emergency messages, plus log them on another # machine. *.emerg * # Save mail and news errors of level err and higher in a # special file. uucp,news.crit /var/log/spooler # Save boot messages also to boot.log local7.* /var/log/boot.log Podemos ver que cada regla del archivo tiene dos campos: un campo de selección y un campo de acción, separados por espacios o tabuladores. El campo de selección esta formado a su vez de dos partes: una del servicio que envía el mensaje y otra de su prioridad, separadas por un punto ( . ); ambas son indiferentes a mayúsculas y minúsculas. La parte del servicio contiene una de las siguientes palabras clave: auth, authpriv, cron, daemon, kern, lpr, mail, mark, news, security (equivalente a auth), syslog, user, uucp y local0 hasta local7. Esta parte especifica el subsistema que ha generado ese mensaje (por ejemplo, todos los programas relacionados con el correo generaran mensajes ligados al servicio mail). La prioridad esta compuesta de uno de los siguientes términos, en orden ascendente: debug, info, notice, warning, warn (equivalente a warning), err, error (equivalente a err), crit, alert, emerg, y panic (equivalente a emerg). La prioridad define la gravedad o importancia del mensaje almacenado. Todos los mensajes de la prioridad especificada y superiores son almacenados de acuerdo con la acción requerida. Ademas de los términos mencionados hasta ahora, el demonio syslogd emplea los siguientes caracteres especiales: '*' (asterisco) Empleado como comodín para todas las prioridades y servicios anteriores, dependiendo de dónde son usados (si antes o después del carácter de separación . ): # Guardar todos los mensajes del servicio mail en /var/adm/mail # mail.* /var/adm/mail ' ' (blanco, espacio, nulo) Indica que no hay prioridad definida para el servicio de la linea almacenada. ',' (coma) Con este carácter es posible especificar múltiples servicios con el mismo patrón de prioridad en una misma línea. Es posible enumerar cuantos servicios se quieran: # Guardar todos los mensajes mail.info y news.info en # /var/adm/info mail,news.=info /var/adm/info ';' (punto y coma) Es posible dirigir los mensajes de varios servicios y prioridades a un mismo destino, separándolos por este carácter: # Guardamos los mensajes de prioridad "info" y "notice" # en el archivo /var/log/messages *.=info;*.=notice /var/log/messages '=' (igual) De este modo solo se almacenan los mensajes con la prioridad exacta especificada y no incluyendo las superiores: # Guardar todos los mensajes críticos en /var/adm/critical # *.=crit /var/adm/critical '!' (exclamación) Preceder el campo de prioridad con un signo de exclamación sirve para ignorar todas las prioridades, teniendo la posibilidad de escoger entre la especificada (!=prioridad) y la especificada más todas las superiores (!prioridad). Cuando se usan conjuntamente los caracteres `=' y `!', el signo de exclamación `!' debe preceder obligatoriamente al signo igual `=', de esta forma:!=. # Guardar mensajes del kernel de prioridad info, pero no de # prioridad err y superiores # Guardar mensajes de mail excepto los de prioridad info kern.info;kern.!err /var/adm/kernel-info mail.*;mail.!=info /var/adm/mail Por su parte, el campo de acción describe el destino de los mensajes, que puede ser : Un fichero plano: Normalmente los mensajes del sistema son almacenados en ficheros planos. Dichos ficheros han de estar especificados con la ruta de acceso completa (comenzando con `/'). Podemos preceder cada entrada con el signo menos, - , para omitir la sincronización del archivo (vaciado del buffer de memoria a disco). Aunque puede ocurrir que se pierda información si el sistema cae justo después de un intento de escritura en el archivo, utilizando este signo se puede conseguir una mejora importante en la velocidad, especialmente si estamos ejecutando programas que mandan muchos mensajes al demonio syslogd. # Guardamos todos los mensajes de prioridad critica en critical # *.=crit /var/adm/critical Un terminal (o la consola): También tenemos la posibilidad enviar los mensajes a terminales; de este modo podemos tener uno los terminales virtuales que muchos sistemas UNIX ofrecen en consola dedicado a listar los mensajes del sistema, que podrán consultados con solo cambiar a ese terminal: de de su ser # Enviamos todos los mensajes a tty12 (ALT+F12 en Linux) y # todos los mensajes críticos del núcleo a consola # *.* /dev/tty12 kern.crit /dev/console Una tubería con nombre: Algunas versiones de syslogd permiten enviar registros a ficheros de tipo pipe simplemente anteponiendo el símbolo ` | ' al nombre del archivo; dicho fichero ha de ser creado antes de iniciar el demonio syslogd, mediante ordenes como mkfifo o mknod. Esto es útil para debug y también para procesar los registros utilizando cualquier aplicación de UNIX, tal y como veremos al hablar de logs remotos cifrados. Por ejemplo, la siguiente línea de /etc/syslog.conf enviaría todos los mensajes de cualquier prioridad a uno de estos ficheros denominado /var/log/mififo: # Enviamos todos los mensajes a la tubería con nombre # /var/log/mififo # *.* |/var/log/mififo Una máquina remota: Se pueden enviar los mensajes del sistema a otra maquina, de manera que sean almacenados remotamente. Esto es útil si tenemos una maquina segura, en la que podemos confiar, conectada a la red, ya que de esta manera se guardaría allí una copia de los mensajes de nuestro sistema y no podrían ser modificados en caso de que alguien entrase en nuestra maquina. Esto es especialmente útil para detectar usuarios ocultos en nuestro sistema (usuarios maliciosos que han conseguido los suficientes privilegios para ocultar sus procesos o su conexión): # Enviamos los mensajes de prioridad warning y superiores al # fichero "syslog" y todos los mensajes (incluidos los # anteriores) a la maquina "secure.upv.es" # *.warn /usr/adm/syslog *.* @secure.machine.es Unos usuarios del sistema (si están conectados): Se especifica la lista de usuarios que deben recibir un tipo de mensajes simplemente escribiendo su login, separados por comas: # Enviamos los mensajes con la prioridad "alert" a root y toni # *.alert root, maripet Todos los usuarios que estén conectados: Los errores con una prioridad de emergencia se suelen enviar a todos los usuarios que estén conectados al sistema, de manera que se den cuenta de que algo va mal: # Mostramos los mensajes urgentes a todos los usuarios # conectados, mediante wall *.=emerg * 4.4.4 Algunos archivos de log En función de la configuración del sistema de auditoría de cada equipo UNIX los eventos que sucedan en la maquina se registraran en determinados ficheros; aunque podemos loggear en cualquier fichero (incluso a través de la red o en dispositivos, como veremos luego), existen ciertos archivos de registro habituales en los que se almacena información. A continuación comentamos los mas comunes y la información que almacenan. syslog El archivo syslog (guardado en /var/adm/ o /var/log/) es quizás el fichero de log mas importante del sistema; en él se guardan, en texto claro, mensajes relativos a la seguridad de la maquina, como los accesos o los intentos de acceso a ciertos servicios. Sin embargo, este fichero es escrito por syslogd, por lo que dependiendo de nuestro fichero de configuración encontraremos en el archivo una u otra información. Al estar guardado en formato texto, podemos visualizar su contenido con un simple cat: Mar 5 04:15:23 anita in.telnetd[11632]: connect from localhost Mar 5 06:16:52 anita rpcbind: connect from 127.0.0.1 to getport(R ) Mar 5 06:16:53 anita last message repeated 3 times Mar 5 06:35:08 anita rpcbind: connect from 127.0.0.1 to getport(R ) Mar 5 18:26:56 anita rpcbind: connect from 127.0.0.1 to getport(R ) Mar 5 18:28:47 anita last message repeated 1 time Mar 5 18:32:43 anita rpcbind: connect from 127.0.0.1 to getport(R ) Mar 6 02:30:26 anita rpcbind: connect from 127.0.0.1 to getport(R ) Mar 6 03:31:37 anita rpcbind: connect from 127.0.0.1 to getport(R ) Mar 6 11:07:04 anita in.telnetd[14847]: connect from rosita Mar 6 11:40:43 anita in.telnetd[14964]: connect from localhost messages En este archivo de texto se almacenan datos informativos de ciertos programas, mensajes de baja o media prioridad destinados mas a informar que a avisar de sucesos importantes, como información relativa al arranque de la maquina; no obstante, como sucedÍa con el fichero syslog, en función de /etc/syslog.conf podremos guardar todo tipo de datos. Para visualizar su contenido es suficiente una orden como cat o similares (sólo se muestra un pequeño fragmento, este archivo contiene 1392 líneas en este momento): Dec 2 16:21:21 localhost syslogd 1.3-3: restart. Dec 2 16:21:21 localhost syslog: syslogd startup succeeded Dec 2 16:21:21 localhost kernel: klogd 1.3-3, log source = /proc/kmsg started. Dec 2 16:21:21 localhost syslog: klogd startup succeeded Dec 2 16:21:21 localhost kernel: Inspecting /boot/System.map-2.2.14-5.0 Dec 2 16:21:21 localhost kernel: Loaded 7364 symbols from /boot/System.map-2.2.14-5.0. Dec 2 16:21:21 localhost kernel: Symbols match kernel version 2.2.14. Dec 2 16:21:21 localhost kernel: Loaded 189 symbols from 9 modules. Dec 2 16:21:21 localhost kernel: Linux version 2.2.14-5.0 ([email protected]) (gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)) #1 Tue Mar 7 21:07:39 EST 2000 Dec 2 16:21:21 localhost kernel: Detected 501145385 Hz processor. Dec 2 16:21:21 localhost kernel: Console: colour VGA+ 80x25 Dec 2 16:21:21 localhost kernel: Calibrating delay loop. 499.71 BogoMIPS Dec 2 16:21:21 localhost kernel: Memory: 127852k/131008k available (1060k kernel code, 416k reserved, 1616k data, 64k init, 0k bigmem) Dec 2 16:21:21 localhost kernel: Dentry hash table entries: 262144 (order 9, 2048k) Dec 2 16:21:21 localhost kernel: Buffer cache hash table entries: 131072 (order 7, 512k) Dec 2 16:21:21 localhost kernel: Page cache hash table entries: 32768 (order 5, 128k) Dec 2 16:21:21 localhost kernel: VFS: Diskquotas version dquot_6.4.0 initialized Dec 2 16:21:21 localhost kernel: CPU: Intel Pentium III (Katmai) stepping 03 Dec 2 16:21:21 localhost kernel: Enabling extended fast FPU save and restore...done. Dec 2 16:21:21 localhost kernel: Not enabling KNI unmasked exception support Dec 2 16:21:21 localhost kernel: Exception 19 error handler not integrated yet Dec 2 16:21:22 localhost kernel: Disabling CPUID Serial number...done. Dec 2 16:21:22 localhost kernel: Checking 386/387 coupling. OK, FPU using exception 16 error reporting. wtmp Este archivo es un fichero binario (no se puede leer su contenido directamente volcándolo con cat o similares) que almacena información relativa a cada conexión y desconexión al sistema. Podemos ver su contenido con ordenes como last: anita:/# last -10 toni pts/11 localhost Mon Mar 6 11:07 - 11:07 (00:00) toni pts/11 rosita Sun Mar 5 04:22 - 04:25 (00:03) ftp ftp andercheran.aiin Sun Mar 5 02:30 still logged in ftp ftp andercheran.aiin Sun Mar 5 00:28 02:30 (02:01) ftp ftp anita Thu Mar 2 03:02 - 00:28 (2+21:25) ftp ftp anita Thu Mar 2 03:01 - 03:02 (00:00) Los registros guardados en este archivo (y también en utmp) tienen el formato de la estructura utmp, que contiene información como el nombre de usuario, la línea por la que accede, el lugar desde donde lo hace y la hora de acceso. Algunas variantes de UNIX (como Solaris o IRIX) utilizan un fichero wtmp extendido denominado wtmpx, con campos adicionales que proporcionan mas información sobre cada conexión. utmp El archivo utmp es un fichero binario con información de cada usuario que está conectado en un momento dado; el programa /bin/login genera un registro en este fichero cuando un usuario conecta, mientras que init lo elimina cuando desconecta. Aunque habitualmente este archivo está situado en /var/adm/, junto a otros ficheros de log. Para visualizar el contenido de este archivo podemos utilizar ordenes como last (indicando el nombre de fichero mediante la opción -f), w o who: anita:/# who root console Mar 2 00:13 root pts/2 Mar 3 00:47 (UNIX) root pts/3 Mar 2 00:18 (UNIX) root pts/5 Mar 2 00:56 (UNIX) root pts/6 Mar 2 02:23 (UNIX:0.0) root pts/8 Mar 3 00:02 (UNIX:0.0) root pts/7 Mar 2 23:43 (UNIX:0.0) root pts/9 Mar 3 00:51 (UNIX) root pts/10 Mar 6 00:23 (UNIX) anita:/# Como sucedía con wtmp, algunos UNIX utilizan también una versión extendida de utmp (utmpx) con campos adicionales. lastlog El archivo lastlog es un fichero binario guardado generalmente en /var/adm/, y que contiene un registro para cada usuario con la fecha y hora de su ultima conexión; podemos visualizar estos datos para un usuario dado mediante la orden finger: anita:/# finger toni Login name: toni In real life: Toni at ANITA Directory: /export/home/toni Shell: /bin/sh Last login Mon Mar 6 11:07 on pts/11 from localhost No unread mail No Plan. anita:/# faillog Este fichero es equivalente al anterior, pero en lugar de guardar información sobre la fecha y hora del ultimo acceso al sistema lo hace del ultimo intento de acceso de cada usuario; una conexión es fallida si el usuario (o alguien en su lugar) teclea incorrectamente su contraseña. Esta información se muestra la siguiente vez que dicho usuario entra correctamente a la maquina: andercheran login: toni Password: Linux 2.0.33. 1 failure since last login. Last was 14:39:41 on ttyp9. Last login: Wed May 13 14:37:46 on ttyp9 from pleione.cc.upv.es. Andercheran:~$ loginlog Si en algunas versiones de UNIX (como Solaris) creamos el archivo /var/adm/loginlog (que originalmente no existe), se registraran en él los intentos fallidos de login, siempre y cuando se produzcan cinco o mas de ellos seguidos: anita:/# cat /var/adm/loginlog toni:/dev/pts/6:Thu Jan 6 07:02:53 2000 toni:/dev/pts/6:Thu Jan 6 07:03:00 2000 toni:/dev/pts/6:Thu Jan 6 07:03:08 2000 toni:/dev/pts/6:Thu Jan 6 07:03:37 2000 toni:/dev/pts/6:Thu Jan 6 07:03:44 2000 anita:/# btmp En algunas distribuciones de UNIX, como Linux o HP-UX, el fichero btmp se utiliza para registrar las conexiones fallidas al sistema, con un formato similar al que wtmp utiliza para las conexiones que han tenido éxito: andercheran:~# last -f /var/adm/btmp |head -7 pnvarro ttyq1 term104.aiind.up Wed Feb 9 16:27 - 15:38 (23:11) jomonra ttyq2 deportes.etsii.u Fri Feb 4 14:27 - 09:37 (9+19:09) PNAVARRO ttyq4 term69.aiind.upv Wed Feb 2 12:56 - 13:09 (20+00:12) panavarr ttyq2 term180.aiind.up Fri Jan 28 12:45 - 14:27 (7+01:42) barra ttyp0 dtra-51.ter.upv. Thu Jan 27 18:42 20:17 (01:34) andercheran:~# sulog Este es un fichero de texto donde se registran las ejecuciones de la orden su, indicando fecha, hora, usuario que lanza el programa y usuario cuya identidad adopta, terminal asociada y éxito ('+') o fracaso ('-') de la operación: anita:/# head -4 /var/adm/sulog SU 12/27 07:41 + console root-toni SU 12/28 23:42 - vt01 toni-root SU 12/28 23:43 + vt01 toni-root SU 12/29 01:09 + vt04 toni-root anita:/# debug En este archivo de texto se registra información de depuración (de debug) de los programas que se ejecutan en la maquina; esta información puede ser enviada por las propias aplicaciones o por el núcleo del sistema operativo: luisa:~# tail -8 /var/adm/debug Dec 17 18:51:50 luisa kernel: ISO9660 Extensions: RRIP_1991A Dec 18 08:15:32 luisa sshd[3951]: debug: sshd version 1.2.21 [i486unknown-linux] Dec 18 08:15:32 luisa sshd[3951]: debug: Initializing random number generator; seed file /etc/ssh_random_seed Dec 18 08:15:32 luisa sshd[3951]: debug: inetd sockets after dupping: 7, 8 Dec 18 08:15:34 luisa sshd[3951]: debug: Client protocol version 1.5; client software version 1.2.21 Dec 18 08:15:34 luisa sshd[3951]: debug: Calling cleanup 0x800cf90(0x0) Dec 18 16:33:59 luisa kernel: VFS: Disk change detected on device 02:00 Dec 18 23:41:12 luisa identd[2268]: Successful lookup: 1593 , 22 : toni.users luisa:~# 4.5.- Amenazas programadas 4.5.1.- Introducción En 1990 Barton P. Miller y un grupo de investigadores publicaron un artículo en el que se mostraba que demasiadas herramientas estándar (más del 25%) de UNIX fallaban ante elementos tan simples como una entrada anormal. Cinco años más tarde otro grupo de investigación, dirigido también por Barton P. Miller, realizó el estudio, lamentablemente no publicado; las conclusiones en este último estudio fueron sorprendentes: el sistema con las herramientas más estables era Slackware Linux, un UNIX gratuito y de código fuente libre que presentaba una tasa de fallos muy inferior al de sistemas comerciales como Solaris o IRIX. Era preocupante comprobar como la mayoría de problemas descubiertos en 1990 seguía presente en los sistemas UNIX estudiados. Aunque por fortuna la calidad del software ha mejorado mucho en los últimos años, y esa mejora lleva asociada una mejora en la robustez del código, los fallos y errores de diseño en aplicaciones o en el propio núcleo son una de las fuentes de amenazas a la seguridad de todo sistema informático. Pero no sólo los errores son problemáticos, sino que existen programas como los virus realizados en la mayoría de situaciones no para realizar tareas útiles sino para comprometer la seguridad de una máquina o de toda una red. Este tipo de programas solamente compromete la seguridad cuando afectan al administrador; si un virus infecta ficheros de un usuario, o si éste ejecuta un troyano, solo podrá perjudicarse a sí mismo: podrá borrar sus ficheros, enviar correo en su nombre o matar sus procesos, pero no hacer lo mismo con el resto de usuarios o el root. El problema para la seguridad viene cuando es el propio administrador quien utiliza programas contaminados por cualquier clase de fauna, y para evitar esto hay una medida de protección básica: la prevención. Es crucial que las actividades como administrador se reduzcan al mínimo, ejecutando como usuario normal las tareas que no requieran de privilegios. Cuando no quede más remedio que trabajar como root (por ejemplo a la hora de instalar software en el sistema), no hemos de ejecutar nada que no provenga de una fuente fiable, e incluso así tomar precauciones en caso de que el programa realice funciones mínimamente delicadas para el sistema operativo (por ejemplo, probarlo antes en una máquina de testeo, o en entornos cerrados con chroot()). Es muy normal, sobre todo entre administradores de Linux, el recomendar que no se ejecute nada sin haber leído previamente el código fuente, o al menos que dicho código esté disponible; esto, aunque es una solución perfecta al problema, es inaplicable en la mayoría de situaciones. Por un lado, no todas las aplicaciones o sistemas tienen su código abierto a sus usuarios, por lo que nos estaríamos restringiendo a utilizar programas generalmente no comerciales algo que quizás no depende de nosotros, como administradores. Por otro, resulta absurdo pensar que un administrador tenga el tiempo necesario para leer (y lo más importante, para comprobar) cada línea del código de todos los programas instalados en sus máquinas. 4.5.2.-Base Fiable de Cómputo La base fiable de cómputo (Trusted Computing Base, TCB) es una característica de ciertos UNIX que incrementa la seguridad del sistema marcando ciertos elementos del mismo como seguros . Aunque estos elementos son básicamente el hardware y ciertos ficheros, la parte software es mucho más importante para el administrador que la máquina física, por lo que aquí hablaremos principalmente de ella. Los ficheros pertenecientes a la base segura de cómputo, y la TCB en su conjunto, tratan de asegurar al administrador que está ejecutando el programa que desea y no otro que un intruso haya podido poner en su lugar (conteniendo, por ejemplo, un troyano). La TCB implementa la política de seguridad del sistema inspeccionando y vigilando las interacciones entre entidades (procesos) y objetos (principalmente ficheros); dicha política suele consistir en un control de accesos y en la reutilización de objetos (cómo debe inicializarse o desinstalarse un objeto antes de ser reasignado). Los ficheros con la marca de seguridad activada son generalmente el propio núcleo del sistema operativo y archivos que mantienen datos relevantes para la seguridad, contenidos en ciertos directorios como /tcb/ o /etc/auth/; cualquier fichero nuevo o que pertenezca a la TCB pero que haya sido modificado automáticamente tiene su marca desactivada. Puede ser activada o reactivada por el administrador (por ejemplo, en AIX con la orden tcbck -a), aunque en algunos sistemas para que un archivo pertenezca a la TCB tiene que haber sido creado con programas que ya pertenecían a la TCB. Con este mecanismo se trata de asegurar que nadie, y especialmente el root, va a ejecutar por accidente código peligroso: si el administrador ha de ejecutar tareas sensibles de cara a la seguridad, puede arrancar un intérprete de comandos seguro (perteneciente a la TCB) que sólo le permitirá ejecutar programas que estén en la base. La comunicación entre la base fiable de cómputo y el usuario se ha de realizar a través de lo que se denomina la ruta de comunicación fiable (Trusted Communication Path, TCP), ruta que se ha de invocar mediante una combinación de teclas (por ejemplo, Ctrl-X Ctrl-R en AIX) denominada SAK (Secure Attention Key) siempre que el usuario deba introducir datos que no deban ser comprometidos, como una clave. Tras invocar a la ruta de comunicación fiable mediante la combinación de teclas correspondiente el sistema operativo se ha de asegurar de que los programas no fiables (los no incluidos en la TCB) no puedan acceder a la terminal desde la que se ha introducido el SAK; una vez conseguido esto generalmente a partir de init se solicitará al usuario en la terminal su login y su password, y si ambos son correctos se lanzará un shell fiable (tsh), que sólo ejecutará programas miembros de la TCB (algo que es muy útil por ejemplo para establecer un entorno seguro para la administración del sistema, si el usuario es el root). Desde el punto de vista del usuario, tras pulsar el SAK lo único que aparecerá será un prompt solicitando el login y la clave; si en lugar de esto aparece el símbolo de tsh, significa que alguien ha intentado robar nuestra contraseña: deberemos averiguar quién está haciendo uso de esa terminal (por ejemplo mediante who) y notificarlo al administrador o tomar las medidas oportunas si ese administrador somos nosotros . A pesar de la utilidad de la TCB, es recomendable recordar que un fichero incluido en ella, con la marca activada, no siempre es garantía de seguridad; como todos los mecanismos existentes, la base fiable de cómputo está pensada para utilizarse junto a otros mecanismos, y no en lugar de ellos. 4.5.3.-Tipos de amenazas programadas Virus Un virus es una secuencia de código que se inserta en un fichero ejecutable, denominado host, de forma que al ejecutar el programa, también se ejecuta el virus. Generalmente esta ejecución implica la copia del código del virus, en otros programas. El virus necesita un programa donde insertarse para ejecutarse, por lo que no puede ser considerado como un programa o proceso independiente. Durante años, se ha discutido sobre la existencia de virus en el entorno UNIX. Realmente, existen virus que infectan tanto binarios ELF como shellscripts. Sin embargo la existencia de virus no tiene mucho interés en sistemas UNIX, ya que generalmente sus efectos no suelen ser muy perjudiciales en los sistemas UNIX de hoy en día. Gusanos Un gusano es un programa capaz de viajar por sí mismo a través de redes de ordenadores para realizar cualquier actividad una vez alcanzada la máquina. Aunque esta actividad no tiene por qué entrañar peligro, un gusano puede instalar en el sistema alcanzado un virus, atacar este sistema como haría un intruso o simplemente consumir excesivas cantidades de ancho de banda en la red afectada. Conejos Los conejos o bacterias son programas que de forma directa no dañan al sistema, sino que se limitan a reproducirse, generalmente de forma exponencial, hasta que la cantidad de recursos consumidos (procesador, memoria, disco,...) se convierte en una negación de servicio para el sistema afectado. La mejor forma de prevenir ataques de conejos ( o simples errores en los programas, que hagan que estos consuman excesivos recursos) es utilizar las facilidades de los núcleos de cualquier UNIX moderno ofrecen para limitar los recursos que un determinado proceso o usuario puede llegar a consumir. Caballos de Troya Un caballo de Troya actual (al igual que el de la mitología griega) es un programa que aparentemente realiza una función útil para quién lo ejecuta, pero que en realidad, o aparte, realiza una función que el usuario desconoce, generalmente dañina. La forma más fácil de descubrir caballos de Troya (aparte de sufrir sus efectos una vez ejecutado) es comparar los ficheros bajo sospecha con una copia de los originales. También es recomendable realizar resúmenes MD5 de nuestros programas y compararlos con los resúmenes originales. Applets hostiles En los últimos años, con la proliferación de la web, Java y Javascript, han aparecido los denominados applets hostiles, que al ser descargados intentan monopolizar o explotar los recursos del sistema de forma inapropiada. Esto incluye desde ataques clásicos como negación de servicio o ejecución remota de programas en la máquina cliente hasta amenazas mucho más elaboradas, como difusión de virus, ruptura lógica de cortafuegos o uso de recursos remotos para grandes cálculos científicos. La propia Sun Microsystems ha reconocido el problema y está trabajando para minimizar los potenciales efectos de estos applets. No obstante, aunque se solucionen los problemas de seguridad del código, es probable que se puedan seguir usando applets como una forma de ataque a los sistemas: mientras que estos programas puedan realizar conexiones por red, no habrán desaparecido los problemas. Bombas lógicas Las bombas lógicas funcionan de manera similar a los troyanos, la diferencia entre ellos es que así como un troyano se ejecuta cada vez que se ejecuta el programa que lo contiene (o al que sustituye), una bomba lógica sólo se activa bajo ciertas condiciones, como una determinada fecha, la existencia de un cierto fichero, o el alcance de cierto número de ejecuciones. De esta manera, una bomba lógica puede permanecer en el sistema inactiva y sin ser detectada durante mucho tiempo y sin que nadie note nada anormal hasta que el daño producido por la bomba ya esté hecho. Puertas traseras Las puertas traseras son trozos de código en un programa que permiten a quién conoce su funcionamiento saltarse los métodos usuales de autentificación para realizar cierta tarea. Los programadores suelen usarlas para probar su código durante el desarrollo, pero pueden mantenerlas en el programa final, ya sea deliberada o involuntariamente. Pensemos por ejemplo en un programa que solicita cinco claves para realizar cierta tarea, es muy probable que el programador instale una puerta trasera una vez que ha comprobado el funcionamiento de dichas claves. A parte de puertas traseras en los programas, también existen otros tipos de puertas traseras. Son las que instalan los intrusos para garantizarse el futuro acceso al sistema, como añadir un usuario con UID 0, añadir un nuevo servicio en un puerto no utilizado o incluso utilizar cron para reinstalar periódicamente dichas puertas en caso de que hayan sido eliminadas. 4.5.4.-Programación segura Dado el gran impacto que pueden tener sobre la seguridad los errores de programación en las aplicaciones, es de vital importancia utilizar técnicas de programación seguras. 4.5.4.1.-Buffer overflow Seguramente uno de los errores más comunes, y sin duda el más conocido y utilizado es el stack smashing o desbordamiento de pila, también conocido por buffer overflow. Aunque el gusano de Morris (1988) ya lo utilizaba, no fue hasta 1997 cuando este fallo se hizo realmente popular. Aunque cada vez los programas son más seguros, especialmente los programas SUID, es casi seguro que un potencial atacante que acceda a nuestro sistema va a intentar si no lo ha hecho ya conseguir privilegios de administrador a través de un buffer overflow. La idea del stack smashing es sencilla: en algunas implementaciones de C es posible corromper la pila de ejecución de un programa escribiendo más allá de los límites de un array declarado auto en una función; esto puede causar que la dirección de retorno de dicha función sea una dirección aleatoria. Esto, unido a permisos de los ficheros ejecutables en UNIX (principalmente a los bits de SUID y SGID), hace que el sistema operativo pueda otorgar acceso root a usuarios sin privilegios. Por ejemplo, imaginemos una función que trate de copiar con strcpy() un array de 200 caracteres en uno de 20: al ejecutar el programa, se generará una violación de segmento (y por tanto el clásico core dump al que los usuarios de UNIX estamos acostumbrados). Se ha producido una sobreescritura de la dirección de retorno de la función; si logramos que esta sobreescritura no sea aleatoria sino que apunte a un código concreto (habitualmente el código de un shell), dicho código se va a ejecutar. El problema reside en los programas SUID y SGID; recordemos que cuando alguien los ejecuta, está trabajando con los privilegios de quien los creó, y todo lo que ejecute lo hace con esos privilegios. . . incluido el código que se ha insertado en la dirección de retorno de nuestra función problemática. Si, como hemos dicho, este código es el de un intérprete de comandos y el fichero pertenece al administrador, el atacante consigue ejecutar un shell con privilegios de root. Para minimizar los efectos del desbordamiento de pila, los administradores han de mantener al mínimo el número de ficheros SUID y SGID en sus sistemas y los programadores tienen que esforzarse en generar código con menos puntos de desbordamiento. 4.5.4.2.- Recomendaciones para una programación segura Después de analizar los problemas que un código mal diseñado puede causar, vamos a citar algunas recomendaciones para la elaboración de programas seguros bajo UNIX. Vamos a hablar de programación en C, obviamente por ser el lenguaje más utilizado en UNIX. El principal problema de la programación en UNIX lo constituyen los programas SUID y SGID, si un programa sin este bit activo tiene un fallo, lo normal es que ese fallo solamente afecte a quien lo ejecuta. Al tratarse de un error de programación, algo no intencionado, su primera consecuencia será el mal funcionamiento de ese programa. Esto cambia cuando el programa es SUID o SGID: en este caso, el error puede comprometer tanto a quien lo ejecuta como a su propietario, y como ese propietario es por norma general root, automáticamente se compromete a todo el sistema. Para la codificación segura de este tipo de programas, se proporcionan unas líneas básicas: Máximas restricciones a la hora de elegir el UID y el GID: Una medida de seguridad básica que todo administrador de sistemas UNIX ha de seguir es realizar todas las tareas con el mínimo privilegio que estas requieran; así, a nadie se le ocurre conectarse al IRC o aprender a manejar una aplicación como root. Esto también se aplica a la hora de programar: cuando se crea un programa SUID o SGID se le ha de asignar tanto el UID como el GID menos peligroso para el sistema. Reset de los UIDs y GIDs efectivos antes de llamar a exec(): Uno de los grandes problemas de los programas SUID es la ejecución de otros programas. Por ejemplo, si el usuario introduce ciertos datos desde teclado, datos que se han de pasar como argumento a otra aplicación, nada nos asegura que esos datos sean correctos. Por tanto, es aconsejable restablecer el UID y el GID efectivos a los del usuario antes de invocar a exec(), de forma que cualquier ejecución inesperada se realice con el mínimo privilegio necesario; esto es aplicable a funciones que indirectamente realicen el exec(), como system() o popen(). Es necesario cerrar todos los descriptores de fichero, excepto los estrictamente necesarios, antes de llamar a exec(): Los descriptores de ficheros son un parámetro que los procesos UNIX heredan de sus padres; de esta forma, si un programa SUID está leyendo un archivo, cualquier proceso hijo tendrá acceso a ese archivo a no ser que explícitamente se cierre su descriptor antes de ejecutar el exec(). La forma más fácil de prevenir este problema es activando un flag que indique al sistema que ha de cerrar cierto descriptor cada vez que se invoque a exec(). Esto se consigue mediante las llamadas fcntl() e ioctl(). Hay que asegurarse de que chroot() realmente restringe: Los enlaces duros entre directorios son algo que el núcleo de muchos sistemas UNIX no permiten debido a que genera bucles en el sistema de ficheros, algo que crea problemas a determinadas aplicaciones. Por ejemplo, Linux no permite crear estos enlaces, pero Solaris o Minix sí. En estos últimos, en los clones de UNIX que permiten hard links entre directorios, la llamada chroot() puede perder su funcionalidad: estos enlaces pueden seguirse aunque no se limiten al entorno con el directorio raíz restringido. Es necesario asegurarse de que no hay directorios enlazados a ninguno de los contenidos en el entorno chroot() (podemos verlo con la opción `-l' de la orden ls, que muestra el número de enlaces de cada archivo). Comprobaciones del entorno en que se ejecutará el programa: En UNIX todo proceso hereda una serie de variables de sus progenitores, como el umask, los descriptores de ficheros, o ciertas variables de entorno ($PATH, $IFS. . . ). Para una ejecución segura, es necesario controlar todos y cada uno de estos elementos que afectan al entorno de un proceso. Especialmente críticas son las funciones que dependen del shell para ejecutar un programa, como system() o execvp(): en estos casos es muy difícil asegurar que el shell va a ejecutar la tarea prevista y no otra. Nunca hacer shellscripts SUID: Aunque en muchos sistemas UNIX la activación del bit setuid en shellscripts no tiene ningún efecto, muchos otros aún permiten que los usuarios especialmente el root creen procesos interpretados y SUID. La potencia de los intérpretes de órdenes de UNIX hace casi imposible controlar que estos programas no realicen acciones no deseadas, violando la seguridad del sistema, por lo que bajo ningún concepto se ha de utilizar un proceso por lotes para realizar acciones privilegiadas con SUID. No utilizar creat() para bloquear: Una forma de crear un fichero de bloqueo es invocar a creat() con un modo que no permita la escritura del archivo (habitualmente el 000), de forma que si otro usuario tratara de hacer lo mismo, su llamada a creat() fallaría. Esto, que a primera vista parece completamente válido, no lo es: en cualquier sistema UNIX, la protección que proporcionan los permisos de un fichero sólo es aplicable si quien trata de acceder a él no es el root. Si esto es así, es decir, si el UID efectivo del usuario que está accediendo al archivo es 0, los permisos son ignorados completamente y el acceso está permitido. De esta forma, el root puede sobreescribir archivos sin que le importen sus bits rwx, lo que implica que si uno de los procesos que compiten por el recurso bloqueado es SUID a nombre del administrador, el esquema de bloqueo anterior se viene abajo. Para poder bloquear recursos en un programa SUID se utiliza la llamada link(), ya que si se intenta crear un enlace a un fichero que ya existe link() falla aunque el proceso que lo invoque sea propiedad del root (y aunque el fichero sobre el que se realice no le pertenezca). También es posible utilizar la llamada al sistema flock() de algunos UNIX, aunque es menos recomendable por motivos de portabilidad entre clones. Capturar todas las señales: Un problema que puede comprometer la seguridad del sistema UNIX es el volcado de la imagen en memoria de un proceso cuando éste recibe ciertas señales (el clásico core dump). Esto puede provocar el volcado de información sensible que el programa estaba leyendo: por ejemplo, en versiones del programa login de algunos UNIX antiguos, se podía leer parte de /etc/shadow enviando al proceso la señal sigterm y consultando el fichero de volcado. No obstante, este problema no resulta tan grave como otro también relacionado con los core dump: cuando un programa SUID vuelca su imagen el fichero resultante tiene el mismo UID que el UID real del proceso. Esto puede permitir a un usuario obtener un fichero con permiso de escritura para todo el mundo pero que pertenezca a otro usuario (por ejemplo, el root): evidentemente esto es muy perjudicial, por lo que parece claro que en un programa SUID necesitamos capturar todas las señales que UNIX nos permita (recordemos que sigkill no puede capturarse ni ignorarse, por norma general). Hay que asegurarse de que las verificaciones realmente verifican: Otra norma a la hora de escribir aplicaciones SUID es la desconfianza de cualquier elemento externo al programa. Hemos de verificar siempre que las entradas (teclado, ficheros. . . ) son correctas, no sólo en su formato sino también en su origen. Cuidado con las recuperaciones y detecciones de errores:Ante cualquier situación inesperada y por lo general, poco habitual, incluso forzada por un atacante un programa SUID debe detenerse sin más; nada de intentar recuperarse del error: detenerse sin más. Esto, que quizás rompe muchos de los esquemas clásicos sobre programación robusta, tiene una explicación sencilla: cuando un programa detecta una situación inesperada, a menudo el programador asume condiciones sobre el error (o sobre su causa) que no tienen por qué cumplirse, lo que suele desembocar en un problema más grave que la propia situación inesperada. Para cada posible problema que un programa encuentre (entradas muy largas, caracteres erróneos o de control, formatos de datos erróneos. . . ) es necesario que el programador se plantee qué es lo que su código debe hacer, y ante la mínima duda detener el programa. Cuidado con las operaciones de entrada/salida: La entrada/salida entre el proceso y el resto del sistema constituye otro de los problemas comunes en programas SUID, especialmente a la hora de trabajar con ficheros; las condiciones de carrera aquí son algo demasiado frecuente: el ejemplo clásico se produce cuando un programa SUID ha de escribir en un archivo propiedad del usuario que ejecuta el programa (no de su propietario). En esta situación lo habitual es que el proceso cree el fichero, realice sobre él un chown() al UID real y al GID real del proceso (es decir, a los identificadores de quién está ejecutando el programa), y posteriormente escriba en el archivo. Pero, ¿qué sucede si el programa se interrumpe tras realizar el open() pero antes de invocar a fchown(), y además el umask del usuario es 0? El proceso habrá dejado un archivo que pertenece al propietario del programa (generalmente el root) y que tiene permiso de escritura para todo el mundo. La forma más efectiva de solucionar el problema consiste en que el proceso engendre un hijo mediante fork(), hijo que asignará a sus UID efectivo y GID efectivo los valores de su UID real y GID real (los identificadores del usuario que lo ha ejecutado, no de su propietario). El padre podrá enviar datos a su hijo mediante pipe(), datos que el hijo escribirá en el fichero correspondiente: así el fichero en ningún momento tendrá por qué pertenecer al usuario propietario del programa, con lo que evitamos la condición de carrera expuesta anteriormente. 4.5.4.3.- Utilización segura de funciones de biblioteca Sin embargo, un correcto estilo de programación no siempre es la solución a los problemas de seguridad del código. Existen llamadas a sistema o funciones de librería que son un clásico a la hora de hablar de bugs en nuestro software. Como norma, tras cualquier llamada se ha de comprobar su valor de retorno y manejar los posibles errores que tenga asociados, con la evidente excepción de las llamadas que están diseñadas para sobreescribir el espacio de memoria de un proceso (la familia exec() por ejemplo) o las que hacen que el programa finalice (típicamente, exit()) . Algunas de las llamadas consideradas más peligrosas (bien porque no realizan las comprobaciones necesarias, bien porque pueden recibir datos del usuario) son las siguientes: system(): Esta es la llamada que cualquier programa SUID debe evitar a toda costa. Si aparece en un código destinado a ejecutarse con privilegios, significa casi con toda certeza un grave problema de seguridad. En algunas ocasiones su peligrosidad es obvia (por ejemplo si leemos datos tecleados por el usuario y a continuación hacemos un system() de esos datos, ese usuario no tendría más que teclear /bin/bash para conseguir los privilegios del propietario del programa), pero en otras no lo es tanto. exec(), popen(): Similares a la anterior; es preferible utilizar execv() o execl(), pero si han de recibir parámetros del usuario sigue siendo necesaria una estricta comprobación de los mismos. setuid(), setgid(). . . :Los programas de usuario no deberían utilizar estas llamadas, ya que no han de tener privilegios innecesarios. strcpy(), strcat(), sprintf(), vsprintf(). . . : Estas funciones no comprueban la longitud de las cadenas con las que trabajan, por lo que son una gran fuente de buffer overflows. Se han de sustituir por llamadas equivalentes que sí realicen comprobación de límites (strncpy(), strncat(). . . ) y, si no es posible, realizar dichas comprobaciones manualmente. getenv(): Otra excelente fuente de desbordamientos de buffer; además, el uso que hagamos de la información leída puede ser peligroso, ya que recordemos que es el usuario el que generalmente puede modificar el valor de las variables de entorno. gets(), scanf(), fscanf(), getpass(), realpath(), getopt(). . . : Estas funciones no realizan las comprobaciones adecuadas de los datos introducidos, por lo que pueden desbordar en algunos casos el buffer destino o un buffer estático interno al sistema. Es preferible el uso de read() o fgets() siempre que sea posible (incluso para leer una contraseña, haciendo por supuesto que no se escriba en pantalla), y si no lo es al menos realizar manualmente comprobaciones de longitud de los datos leídos. gethostbyname(), gethostbyaddr(): Seguramente ver las amenazas que provienen del uso de estas llamadas no es tan inmediato como ver las del resto; generalmente hablamos de desbordamiento de buffers, de comprobaciones de límites de datos introducidos por el usuario. . . pero no nos paramos a pensar en datos que un atacante no introduce directamente desde teclado o desde un archivo, pero cuyo valor puede forzar incluso desde sistemas que ni siquiera son el nuestro. syslog(): Hemos de tener la precaución de utilizar una versión de esta función de librería que compruebe la longitud de sus argumentos; si no lo hacemos y esa longitud sobrepasa un cierto límite (generalmente, 1024 bytes) podemos causar un desbordamiento en los buffers de nuestro sistema de log, dejándolo inutilizable. realloc(): Ningún programa privilegiado o no que maneje datos sensibles (por ejemplo, contraseñas, correo electrónico. . . y especialmente aplicaciones criptográficas) debe utilizar esta llamada. realloc() se suele utilizar para aumentar dinámicamente la cantidad de memoria reservada para un puntero. Lo habitual es que la nueva zona de memoria sea contigua a la que ya estaba reservada, pero si esto no es posible realloc() copia la zona antigua a una nueva ubicación donde pueda añadirle el espacio especificado. ¿Cuál es el problema? La zona de memoria antigua se libera (perdemos el puntero a ella) pero no se pone a cero, con lo que sus contenidos permanecen inalterados hasta que un nuevo proceso reserva esa zona; accediendo a bajo nivel a la memoria (por ejemplo, leyendo /proc/kcore o /dev/kmem) sería posible para un atacante tener acceso a esa información. De cualquier forma, si vamos a liberar una zona en la que está almacenada información sensible, lo mejor en cualquier caso es ponerla a cero manualmente, por ejemplo mediante bzero() o memset(). open(): El sistema de ficheros puede modificarse durante la ejecución de un programa de formas que en ocasiones ni siquiera imaginamos; por ejemplo, en UNIX se ha de evitar escribir siguiendo enlaces de archivos inesperados (un archivo que cambia entre una llamada a lstat() para comprobar si existe y una llamada a open() para abrirlo en caso positivo). No obstante, no hay ninguna forma de realizar esta operación atómicamente sin llegar a mecanismos de entrada/salida de muy bajo nivel. Bibliografía A. Ribagorda, A. Calvo, M. Angel Gallardo. “Seguridad en UNIX. Sistemas abiertos e Internet.” Paraninfo, 1996 S. Garfinkel, G. Spafford “Seguridad práctica en UNIX e Internet.””, 2ª edición Mc Graw Hill, 1999 A. Villalón Huerta “Seguridad en UNIX y redes.”, versión 1.2 Open Publication License, http://www.opencontent.org/openpub/, 2000 A. Silberschatz, P. Galvin "Sistemas Operativos. Conceptos Fundamentales.”, 5ª edición Addison-Wesley-Longman, 1999 G. Mourani “Securing and Optimizing Linux. RedHat Edition -A Hands on Guide” Open Network Architecture http://www.openna.com, 2000 K. Seifried “Guía de Seguridad del Administrador de Linux” http://segurinet.com/gsal, 1999 L. Wirzenius ”The Linux System Administrators' Guide”, versión 0.6.2 Linux Documentation Project, http://sunsite.unc.edu/LDP/, 1998 W. Tarreau “Security under Linux : the Buffer Overflow Problem” linux-stack-overflow.tgz Pedro Gómez Ochoa Jacobo Chamorro