El Gusano de Internet de 1988
El siguiente documento narra la historia del Gusano de Internet de 1988 y cómo paralizó internet. No lo escribí, pero es difícil encontrarlo en la red hoy en día, así que lo ofrezco aquí bajo la teoría de que quienes no aprenden de la historia están condenados a repetirla.
Recuerdo cuando ocurrió. Fue un gran acontecimiento para los informáticos como yo, pero en 1988 Internet era desconocida incluso para los periodistas más sofisticados, y la World Wide Web aún no se había inventado. Recuerdo que el noticiero vespertino de la NBC dedicó menos de 30 segundos al tema. Si hoy ocurriera una interrupción de Internet equivalente en su gravedad, el presidente de Estados Unidos probablemente daría una conferencia de prensa para calmar a la nación.
Cabe mencionar que la persona que creó el Gusano es el hijo del ex Científico Jefe de la Agencia de Seguridad Nacional de Estados Unidos.
1. Introducción
Existe una delgada línea entre ayudar a los administradores a proteger sus sistemas y proporcionar un recetario a los delincuentes. [Grampp y Morris, "Seguridad del sistema operativo UNIX"]
El 3 de noviembre de 1988 se conoce ya como "el Jueves Negro". Administradores de sistemas de todo el país acudieron a trabajar ese día y descubrieron que sus redes informáticas estaban sometidas a una enormes esfuerzos telemáticos. En caso de lograr iniciar sesión y generar un listado del estado del sistema, apreciaban lo que parecían ser docenas o cientos de procesos de "shell" (intérprete de comandos). Si intentaban eliminar los procesos, advertían la aparición de nuevos procesos con mayor rapidez de la necesaria para poder eliminarlos. Reiniciar el ordenador parecía no surtir efecto alguno: a los pocos minutos de arrancar, la máquina se veía nuevamente sobrecargada por estos misteriosos procesos.
Los sistemas afectados habían sido invadidos por un gusano informático. Un gusano es un programa que se propaga por una red informática, utilizando los recursos de una máquina para atacar a otras. (Un gusano no es exactamente lo mismo que un virus, que es un fragmento de programa que se inserta en otros programas). El gusano se había aprovechado de fallos de seguridad en sistemas UNIX BSD 4.2 o 4.3, o derivados de éstos, como SunOS. Estos fallos le permitieron conectarse a máquinas a través de una red, eludir sus autenticaciones de inicio de sesión, copiarse y luego proceder a atacar aún más máquinas. La enorme sobrecarga de sistemas era producida por la multitud de gusanos intentando propagar la epidemia telemática.
Aunque se había especulado mucho sobre la posibilidad de un ataque, previamente Internet nunca see había visto atacada de una manera tal. A diferencia de los virus informático - que son una grave plaga en el mundo de las computadoras - la mayoría de los administradores de sistemas desconocían el concepto de gusanos, y tardó un tiempo en determinar qué era lo que estaba sucediendo y cómo debía ser abordado. Este trabajo pretende informar a la gente exactamente qué sucedió y cómo se produjo, para que estén mejor preparados la próxima vez. Se examinará en detalle el comportamiento del gusano, tanto para mostrar exactamente qué hace y qué no hace, como para demostrar los peligros de gusanos futuros.
El epígrafe anterior resulta irónico, puesto que el autor del gusano utilizó la información de dicho artículo para atacar sistemas. Dado que la información es ahora bien conocida, gracias a que miles de ordenadores tienen copias del gusano, parece improbable que este artículo pueda causar daños similares, pero es sin duda una idea preocupante. A continuación se ofrecerán opiniones sobre este y otros temas.
2. Cronología
Recuerde que, al conectarse a otra computadora, se conecta a todas las computadoras a las que esa computadora se ha conectado. [Dennis Miller, en Saturday Night Live de la NBC]
Este es el resumen de un mensaje que recibí: Lo siento. [Andy Sudduth, en una publicación anónima en la lista TCP-IP en nombre del autor del gusano, 3/11/88]
Muchos detalles de la cronología del ataque aún no están disponibles. La siguiente lista representa las fechas y horas que conocemos actualmente. Todas las horas se muestran en la hora estándar del Pacífico para mayor comodidad.
2/11: 18:00 (aprox.) Esta fecha y hora se observaron en los archivos del gusano encontrados en prep.ai.mit.edu, una máquina VAX 11/750 en el Laboratorio de Inteligencia Artificial del MIT. Los archivos se eliminaron posteriormente y se perdió la hora exacta. El registro del sistema en prep estuvo interrumpido durante dos semanas. El sistema no ejecuta contabilidad y los discos no tienen copias de seguridad en cinta: se trató de un objetivo perfecto. Se reportó la actividad de varios usuarios "turistas" (personas con cuentas públicas) esa noche. Estos usuarios habrían aparecido en el registro de sesión, pero véase más abajo.
11/2: 18:24 Primera infección conocida en la Costa Oeste: la máquina rand.org en Rand Corp., en Santa Mónica.
11/2: 19:04 csgw.berkeley.edu se encuentra infectada. Esta máquina es una importante puerta de enlace de red en UC Berkeley. Mike Karels y Phil Lapsley descubren la infección poco después.
11/2: 19:54 mimsy.umd.edu es atacado a través de su servidor finger. Esta máquina se encuentra en el Departamento de Ciencias de la Computación de la Universidad de Maryland en College Park.
11/2: 20:00 (aprox.) Suns en el Laboratorio de IA del MIT son atacados.
11/2: 20:28 Primer ataque de sendmail a mimsy.
11/2: 20:40 El personal de Berkeley descubre los ataques de sendmail y rsh, detecta peculiaridades de telnet y finger, y comienza a desactivar estos servicios.
11/2: 20:49 cs.utah.edu ha sido infectada. Esta VAX 8600 es el mainframe del Departamento de Informática de la Universidad de Utah. Las siguientes entradas siguen eventos documentados en Utah y son representativas de otras infecciones en todo el país.
11/2: 21:09 Primer ataque de sendmail a cs.utah.edu.
11/2: 21:21 El promedio de carga en cs.utah.edu alcanza 5. El "promedio de carga" es un valor generado por el programa de sistema "top" que representa el número promedio de trabajos en la cola de ejecución durante el último minuto. Una carga de 5 en un VAX 8600 reduce notablemente los tiempos de respuesta, mientras que una carga superior a 20 supone una degradación drástica. A las 21:00, la carga suele estar entre 0,5 y 2.
11/2: 21:41 El promedio de carga en cs.utah.edu alcanza 7.
11/2: 22:01 El promedio de carga en cs.utah.edu alcanza 16.
11/2: 22:06 Se alcanza el número máximo de procesos ejecutables distintos (100) en cs.utah.edu; el sistema queda inutilizable.
11/2: 22:20 Jeff Forys, de Utah, elimina gusanos en cs.utah.edu. Los clústeres de estaciones de trabajo Sun de Utah se ven infectados.
11/2: 22:41 Una reinfestación provoca que el promedio de carga alcance 27 en cs.utah.edu.
11/2: 22:49 Forys cierra cs.utah.edu.
11/3: 23:21 Una reinfestación provoca que la carga promedio alcance 37 en cs.utah.edu, a pesar de los continuos esfuerzos de Forys por eliminar los gusanos.
11/2: 23:28 Peter Yee, del Centro de Investigación Ames de la NASA, publica una advertencia en la lista de correo TCP-IP: "Actualmente estamos siendo atacados por un VIRUS de Internet. Ha afectado a UC Berkeley, UC San Diego, Lawrence Livermore, Stanford y NASA Ames". Sugiere desactivar los servicios Telnet, FTP, Finger, RSH y SMTP. No menciona rexec. Yee se encuentra en Berkeley trabajando con Keith Bostic [programador del editor nvi], Mike Karels y Phil Lapsley.
11/3: 00:34 A petición de otra persona, Andy Sudduth, de Harvard, publica anónimamente una advertencia en la lista TCP-IP: "Podría haber un virus suelto en internet". Este es el primer mensaje que describe (brevemente) cómo funciona el ataque a través de finger, explica cómo derrotar el ataque a través de SMTP recompilando sendmail y menciona explícitamente el ataque rexec. Desafortunadamente, el mensaje de Sudduth queda bloqueado en relay.cs.net mientras esa puerta de enlace permanece cerrada para combatir el gusano, y no se entrega durante casi dos días. Sudduth reconocerá la autoría del mensaje en un mensaje posterior a TCP-IP el 5 de noviembre.
11/3: 02:54 Keith Bostic publica un parche para sendmail en la lista de correo del grupo de noticias comp.bugs.4bsd.ucb-fixes y a la lista de correo TCP-IP. Estas correcciones (y las posteriores) también se envían directamente a importantes administradores de sistemas de todo el país.
11/3: temprano en la mañana El registro de sesión wtmp desaparece misteriosamente de prep.ai.mit.edu.
11/3: 05:07 Edward Wang, de Berkeley, descubre y reporta el ataque a través de finger, pero su mensaje no llega a oídos de Mike Karels hasta 12 horas después.
11/3: 09:00 Comienza el Taller Unix anual de Berkeley en la Universidad de California en Berkeley. Unos 40 importantes administradores de sistemas y patrocinadores se encuentran en la ciudad para asistir a él, en el justo momento que el desastre estalla en casa. Varias personas que habían planeado volar el jueves por la mañana se ven atrapadas por la crisis. Keith Bostic pasa gran parte del día al teléfono en las oficinas del Grupo de Investigación de Sistemas Informáticos, respondiendo llamadas en pánico de administradores de sistemas de todo el país.
11/3: 15:00 (aprox.) El equipo de MIT Athena llama a Berkeley con un ejemplo de cómo funciona el fallo del servidor finger.
11/3: 16:26 Dave Pare llega a las oficinas del CSRG de Berkeley; comienzan poco después el desensamblado y la descompilación utilizando las herramientas especiales de Pare.
11/3: 18:00 (aprox.) El grupo de Berkeley pide calzones para comer. La gente entra y sale; las oficinas están abarrotadas, hay mucha emoción. Se está trabajando en paralelo en MIT Athena; ambos grupos intercambian código fuente.
11/3: 19:18 Keith Bostic publica un parche para el servidor finger.
11/4: 06:00 Los miembros del equipo de Berkeley, con el gusano casi completamente desensamblado y prácticamente descompilado, finalmente se van a dormir un par de horas antes de regresar al taller.
11/4: 12:36 Theodore Ts'o, del Proyecto Athena en el MIT, anuncia públicamente que el MIT y Berkeley han desensamblado completamente el gusano.
11/4: 17:00 (aprox.) Se realiza una breve presentación sobre el gusano al final del Taller UNIX de Berkeley.
11/8: Reunión del Centro Nacional de Seguridad Informática para debatir el gusano. Asisten unos 50 asistentes.
11/11: 00:38 Se instala en Berkeley el código fuente del gusano, completamente descompilado y comentado.
3. Resumen
¿Qué hacía exactamente el gusano para causar una epidemia? El gusano consiste en un programa de rutina de arranque de 99 líneas escrito en lenguaje C, además de un gran archivo objeto reubicable disponible en versiones para VAX y para Sun-3. La evidencia interna demostró que el archivo objeto se generó a partir de código fuente escrito en C, por lo que fue natural descompilar el lenguaje máquina binario a C; ahora tenemos más de 3200 líneas de código C comentado, recompilable y prácticamente completo. Comenzaremos el recorrido por el gusano con un breve resumen de sus objetivos básicos, seguido de un análisis en profundidad de sus diversos comportamientos, según se revela en la descompilación.
Las actividades del gusano se dividen en ataque y defensa. El ataque consiste en localizar hosts (y cuentas) para penetrar y luego explotar vulnerabilidades de seguridad en sistemas remotos para enviar una copia del gusano y ejecutarlo. El gusano obtiene direcciones de host examinando las tablas del sistema /etc/hosts.equiv y /.rhosts, archivos de usuario como .forward y .rhosts, información de enrutamiento dinámico generada por el programa netstat y, finalmente, direcciones de host generadas aleatoriamente en redes locales. Las clasifica por orden de preferencia, probando primero un archivo como /etc/hosts.equiv porque contiene nombres de máquinas locales que probablemente permitan conexiones no autenticadas. La penetración en un sistema remoto se puede lograr de tres maneras. a) El gusano puede aprovechar un bug en el servidor finger que le permite descargar código fuente en lugar de una solicitud finger, y engañar al servidor para que lo ejecute. b) El gusano puede usar una "trampa" en el servicio de correo SMTP sendmail, aprovechando un bug en el código de depuración que le permite ejecutar un intérprete de comandos y descargar código fuente a través de una conexión de correo. c) Si el gusano logra penetrar en una cuenta local adivinando su contraseña, puede usar los servicios de interpretación de comandos remotos rexec y rsh para atacar los hosts que comparten esa cuenta. En cada caso, el gusano se las arregla para obtener un intérprete de comandos remoto que puede usar para copiar, compilar y ejecutar el programa de arranque de 99 líneas. El programa de arranque establece su propia conexión de red con el gusano local y copia los demás archivos que necesita. Con estos fragmentos, construye un gusano remoto y el proceso de infección comienza de nuevo. Las tácticas de defensa se dividen en tres categorías: impedir la detección de intrusiones, inhibir el análisis del programa y autenticar otros gusanos. La forma más sencilla del gusano de ocultarse es cambiar su nombre. Al iniciarse, borra su lista de argumentos y establece su argumento en sh como 0, lo que le permite hacerse pasar por un intérprete de comandos inocuo. Utiliza fork() para cambiar el ID de su proceso, sin permanecer demasiado tiempo con un solo ID. Estas dos tácticas buscan ocultar la presencia del gusano en los listados de estado del sistema. El gusano intenta dejar la menor cantidad de archivos innecesarios posible, por lo que al iniciarse lee todos sus archivos de soporte en memoria y borra las copias del sistema de archivos que lo indican. Desactiva la generación de archivos de núcleo, por lo que si el gusano comete un error, no deja evidencia en forma de volcados de núcleo. Esta última táctica también está diseñada para bloquear el análisis del programa: impide que un administrador envíe una señal de software al gusano para obligarlo a volcar un archivo de núcleo. Sin embargo, existen otras maneras de obtener un archivo de volcado de núcleo, por lo que el gusano altera cuidadosamente los datos de caracteres en memoria para evitar que se los extraigan fácilmente. Las copias de archivos de disco se codifican mediante la operación O exclusiva repetida de una secuencia de código de diez bytes; las cadenas estáticas se codifican byte a byte mediante la operación O exclusiva con el valor hexadecimal 81, excepto una lista de palabras privada, que se codifica con el valor hexadecimal 80. Si se capturase archivos del gusano de alguna manera antes de que este pudiese eliminarlos, los archivos objeto se cargarán de tal manera que la mayoría de las entradas no esenciales resultan eliminada de la tabla de símbolos, lo que dificultaría adivinar los propósitos de las rutinas del gusano a partir de sus nombres. El gusano también realiza un esfuerzo trivial para evitar que otros programas se aprovechen de sus comunicaciones; en teoría, un sitio bien preparado podría prevenir la infección enviando mensajes a los puertos que el gusano está escuchando, por lo que el gusano se asegura de probar las conexiones mediante un breve intercambio de "números mágicos" aleatorios.
Al estudiar un programa tan complejo como este, es tan importante establecer lo que el programa no hace como lo que sí hace. El gusano no elimina los archivos del sistema: solo elimina los archivos que creó durante el proceso de arranque. El programa no intenta incapacitar un sistema eliminando archivos importantes, ni ningún archivo. No elimina archivos de registro ni interfiere con el funcionamiento normal, salvo por el consumo de recursos del sistema. El gusano no modifica los archivos existentes: no es un virus. El gusano se propaga copiándose y compilándose en cada sistema; no modifica otros programas para que realicen su trabajo. Debido a su método de infección, no cuenta con privilegios suficientes para modificar los programas.
El gusano no instala troyanos: su método de ataque es estrictamente activo; nunca espera a que un usuario caiga en una trampa. Esto se debe, en parte, a que no puede permitirse perder tiempo esperando troyanos; debe reproducirse antes de verse descubierto. Finalmente, el gusano no registra ni transmite contraseñas descifradas: salvo su propia lista estática de contraseñas favoritas, no propaga contraseñas descifradas a nuevos gusanos ni las transmite a una base de operaciones. Esto no significa que las cuentas en las que ingresó sean seguras solo porque no reveló sus contraseñas; si el gusano puede adivinar la contraseña de una cuenta, sin duda otras también pueden. El gusano no intenta obtener privilegios de superusuario: si bien intenta acceder a las cuentas, no depende de privilegios específicos para propagarse, y - si de alguna manera los obtuviese - jamás hace un uso especial de dichos privilegios. El gusano no se propaga a través de uucp, X.25, DECNET ni BITNET: requiere específicamente TCP/IP. No infecta sistemas System V a menos que hayan sido modificados para usar programas de red Berkeley como sendmail, fingerd y rexec.
4. Funcionamiento interno2>
Ahora, algunos detalles: seguiremos el hilo principal de control del gusano y luego examinaremos algunas de sus estructuras de datos antes de analizar cada fase de actividad.
4.1. El hilo de control
Cuando el gusano comienza a ejecutarse en main(), se encarga de algunas inicializaciones, defensas y limpieza. Lo primero que hace es cambiar su nombre a sh. Esto reduce el tiempo durante el cual el gusano es visible en una lista de estado del sistema como un proceso con un nombre extraño como x9834753. A continuación, inicializa el generador de números aleatorios, iniciándolo con la hora actual, desactiva los volcados de memoria y se prepara para morir cuando fallan las conexiones remotas. Con esto resuelto, el gusano procesa su lista de argumentos. Primero busca la opción -p $$, donde $$ representa el ID del proceso de su proceso padre; esta opción le indica al gusano que debe encargarse de la limpieza. Procede a leer cada uno de los archivos que se le dieron como argumentos. Si se realiza una limpieza, elimina cada archivo tras leerlo. Si el gusano no recibió el archivo fuente de arranque l1.c como argumento, finaliza silenciosamente; esto quizás tenga como objetivo ralentizar a quienes experimentan con el gusano. Si se realiza una limpieza, el gusano cierra sus descriptores de archivo, separándose temporalmente de su gusano padre remoto, y elimina algunos archivos. (Uno de estos archivos, /tmp/.dumb, nunca es creado por el gusano y la desvinculación parece ser un remanente de una etapa anterior del desarrollo). El gusano entonces pone a cero su lista de argumentos, de nuevo para frustrar el programa de estado del sistema ps. El siguiente paso es inicializar la lista de interfaces de red del gusano; estas interfaces se utilizan para encontrar redes locales y buscar direcciones alternativas del host actual. Finalmente, si se realiza una limpieza, el gusano restablece su grupo de procesos y finaliza el proceso que ayudó a iniciarlo. La última acción del gusano en main() es llamar a una función llamada doit(), que contiene el bucle principal del gusano.
Pseudocódigo en "C" para la función doit()
doit()
{
Introduce la hora en el generador de números aleatorios del
host de ataque: puertas de enlace, redes locales, redes remotas
checkother();
send message();
for (;;)
{
cracksome();
other_sleep(30);
cracksome();
cambia el ID del proceso
ataca hosts: puertas de enlace, hosts conocidos, redes remotas, redes locales
other_sleep(120);
if (han pasado 12 horas)
reinicia la tabla de hosts
if (pleasequit && nextw > 10)
exit(0);
}
}
doit() ejecuta un breve prólogo antes de iniciar el bucle principal. Introduce la hora actual en el generador de números aleatorios de forma redundante, guardando la hora para saber cuánto tiempo lleva ejecutándose. El gusano entonces intenta su primera infección. Inicialmente ataca las puertas de enlace que encuentra con el programa de estado de red netstat. Si no logra infectar uno de estos hosts, comprueba números de host aleatorios en redes locales y luego prueba con números de host aleatorios en redes situadas más allá de las puertas de enlace, deteniéndose en cada caso si tiene éxito. (Tenga en cuenta que esta secuencia de ataques difiere de la que utiliza el gusano tras entrar en el bucle principal).
Tras este primer intento de infección, el gusano ejecuta la rutina checkother() para comprobar si ya hay otro gusano en la máquina local. En esta comprobación, el gusano actúa como cliente de un gusano existente, que a su vez actúa como servidor; pueden intercambiar mensajes de "control de población", tras lo cual uno de los dos gusanos se desactiva.
Justo antes de entrar en el bucle principal, se ejecuta una rutina inusual. La llamamos send_message(), pero en realidad no envía nada. Parece que su objetivo era que una de cada 15 copias del gusano enviara un datagrama de 1 byte a un puerto del host ernie.berkeley.edu, ubicado en el Departamento de Informática de la Universidad de California en Berkeley. Se ha sugerido que se trataba de una treta, diseñada para desviar la atención hacia ernie y desviarla del host real del autor. Dado que la rutina tiene un error (configura un socket TCP, pero intenta enviar un paquete UDP), no se envía nada. Es posible que se tratara de una treta más profunda, diseñada para ser descubierta únicamente por descompiladores; de ser así, este no sería el único obstáculo deliberado que el autor nos impuso. En cualquier caso, los administradores de Berkeley nunca detectaron ningún proceso escuchando en el puerto 11357 de ernie, y no encontramos código en el gusano que escuche en ese puerto, independientemente del host.
El bucle principal comienza con una llamada a una función llamada cracksome() para descifrar contraseñas. El descifrado de contraseñas es una actividad en la que el gusano trabaja constantemente de forma incremental. Se toma un descanso de 30 segundos para buscar copias intrusas del gusano en el host local y luego vuelve a descifrar. Después de esta sesión, se bifurca (crea un nuevo proceso que se ejecuta con una copia de la misma imagen) y el proceso anterior finaliza. Esto sirve para intercambiar los números de identificación del proceso y dificulta el rastreo del gusano con el programa de estado del sistema ps. En este punto, el gusano vuelve a su fase infecciosa, probando - en orden de preferencia - puertas de enlace, hosts listados en tablas del sistema como /etc/hosts.equiv, números de host aleatorios en el lado opuesto de las puertas de enlace y hosts aleatorios en redes locales. Como antes, si logra infectar un nuevo host, lo marca en una lista y abandona la fase de infección por el momento. Tras la infección, el gusano dedica dos minutos a buscar nuevas copias locales; esto se hace aquí porque un host remoto recién infectado podría intentar reinfectar el host local. Si transcurren 12 horas y el gusano sigue activo, asume que ha tenido mala suerte debido a la caída de las redes o los hosts y reinicia su tabla de hosts para poder empezar desde cero. Al final del bucle principal, el gusano comprueba si está programado para morir como resultado de sus funciones de control de población; si es así, y si ha realizado suficiente trabajo descifrando contraseñas, sale.
4.2. Estructuras de datos
El gusano mantiene al menos cuatro estructuras de datos interesantes, cada una asociada a un conjunto de rutinas de apoyo.
La estructura de objeto se utiliza para almacenar copias de archivos. Los archivos se cifran mediante la función xorbuf() mientras están en memoria, de modo que los volcados del gusano no revelen nada interesante. Los archivos se copian al disco en un sistema remoto antes de iniciar un nuevo gusano, y los nuevos gusanos leen los archivos en memoria y borran las copias del disco como parte de sus tareas de inicio. Cada estructura contiene un nombre, una longitud y un puntero a un búfer. La función getobjectbyname() recupera un puntero a una estructura de objeto con nombre; por alguna razón, solo se utiliza para acceder al archivo fuente de arranque.
La estructura de interfaz contiene información sobre las interfaces de red del host actual. Se utiliza principalmente para comprobar si hay redes locales conectadas. Contiene un nombre, una dirección de red, una máscara de subred y algunos indicadores. La tabla de interfaz se inicializa una vez al iniciar.
La estructura de host se utiliza para registrar el estado y las direcciones de los hosts. Los hosts se añaden a esta lista dinámicamente a medida que el gusano encuentra nuevas fuentes de nombres y direcciones de host. Se puede buscar una dirección o nombre específico en la lista, con la opción de insertar una nueva entrada si no se encuentra ninguna. Los bits de bandera se utilizan para indicar si el host es una puerta de enlace, si se encontró en una tabla del sistema como /etc/hosts.equiv, si el gusano ha tenido dificultades para atacar el host por alguna razón y si ya se ha infectado con éxito. Los bits de "no se puede infectar" e "infectado" se borran cada 12 horas, y los hosts de baja prioridad se eliminan para volver a acumularlos posteriormente. La estructura contiene hasta 12 nombres (alias) y hasta 6 direcciones de red distintas para cada host.
En nuestros códigos fuentes, lo que hemos denominado la estructura Muck se utiliza para rastrear cuentas con el fin de descifrar contraseñas. (Se le asignó el nombre "muck" por razones sentimentales, aunque "pw" o "acct" podrían ser más mnemónicos). Cada estructura contiene un nombre de cuenta, una contraseña cifrada y una contraseña descifrada (si está disponible), además del directorio de inicio y los campos de información personal del archivo de contraseñas.
4.3. Control poblacional
El gusano contiene un mecanismo que parece estar diseñado para limitar el número de copias de sí mismo que se ejecuten en un sistema determinado, pero más allá de eso, nuestra comprensión actual de los objetivos de diseño es limitada. Claramente, esto no impide sobrecargar al sistema, aunque parecería regular la infección intentando que las primeras copias pasen desapercibidas. Se ha sugerido que una simulación de las características de control de la población del gusano podría revelar más sobre su diseño, y nos interesa desarrollar dicha simulación.
El gusano utiliza una técnica cliente-servidor para controlar el número de copias que se ejecutan en la máquina actual. La rutina checkother() se ejecuta al inicio. Esta función intenta conectarse a un servidor que escucha en el puerto TCP 23357. De no existir servidor alguno presente, tal intento de conexión regresa inmediatamente, pero se bloquea si hay uno disponible y ocupado; un gusano de servidor ejecuta periódicamente su código de servidor durante operaciones que consumen mucho tiempo para evitar que la cola de conexiones crezca. Una vez que que el cliente intercambia números mágicos con el servidor como forma trivial de autenticación, el cliente y el servidor tiran dados para decidir quién sobrevive. Si el "o" exclusivo de los bits bajos respectivos de los números aleatorios del cliente y del servidor es 1, el servidor gana; de lo contrario, gana el cliente. El perdedor activa un flag "por favor, salga" que finalmente le permite salir al final del bucle principal. Si en algún momento ocurre un problema (falla una lectura del servidor o se devuelve un número mágico incorrecto), el gusano cliente regresa de la función, convirtiéndose en un gusano que nunca actuará como servidor y, por lo tanto, no participará en el control poblacional. Una prueba al principio de la función hace que 1 de cada 7 gusanos se salte el control poblacional, tal vez como precaución contra un servidor cataléptico. De esta forma el gusano termina el juego de población kother() en jaque, en uno de tres estados: programado para morir después de un tiempo (con pleasequit activado); corriendo como servidor (con la posibilidad de perder la partida posteriormente); e inmortal (a salvo del riesgo del control poblacional).
Una rutina complementaria, other_sleep(), ejecuta la función servidor. Se le pasa un tiempo en segundos y utiliza la llamada al sistema de Berkeley select() para esperar ese tiempo aceptando conexiones de clientes. Al entrar en la función, comprueba si tiene un puerto de comunicaciones para aceptar conexiones; de no ser así, simplemente permanece en reposo durante el tiempo especificado y regresa. De lo contrario, repite select(), disminuyendo el tiempo restante después de atender a un cliente hasta que se agota y la función regresa. Cuando el servidor adquiere un cliente, realiza el protocolo inverso del cliente, decidiendo finalmente si continuar o abandonar. other_sleep() se llama desde diferentes puntos del código para que los clientes no esperen demasiado.
Dado el elaborado esquema del gusano para controlar la reinfección, ¿qué lo llevó a reproducirse tan rápido en una máquina individual como para saturarla? Un factor es la prueba de 1 en 7 en checkother(): los gusanos que se saltan la fase de cliente se vuelven inmortales y, por lo tanto, no corren el riesgo de ser eliminados por una simple tirada de dados. Otra fuente de carga del sistema es el problema de que, al decidir que ha perdido, un gusano aún puede realizar muchas operatorias antes de salir. La rutina de cliente ni siquiera se ejecuta hasta que el gusano recién nacido ha intentado infectar al menos un host remoto - e incluso si un gusano pierde la tirada - continúa ejecutándose hasta el final del bucle principal, e incluso entonces no saldrá a menos que haya ciclado por el bucle principal varias veces, limitado por su progreso en el descifrado de contraseñas. Finalmente, los gusanos nuevos pierden todo el historial de infección de sus padres, por lo que sus hijos intentan constantemente reinfectar el host del padre, así como los de los otros hijos. Si ponemos todos estos factores en consideración, no resulta sorprendente que - transcurridas una o dos horas de la infección - una máquina se dedique por completo a la ejecución de gusanos.
4.4. Localización de nuevos hosts para infectar
Una de las características del gusano es que todos sus ataques son activos, jamás pasivos. Como consecuencia, el gusano no puede esperar a que un usuario lo lleve a otra máquina como si fuera un chicle en un zapato; debe buscar hosts por sí mismo.
El gusano tiene una lista de prioridades muy definida al buscar hosts. Sus hosts favoritos son las puertas de enlace; la rutina hg() intenta infectar cada uno de los hosts que considera puertas de enlace. Solo cuando se sabe que todas las puertas de enlace están infectadas o a prueba de infecciones, es que el gusano procede a otros hosts. hg() llama a la función rt_init() para obtener una lista de puertas de enlace; esta lista se obtiene ejecutando el programa de estado de red netstat y analizando su salida. En caso de que el host actual sea una puerta de enlace, el gusano se asegura de omitir el dispositivo de bucle invertido y cualquier interfaz local; al finalizar, aleatoriza el orden de la lista y añade las primeras 20 puertas de enlace a la tabla de hosts para acelerar las búsquedas iniciales. A continuación, prueba cada puerta de enlace en secuencia hasta encontrar un host que pueda infectarse o hasta agotar los hosts.
Tras descartar las puertas de enlace, la siguiente prioridad del gusano son los hosts cuyos nombres fueron encontrados a través de un análisis de los archivos del sistema. Al comenzar el descifrado de contraseñas, se examinan los archivos /etc/hosts.equiv (que contiene los nombres de los hosts a los que el host local otorga permisos de usuario sin autenticación) y /.rhosts (que contiene los nombres de los hosts desde los que el host local permite inicios de sesión remotos con privilegios), así como los archivos .forward de todos los usuarios (que listan los hosts a los que se reenvía el correo desde el host actual). Estos hosts se marcan para que puedan analizarse antes que el resto. La función hi() se encarga de atacar estos hosts. Cuando se agotan los hosts más rentables, el gusano empieza a buscar hosts que no estén registrados en los archivos. La rutina hl() revisa las redes locales: recorre las direcciones del host local, ocultando la parte del host y sustituyéndola por un valor aleatorio. ha() realiza la misma función para hosts remotos, verificando direcciones alternativas de puertas de enlace. Un código especial gestiona la práctica de ARPAnet de colocar el número IMP en los bits bajos del host y el puerto IMP real (que representa al host) en los bits altos. La función que ejecuta estas sondas aleatorias, a la que llamamos hack_netof(), parece tener un error que le impide atacar hosts en redes locales; esto puede deberse a un malentendido nuestro, por supuesto, pero en cualquier caso, la comprobación de hosts desde los archivos del sistema debería ser suficiente para cubrir todos o casi todos los hosts locales.
El descifrado de contraseñas es otro generador de nombres de host, pero dado que se gestiona de forma independiente del esquema habitual de ataque al host que se presenta aquí, se analizará más adelante junto con el resto del material sobre contraseñas.
4.5. Agujeros de seguridad
El primer hecho a tener en cuenta es que Unix no fue desarrollado teniendo en cuenta la seguridad, en un sentido realista... [Dennis Ritchie, "Sobre la seguridad de Unix"]
Esta sección analiza los servicios TCP utilizados por el gusano para penetrar en los sistemas. Resulta un tanto injusto usar la cita anterior cuando la implementación de los servicios que vamos a analizar fue distribuida por Berkeley en lugar de Bell Labs, pero la opinión resulta acertada. Durante mucho tiempo, la balanza entre seguridad y comodidad en los sistemas Unix se ha inclinado a favor de la comodidad. Como dijo Brian Reid sobre el ataque a Stanford hace dos años: "La comodidad del programador es la antítesis de la seguridad, porque se convertirá en comodidad para el intruso si la cuenta del programador se ve comprometida". La lección de esa experiencia parece haber sido olvidada por la mayoría de la gente, pero no por el autor del gusano.
4.5.1. Rsh y rexec
Estas notas describen cómo el diseño de TCP/IP y la implementación de 4.2BSD permiten a los usuarios de hosts no confiables y posiblemente muy distantes hacerse pasar por usuarios de hosts confiables. [Robert T. Morris, "Una debilidad en el software TCP/IP de Unix 4.2BSD"]
Rsh y rexec son servicios de red que ofrecen intérpretes de comandos remotos. Rexec utiliza autenticación por contraseña; rsh se basa en un puerto de origen privilegiado y archivos de permisos. El gusano explota dos vulnerabilidades: la probabilidad de que una máquina remota con una cuenta de usuario local tenga la misma contraseña que la cuenta local, lo que permite la penetración a través de rexec, y la probabilidad de que dicha cuenta remota incluya el host local en sus archivos de permisos de rsh. Ambas vulnerabilidades son, en realidad, problemas de negligencia o conveniencia para los usuarios y administradores de sistemas, más que errores reales, pero representan vías de infección, al igual que los errores de seguridad involuntarios.
El primer uso de rsh por parte del gusano es bastante simple: busca una cuenta remota con el mismo nombre que la que (sin sospecharlo) ejecuta el gusano en la máquina local. Esta prueba forma parte del menú estándar de ataques realizados para cada host; si falla, el gusano recurre a finger y luego a sendmail. Muchos sitios, incluido Utah, ya estaban protegidos de este ataque trivial al no proporcionar shells remotos para pseudousuarios como daemon o nobody.
Un uso más sofisticado de estos servicios se encuentra en las rutinas de descifrado de contraseñas. Tras adivinar una contraseña, el gusano intenta inmediatamente penetrar en los hosts remotos asociados a la cuenta vulnerada. Lee el archivo .forward del usuario (que contiene una dirección a la que se reenvía el correo) y el archivo .rhosts (que contiene una lista de hosts y, opcionalmente, los nombres de usuario de aquellos hosts que tienen permiso para acceder a la máquina local con rsh, omitiendo la autenticación de contraseña habitual), probando estos nombres de host hasta que lo consigue. Cada host objetivo es atacado de dos maneras. El gusano primero contacta con el servidor rexec del host remoto y le envía el nombre de la cuenta que se encuentra en los archivos .forward o .rhosts, seguido de la contraseña adivinada. Si esto falla, el gusano se conecta al servidor rexec local con el nombre de la cuenta local y lo usa para contactar con el servidor rsh del objetivo. El servidor rsh remoto permitirá la conexión siempre que el nombre del host local aparezca en el archivo /etc/hosts.equiv o en el archivo .rhosts privado del usuario.
Fortalecer estos servicios de red es mucho más problemático que corregir finger y sendmail, por desgracia. A los usuarios no les gusta la incomodidad de escribir su contraseña al iniciar sesión en un host local de confianza, y no quieren recordar contraseñas diferentes para cada uno de los muchos hosts con los que puedan tener que lidiar. Algunas soluciones pueden ser peores que la enfermedad; por ejemplo, un usuario que se ve obligado a lidiar con muchas contraseñas es más propenso a anotarlas en algún lugar.
4.5.2. Finger
gets fue eliminado de nuestra [biblioteca C] hace un par de días. [Bill Cheswick de AT&T Bell Labs Research, comunicación privada, 9/11/88]
Probablemente la forma más ingeniosa de hacking del gusano es su apropiación del servicio TCP finger para acceder a un sistema. Finger reporta información sobre un usuario en un host, generalmente incluyendo datos como su nombre completo, ubicación de su oficina, número de extensión, etc. La versión Berkeley (3) del servidor finger es un programa muy simple: lee una solicitud del host de origen, ejecuta el programa finger local con la solicitud como argumento y envía la salida. Desafortunadamente, el servidor finger lee la solicitud remota con gets(), una rutina estándar de la biblioteca C que data de tiempos inmemoriales y que no comprueba si el búfer de solicitudes de 512 bytes del servidor se desborda en la pila. El gusano envía al servidor finger una solicitud de 536 bytes; La mayor parte de la solicitud es código máquina VAX que solicita al sistema que ejecute el intérprete de comandos sh, y los 24 bytes adicionales representan los datos suficientes para sobrescribir el marco de pila del servidor para la rutina principal. Cuando la rutina principal del servidor finaliza, se supone que el contador de programa de la función alling se restaura desde la pila, pero el gusano sobrescribió este contador con uno que apunta al código VAX en el búfer de solicitudes. El programa accede al código del gusano y ejecuta el intérprete de comandos, que el gusano utiliza para iniciar su arranque.
No es sorprendente que, poco después de que se informara que el gusano utilizaba esta función de gets(), varias personas reemplazaran todas las instancias de gets() en el código del sistema con código sensato que verifica la longitud del búfer. Algunos incluso llegaron a eliminar gets() de la biblioteca, aunque la función aparentemente está exigida por el próximo estándar ANSI C (4). Hasta ahora, nadie ha afirmado haber solucionado el problema del servidor finger antes del incidente del gusano, pero en mayo de 1988, estudiantes de la UC Santa Cruz aparentemente penetraron la seguridad utilizando un servidor finger diferente afectado por un bug similar. El administrador del sistema de la UCSC notó que el servidor finger de Berkeley tenía un error similar y envió un correo electrónico a Berkeley, pero en ese momento no se apreció la gravedad del problema (Jim Haynes, comunicación privada).
Una nota final: el gusano es meticuloso en algunas áreas, pero no en otras. Por lo que sabemos, no hubo una versión Sun-3 de la intrusión finger, a pesar de que el servidor Sun-3 era tan vulnerable como el de VAX. ¿Quizás el autor tenía fuentes de VAX disponibles, pero no de Sun?
4.5.3. Sendmail
La trampa surgió de dos "características" distintas que, aunque inocuas por sí mismas, eran mortales al combinarse (algo así como gas nervioso binario). [Eric Allman, comunicación personal, 22/11/88]
El ataque a través de sendmail es quizás el menos preferido en el arsenal del gusano, pero a pesar de ello, un sitio en Utah fue objeto de casi 150 ataques sendmail durante el Jueves Negro. Sendmail es el programa que proporciona el servicio de correo SMTP en redes TCP para sistemas UNIX de Berkeley. Utiliza un protocolo simple orientado a caracteres para aceptar correo de sitios remotos. Una característica de sendmail es que permite que el correo se entregue a procesos en lugar de a archivos de buzón; Esto se puede usar con, por ejemplo, el programa de vacaciones para notificar a los remitentes que estás fuera de la ciudad y que no puedes responder a sus correos temporalmente. Normalmente, esta función solo está disponible para los destinatarios. Desafortunadamente, se creó accidentalmente una pequeña falla al corregir un par de errores de seguridad anteriores: si sendmail se compila con el indicador DEBUG y el remitente, en tiempo de ejecución, solicita que sendmail entre en modo de depuración enviando el comando debug, permite a los remitentes pasar una secuencia de comandos en lugar de un nombre de usuario para el destinatario. Lamentablemente, la mayoría de las versiones de sendmail se compilan con DEBUG, incluida la que Sun envía en su distribución binaria. El gusano imita una conexión SMTP remota, introduciendo /dev/null como nombre del remitente y una cadena cuidadosamente diseñada como destinatario. La cadena configura un comando que borra la cabecera del mensaje y pasa el cuerpo a un intérprete de comandos. El cuerpo contiene una copia del código fuente de arranque del gusano, además de los comandos para compilarlo y ejecutarlo. Una vez que el gusano finaliza el protocolo y cierra la conexión con sendmail, el programa de arranque se construye en el host remoto y el gusano local espera su conexión para completar el proceso de construcción de un nuevo gusano.
Por supuesto, esta no es la primera vez que se encuentra una vulnerabilidad o "trampa" involuntaria como esta en sendmail, y puede que no sea la última. En su discurso de aceptación del Premio Turing, Ken Thompson afirmó: "No se puede confiar en código que no se ha creado completamente por uno mismo. (Especialmente en código de empresas que emplean a personas como yo)". De hecho, como dice Eric Allman, "Ni siquiera se puede confiar en código que se ha creado completamente por uno mismo". El problema básico de confiar en los programas del sistema no es fácil de resolver.
4.6. Infección
El gusano utiliza dos rutinas favoritas cuando decide infectar un host. Una rutina, denominada infect(), se utiliza en rutinas de escaneo de host como hg(). Infect() primero comprueba que no esté infectando el equipo local, un equipo ya infectado o uno previamente atacado pero no infectado con éxito. Los estados "infectado" e "inmune" se marcan mediante indicadores en la estructura del host cuando los ataques tienen éxito o fallan, respectivamente. El gusano se asegura de obtener una dirección para el host objetivo, marcándolo como inmune si no lo consigue. A continuación, realiza una serie de ataques: primero mediante rsh desde la cuenta bajo la que se ejecuta el gusano, luego mediante finger y finalmente mediante sendmail. Si infect() falla, marca el host como inmune.
La otra rutina de infección se llama hul() y se ejecuta desde el código de descifrado de contraseñas tras adivinar una contraseña. Hul(), al igual que infect(), se asegura de no volver a infectar un host y luego busca una dirección. Si un posible nombre de usuario remoto está disponible en un archivo .forward o .rhosts, el gusano lo comprueba para asegurarse de que sea correcto; no debe contener signos de puntuación ni caracteres de control. Si un nombre de usuario remoto no está disponible, el gusano utiliza el nombre de usuario local. Una vez que el gusano obtiene un nombre de usuario y una contraseña, contacta con el servidor rexec en el host objetivo e intenta autenticarse. Si lo logra, procede a la fase de arranque; de lo contrario, intenta un enfoque ligeramente diferente: se conecta al servidor rexec local con el nombre de usuario y la contraseña locales y luego usa este intérprete de comandos para ejecutar un intérprete de comandos en el equipo objetivo con rsh. Esto tendrá éxito si el host remoto indica que confía en el host local en su archivo /etc/hosts.equiv, o si la cuenta remota indica que confía en la cuenta local en su archivo .rhosts. hul() ignora el indicador "inmune" de infect() y no lo activa, ya que hul() puede tener éxito por cuenta, algo que infect() no puede lograr por host.
Tanto infect() como hul() usan una rutina llamada sendworm() para realizar su trabajo sucio (5). sendworm() busca el archivo fuente de arranque ll.c en su lista de objetos y luego usa la rutina makemagic() para obtener un punto final del flujo de comunicación (un socket), un número de puerto de red aleatorio para el encuentro y un número mágico para la autenticación. (makemagic() tiene un efecto secundario interesante: busca una dirección utilizable para el host objetivo intentando conectarse a su puerto TCP telnet; esto produce un mensaje de registro característico del servidor telnet). Si makemagic() tuvo éxito, el gusano comienza a enviar comandos al intérprete de comandos remoto iniciado por el ataque inmediatamente anterior. Cambia su directorio a un lugar desprotegido (/usr/tmp) y luego envía el código fuente de arranque, utilizando el editor de flujo UNIX sed para analizar el flujo de entrada. El código fuente de arranque se compila y se ejecuta en el sistema remoto, y el gusano ejecuta una rutina llamada waithit() para esperar a que el sistema de arranque remoto devuelva la llamada al puerto seleccionado.
El sistema de arranque es bastante simple. Se le proporciona la dirección del host de origen, un número de puerto TCP y un número mágico como argumentos. Al iniciarse, se desvincula para que no pueda ser detectado en el sistema de archivos y luego llama a fork() para crear un nuevo proceso con la misma imagen. El proceso anterior finaliza, lo que permite que el gusano original continúe con su actividad. El programa de arranque lee sus argumentos y los pone a cero para ocultarlos del programa de estado del sistema; entonces está listo para conectarse a través de la red al gusano padre. Una vez establecida la conexión, el programa de arranque envía el número mágico, que el padre comparará con su propia copia. Si el padre acepta el número (que se procesa cuidadosamente para que sea independiente del orden de bytes del host), enviará una serie de nombres de archivo y archivos que el programa de arranque escribe en el disco. Si ocurre un problema, el programa de arranque elimina todos estos archivos y finaliza. Finalmente, la transacción se completa y el programa de arranque llama a un intérprete de comandos para finalizar el trabajo.
Mientras tanto, el gusano padre en waithit() espera hasta dos minutos a que el programa de arranque repita la llamada; si el programa de arranque no logra repita la llamada, o si la autenticación falla, el gusano decide rendirse e informa de un fallo. Cuando la conexión es exitosa, el gusano envía todos sus archivos, seguido de un indicador de fin de archivo. Hace una pausa de cuatro segundos para que se inicie un intérprete de comandos en el lado remoto y luego emite comandos para crear un nuevo gusano. Para cada archivo de objeto reubicable en la lista de archivos, el gusano intenta construir un objeto ejecutable; normalmente, cada archivo contiene código para un tipo de ordenador específico, y las compilaciones fallarán hasta que el gusano intente con el tipo de ordenador adecuado. Si el gusano padre finalmente consigue construir un gusano hijo ejecutable, lo libera con la opción -p para matar al intérprete de comandos y luego cierra la conexión. El host objetivo se marca como "infectado". Si ninguno de los objetos produce un gusano hijo utilizable, el gusano padre elimina los restos y waithit() devuelve una indicación de error.
Cuando un sistema está inundado de gusanos, el directorio /usr/tmp puede llenarse de archivos sobrantes debido a un error en waithit(). Si la compilación de un gusano tarda más de 30 segundos, el código de resincronización informará de un error, pero waithit() no podrá eliminar los archivos creados. En una de nuestras máquinas, se acumularon 13 MB de material que representa 86 conjuntos de archivos durante 5,5 horas.
4.7. Descifrado de contraseñas
Un algoritmo de descifrado de contraseñas parece lento y voluminoso para un gusano, pero este lo consigue gracias a su persistencia y eficiencia. El gusano se beneficia de algunas estadísticas desafortunadas sobre las opciones de contraseñas habituales. Aquí explicamos cómo el gusano elige las contraseñas para probar y cómo se modificó la rutina de cifrado de contraseñas de UNIX.
4.7.1. Adivinación de contraseñas
Por ejemplo, si el nombre de usuario es "abc", entonces "abc", "cba" y "abcabc" son excelentes candidatos para contraseñas. [Grampp y Morris, "Seguridad del sistema operativo UNIX"]
La adivinación de contraseñas del gusano se realiza mediante una pequeña máquina de 4 estados. El primer estado recopila datos de contraseñas, mientras que los estados restantes representan fuentes cada vez menos probables de contraseñas potenciales. La rutina central de descifrado se llama cracksome() y contiene un conmutador en cada uno de los cuatro estados.
La rutina que implementa el primer estado se llama crack_0(). Su función es recopilar información sobre hosts y cuentas. Se ejecuta una sola vez; la información recopilada persiste durante la vida útil del gusano. Su implementación es sencilla: lee los archivos /etc/hosts.equiv y /.rhosts en busca de hosts que atacar, y luego lee el archivo de contraseñas en busca de cuentas. Para cada cuenta, el gusano guarda el nombre, la contraseña cifrada, el directorio personal y los campos de información del usuario. Como comprobación preliminar rápida, busca un archivo .forward en el directorio personal de cada usuario y guarda cualquier nombre de host que encuentre en ese archivo, marcándolo como los anteriores.
De forma poco imaginativa, llamamos a la función para el siguiente estado crack_1(). crack_1() busca contraseñas fácilmente descifradas. Estas contraseñas pueden adivinarse simplemente basándose en la información ya contenida en el archivo de contraseñas. Grampp y Morris informan de un estudio de más de 100 archivos de contraseñas, donde entre el 8 y el 30 % de las contraseñas se adivinaron utilizando únicamente el nombre literal de la cuenta y un par de variaciones. El gusano intenta algo más: comprueba la contraseña nula, el nombre de la cuenta, el nombre de la cuenta concatenado consigo mismo, el nombre (extraído del campo de información del usuario, con la primera letra en minúscula), el apellido y el nombre de la cuenta invertido. Revisa hasta 50 cuentas por cada llamada a cracksome(), guardando su lugar en la lista de cuentas y avanzando al siguiente estado cuando se queda sin cuentas para probar.
El siguiente estado lo gestiona crack_2(). En este estado, el gusano compara una lista de contraseñas favoritas, una por llamada, con todas las contraseñas cifradas del archivo de contraseñas. La lista contiene 432 palabras, la mayoría de las cuales son palabras reales en inglés o nombres propios; parece probable que esta lista se generara robando archivos de contraseñas y descifrándolos a su antojo en el equipo doméstico del autor del gusano. Se utiliza una variable global nextw para contar el número de contraseñas intentadas, y es este recuento (más una pérdida en el control de la población) el que determina si el gusano sale al final del bucle principal. nextw debe ser mayor que 10 para que el gusano pueda salir. Dado que el gusano normalmente dedica 2,5 minutos a buscar clientes durante el bucle principal y llama a cracksome() dos veces en ese período, parece que el gusano debe realizar un mínimo de 7 pasadas por el bucle principal, lo que le lleva más de 15 minutos (6). El gusano tardará al menos 9 horas en escanear su lista de contraseñas integrada y pasar al siguiente estado.
El último estado lo gestiona crack_3(). Abre el diccionario en línea de UNIX /usr/dict/words y lo revisa palabra por palabra. Si una palabra está en mayúsculas, el gusano también intenta una versión en minúsculas. Esta búsqueda puede ser interminable: el gusano tardaría unas cuatro semanas en completar un diccionario típico como el nuestro.
Cuando el gusano selecciona una contraseña posible, la pasa a una rutina llamada try_password(). Esta función llama a la versión especial del gusano de la función de cifrado de contraseñas UNIX, crypt(), y compara el resultado con la contraseña cifrada real de la cuenta objetivo. De ser iguales - o si la contraseña y la suposición son la cadena nula (sin contraseña) - el gusano almacena la contraseña en texto plano y procede a atacar los hosts conectados a esta cuenta. Una rutina llamada try_forward_and_rhosts() lee los archivos .forward y .rhosts del usuario, llamando a la función hul() descrita anteriormente para cada cuenta remota que encuentra.
4.7.2. Cifrado de contraseñas más rápido
El uso de contraseñas cifradas parece razonablemente seguro a falta de una atención seria por parte de expertos en la materia. [Morris y Thompson, "Seguridad de contraseñas: Un caso práctico"]
Desafortunadamente, algunos expertos en la materia han estado prestando mucha atención a las implementaciones rápidas del algoritmo de cifrado de contraseñas UNIX. La autenticación de contraseñas en UNIX funciona sin introducir ninguna versión legible de la contraseña en el sistema y, de hecho, sin proteger la contraseña cifrada contra la lectura por parte de los usuarios. Cuando un usuario escribe una contraseña sin cifrar, el sistema la cifra mediante la rutina estándar de la biblioteca crypt() y la compara con una copia guardada de la contraseña cifrada. El algoritmo de cifrado está diseñado para ser prácticamente invertible, impidiendo la recuperación de contraseñas examinando únicamente el texto cifrado. Además, su ejecución es costosa, por lo que las pruebas de conjeturas llevarán mucho tiempo. El algoritmo de cifrado de contraseñas de UNIX se basa en el Estándar Federal de Cifrado de Datos (DES). Actualmente, nadie sabe cómo invertir este algoritmo en un tiempo razonable, y aunque existen chips de codificación DES rápidos, la versión UNIX del algoritmo está ligeramente alterada, lo que impide utilizar un chip DES estándar para implementarlo. Se han mitigado los problemas de la implementación de DES en UNIX. La velocidad de las computadoras aumenta continuamente; las máquinas actuales suelen ser varias veces más rápidas que las que estaban disponibles cuando se inventó el esquema de contraseñas actual. Al mismo tiempo, se han descubierto maneras de agilizar el software DES. Las contraseñas de UNIX ahora son mucho más susceptibles a la adivinación persistente, especialmente si ya se conocen las contraseñas cifradas. La versión del gusano de la rutina crypt() de UNIX se ejecutó más de 9 veces más rápido que la versión estándar cuando la probamos en nuestro VAX 8600. Mientras que la rutina crypt() estándar tarda 54 segundos en cifrar 271 contraseñas en nuestro 8600 (la cantidad de contraseñas que contiene nuestro archivo de contraseñas), la rutina del gusano tarda menos de 6 segundos.
El algoritmo crypt() del gusano parece ser un equilibrio entre tiempo y espacio: el tiempo necesario para cifrar una sola contraseña frente al considerable espacio de tabla adicional necesario para optimizar el rendimiento del algoritmo. Curiosamente, una mejora en el rendimiento ahorra algo de espacio. El algoritmo tradicional de UNIX almacena cada bit de la contraseña en un byte, mientras que el algoritmo del gusano los empaqueta en dos palabras de 32 bits. Esto permite al algoritmo del gusano utilizar operaciones de campo de bits y desplazamiento sobre los datos de la contraseña, lo que resulta enormemente más rápido. Otras mejoras de velocidad incluyen el desenrollado de bucles, la combinación de tablas, el precomputado de desplazamientos y máscaras, y la eliminación de permutaciones iniciales y finales redundantes al realizar las 25 aplicaciones del DES modificado que utiliza el algoritmo de cifrado de contraseñas. La mayor mejora de rendimiento se debe a la combinación de permutaciones: el gusano utiliza matrices expandidas indexadas por grupos de bits en lugar de los bits individuales que utiliza el algoritmo estándar. La versión rápida de crypt() de Matt Bishop realiza todas estas tareas y, además, precomputa aún más funciones, duplicando el rendimiento del algoritmo del gusano, pero requiriendo casi 200 KB de datos inicializados, en comparación con los 6 KB que utiliza el gusano y los menos de 2 KB que utiliza el crypt() normal.
¿Cómo pueden los administradores de sistemas defenderse de las implementaciones rápidas de crypt()? Una sugerencia para frustrar a los atacantes es la idea de los archivos de contraseñas ocultas. En este esquema, las contraseñas cifradas se ocultan en lugar de ser públicas, lo que obliga a un atacante a acceder a una cuenta privilegiada o a usar la CPU del host y un algoritmo de cifrado (lento) para atacar, con el riesgo añadido de que se registren las solicitudes de prueba de contraseñas y se descubra el descifrado. La desventaja de los archivos de contraseñas ocultas es que si los atacantes logran burlar las protecciones del archivo que contiene las contraseñas reales, todas las contraseñas se considerarán descifradas y deberán reemplazarse. Otra sugerencia es reemplazar la implementación de DES de UNIX con la más rápida disponible, pero ejecutarla 1000 veces o más en lugar de las 25 que se utilizan en el código de crypt() de UNIX. A menos que el número de repeticiones esté vinculado a la velocidad de CPU más rápida disponible, este enfoque simplemente pospone "el Día del Juicio Final" hasta que el atacante encuentre una máquina más rápida. Es interesante notar que Morris y Thompson midieron el tiempo para calcular el antiguo algoritmo de cifrado de contraseña M-209 (no DES) utilizado en las primeras versiones de UNIX en la PDP-11/70 y descubrieron que una buena implementación tomaba solo 1,25 milisegundos por cifrado, lo que consideraron insuficiente; actualmente, el VAX 8600 que utiliza el algoritmo basado en DES de Matt Bishop necesita 11,5 milisegundos por cifrado, y pronto estarán disponibles máquinas 10 veces más rápidas que el VAX 8600 a un precio más económico (¡si no lo están ya!).
5. Opiniones
El acto de entrar en un sistema informático debe tener el mismo estigma social que entrar en la casa del vecino. No debería importar que la puerta del vecino esté abierta. [Ken Thompson, Conferencia del Premio Turing de 1983]
[Los creadores de virus están] robando un coche para darse un capricho. [R. H. Morris, en su testimonio ante el Capitolio en 1983, citado en el New York Times el 11/11/88]
No pretendo ofrecer declaraciones definitivas sobre la moralidad del autor del gusano, la ética de publicar información de seguridad ni las necesidades de seguridad de la comunidad informática UNIX, ya que personas mejor (y menos) cualificadas que yo siguen criticando estos temas con vehemencia en los diversos grupos de noticias y listas de correo de la red. Para el administrador de sistemas común y corriente - que podría haberse sentido confundido por tanta información y desinformación - intentaré responder a algunas de las preguntas más relevantes de forma concisa pero útil.
¿Causó daños el gusano? El gusano no destruyó archivos, ni interceptó correo privado, ni reveló contraseñas, ni corrompió bases de datos ni instaló troyanos. Compitió por el tiempo de CPU con los procesos de usuario legítimos, llegando a saturarlos. Consumió recursos limitados del sistema, como la tabla de archivos abiertos y la tabla de texto de procesos, provocando fallos en los procesos de usuario por falta de ellos. Provocó el bloqueo de algunas máquinas al operarlas casi al límite de su capacidad, lo que generó errores que no aparecen con cargas normales. Obligó a los administradores a reiniciar el sistema una o más veces para eliminar los gusanos, lo que cerró las sesiones de usuario y los trabajos de larga duración. Obligó a los administradores a cerrar las puertas de enlace de red, incluidas las puertas de enlace entre importantes redes de investigación nacionales, para aislar el gusano. Esto provocó retrasos de hasta varios días en el intercambio de correo electrónico, lo que provocó que algunos proyectos incumplieran plazos y que otros perdieran valioso tiempo de investigación. Obligó al personal de sistemas de todo el país a abandonar sus ataques y a trabajar 24 horas al día intentando eliminar los gusanos. Esto causó tal temor en los directivos de al menos una institución que depuraron todos los discos de sus instalaciones que estaban en línea en el momento de la infección y limitaron la recarga de archivos a los datos verificablemente no modificados por un agente externo. Provocó una degradación sustancial del ancho de banda a través de las puertas de enlace que seguían funcionando después del inicio de la infección, ya que las puertas de enlace utilizaban gran parte de su capacidad simplemente para enviar el gusano de una red a otra. Penetró en las cuentas de usuario y dio la impresión de que un usuario estaba alterando un sistema cuando, en realidad, no era responsable. Es cierto que el gusano podría haber sido mucho más dañino de lo que realmente resultó ser: en las últimas semanas, se han descubierto varios fallos de seguridad que el gusano podría haber utilizado para destruir completamente un sistema. Quizás deberíamos estar agradecidos por haber evitado consecuencias increíblemente terribles, y quizás también deberíamos estar agradecidos por haber aprendido tanto sobre las debilidades en las defensas de nuestros sistemas, pero creo que deberíamos compartir nuestro agradecimiento con alguien más que el autor del gusano.
¿Era malicioso el gusano? Algunas personas han sugerido que el gusano fue un experimento inocente que se salió de control, y que nunca se pretendió que se propagara tan rápido ni tan ampliamente. Podemos encontrar evidencia en el gusano que apoya y contradice esta hipótesis. Existen varios errores en el gusano que parecen ser el resultado de una programación apresurada o descuidada. Por ejemplo, en la rutina if init() del gusano, hay una llamada a la función del bloque cero bzero() que utiliza incorrectamente el propio bloque en lugar de su dirección como argumento. También es posible que un error fuera responsable de la ineficacia de las medidas de control de población utilizadas por el gusano. Esto podría interpretarse como evidencia de que una versión de desarrollo del gusano se filtró accidentalmente, y quizás el autor originalmente pretendía probar la versión final en condiciones controladas, en un entorno del que no escaparía. Por otro lado, existe evidencia considerable de que el gusano fue diseñado para reproducirse rápidamente y propagarse a grandes distancias. Se puede argumentar que los ataques de control de población del gusano son anémicos por diseño: son un compromiso entre propagarlo lo más rápido posible y aumentar la carga lo suficiente como para ser detectado y derrotado. Un gusano existirá durante un tiempo considerable y realizará una cantidad considerable de trabajo incluso si pierde la tirada de dados (imaginaria); además, 1 de cada 7 gusanos se vuelve inmortal y no puede ser eliminado por tiradas de dados. Existe amplia evidencia de que el gusano fue diseñado para obstaculizar los esfuerzos por detenerlo incluso después de ser identificado y capturado. Ciertamente lo logró, ya que tardó casi un día antes de que el último modo de infección (el servidor de dedo) fuera identificado, analizado y reportado ampliamente; el gusano tuvo mucho éxito propagándose durante este tiempo, incluso en
Sistemas que habían solucionado el problema de depuración de sendmail y desactivado rexec. Finalmente, existen pruebas de que el autor del gusano lo introdujo deliberadamente en un sitio externo que se dejó abierto y abierto a usuarios externos ocasionales, abusando de esta hospitalidad de forma bastante descortés. Al parecer, abusó aún más de esta confianza al eliminar un archivo de registro que podría haber revelado información que vinculara su sitio web con la infección. Creo que la inocencia reside en la comunidad de investigadores, no en el autor del gusano.
¿La publicación de los detalles del gusano perjudicará aún más la seguridad? En cierto sentido, el propio gusano ha resuelto ese problema: se ha publicado enviando copias a cientos o miles de máquinas en todo el mundo. Claro que un malhechor que quiera usar las artimañas del gusano tendría que hacer el mismo esfuerzo que nosotros para comprender el programa, pero solo nos llevó una semana descompilarlo por completo, así que, si bien se necesita fortaleza para hackear el gusano, claramente no es muy difícil para un programador decente. Uno de los trucos más efectivos del gusano se anunció al entrar: la mayor parte del ataque a sendmail es visible en el archivo de registro, y unos minutos de investigación con las fuentes revelarán el resto del truco. El algoritmo de contraseñas rápidas del gusano podría ser útil para los cibercriminales, pero al menos otras dos implementaciones más rápidas llevan disponibles un año o más, por lo que no es muy secreto, ni siquiera muy original. Finalmente, los detalles del gusano se han esbozado con suficiente detalle en varios grupos de noticias y listas de correo, por lo que los principales ataques son de dominio público. Creo que es más importante que entendamos qué ocurrió, para que podamos reducir la probabilidad de que vuelva a ocurrir, que perder tiempo en un esfuerzo inútil por ocultar el problema a todos, excepto a los cibercriminales. Hay soluciones para las distribuciones de código fuente y binarias ampliamente disponibles, y cualquiera que utilice un sistema con estas vulnerabilidades debe revisarlas de inmediato, si aún no lo ha hecho.
6. Conclusión
Ha aumentado considerablemente la conciencia pública. [R. H. Morris, citado en el New York Times el 5/11/88]
Esta cita es una de las subestimaciones del año. La noticia del gusano apareció en la portada del New York Times y otros periódicos durante días. Fue tema de reportajes en televisión y radio. Incluso la tira cómica del condado de Bloom se burló de ella.
Nuestra comunidad nunca antes había estado en el centro de atención de esta manera, y a juzgar por la respuesta, nos ha asustado. No voy a entrar en clichés sobre cómo nos va a cambiar esta experiencia, pero sí diré que creo que estos problemas se han ignorado durante mucho más tiempo del que era seguro, y creo que una mejor comprensión de la crisis que acaba de pasar nos ayudará a afrontar mejor la próxima. Esperemos tener la misma suerte la próxima vez que la tuvimos esta vez.
Agradecimientos
Nadie tiene la culpa de las inexactitudes aquí contenidas, excepto yo, pero hay muchas personas a quienes agradecer por ayudar a descompilar el gusano y por ayudar a documentar la epidemia. Dave Pare y Chris Torek estuvieron en el centro de la acción durante la sesión nocturna en Berkeley, y recibieron la ayuda y la opinión de Keith Bostic, Phil Lapsley, Peter Yee, Jay Lepreau y miles de personas más. Glenn Adams y Dave Siegel proporcionaron buena información sobre el ataque al Laboratorio de Inteligencia Artificial del MIT, mientras que Steve Miller me dio detalles sobre Maryland, Jeff Forys sobre Utah y Phil Lapsley, Peter Yee y Keith Bostic sobre Berkeley. Bill Cheswick me envió un par de anécdotas divertidas de AT&T Bell Labs. Jim Haynes me explicó detalladamente los problemas de seguridad detectados por sus estudiantes universitarios en la UC Santa Cruz. Eric Allman, Keith Bostic, Bill Cheswick, Mike Hibler, Jay Lepreau, Chris Torek y Mike Zeleznik aportaron muchos comentarios útiles. Gracias a todos, y a todos los que olvidé mencionar.
El artículo de Matt Bishop "Una versión rápida del DES y un algoritmo de cifrado de contraseñas", (c)1987, escrito por Matt Bisbop y la Asociación de Investigación Espacial de Universidades, me ayudó a descifrar (ligeramente) los misterios del DES. Si desea comprender cómo el gusano hackea DES, le recomiendo consultar primero aquí. El artículo está disponible con la distribución deszip de Bishop, que incluye software para el cifrado rápido de DES. Esta última se produjo mientras Bishop trabajaba en el Instituto de Investigación de Ciencias de la Computación Avanzadas del Centro de Investigación Ames de la NASA; Bishop ahora trabaja en el Dartmouth College (bishop@bear.dartmouth.edu). Me envió una nota muy útil sobre la implementación de crypt() en el gusano, en la que me basé mucho al hablar del algoritmo mencionado.
Los siguientes documentos también se citaron anteriormente para obtener citas u otro material:
Estándar de Cifrado de Datos, FIPS PUB 46, Oficina Nacional de Normas, Washington D.C., 15 de enero de 1977.
F. T. Grampp y R. H. Morris, "Seguridad del sistema operativo UNIX", en AT&T Bell Laboratories Technical Journal, octubre de 1984, vol. 63, n.º 8, parte 2, pág. 1649.
Brian W. Kernighan y Dennis Ritchie, El lenguaje de programación C, segunda edición, Prentice Hall: Englewood Cliffs, NJ, (C)1988.
John Markoff, "El autor de un 'virus' informático es hijo de un experto estadounidense en seguridad electrónica", pág. 1 del New York Times, 5 de noviembre de 1988.
John Markoff, "La pasión de una familia por las computadoras, que se agrió", pág. 1 del New York Times, 11 de noviembre de 1988.
Robert Morris y Ken Thompson, "Seguridad de contraseñas: Un caso práctico", fechado el 3 de abril de 1978, en el Manual del programador de UNIX, en los Documentos complementarios o en el Manual del administrador del sistema, según dónde y cuándo se obtuvieron los manuales.
Robert T. Morris, "Una debilidad en el software TCP/IP de Unix 4.2BSD", Informe técnico de ciencias de la computación n.º 117 de AT&T Bell Laboratories, 25 de febrero de 1985. Este artículo describe una forma de suplantar TCP/IP para que un host no confiable pueda usar el servidor rsh en cualquier sistema UNIX 4.2BSD, en lugar de un ataque basado en la irrupción en cuentas de hosts confiables, que es lo que utiliza el gusano.
Brian Reid, "Intrusiones masivas de UNIX en Stanford", RISKS-FORUM Digest, vol. 3, Número 56, 16 de septiembre de 1986.
Dennis Ritchie, "Sobre la seguridad de UNIX", del 10 de junio de 1977, en el mismo manual donde se encontró el artículo de Morris y Thompson.
Ken Thompson, "Reflexiones sobre la confianza", Conferencia del Premio Turing de la ACM de 1983, en Communications of the ACM, vol. 27, n.º 8, pág. 761, agosto de 1984.
Notas al pie
(1) Internet es una red lógica compuesta por muchas redes físicas, todas con protocolos de red de clase IP.
(2) VAX y Sun-3 son modelos de computadoras fabricados por Digital Equipment Corp. y Sun Microsystems Inc., respectivamente. UNIX es una marca registrada de AT&T Trademark Laboratories.
(3) De hecho, al igual que gran parte del código de la distribución Berkeley, el servidor de huellas digitales fue aportado desde otra fuente; En este caso, parece que el MIT fue la fuente.
(4) Véase, por ejemplo, el Apéndice B, sección 1.4 de la segunda edición de El lenguaje de programación C de Kernighan y Ritchie.
(5) Una pequeña excepción: el ataque sendmail no utiliza sendworm(), ya que necesita gestionar el protocolo SMTP además de la interfaz del intérprete de comandos, pero el principio es el mismo.
(6) Para quienes presten atención a los detalles: La primera llamada a cracksome() se consume leyendo archivos del sistema. El gusano debe dedicar al menos una llamada a cracksome0 en el segundo estado a atacar contraseñas triviales. Esto representa al menos una pasada por el bucle principal. En el tercer estado, cracksome() prueba una contraseña de su lista de favoritas en cada llamada; el gusano saldrá si pierde una tirada de dados y se han comprobado más de diez palabras, lo que representa al menos seis bucles: dos palabras en cada bucle durante cinco bucles para alcanzar las 10 palabras, y luego otro bucle para superar esa cifra. En total, esto suma un mínimo de 7 bucles. Si los 7 bucles ocuparan el tiempo máximo de espera para los clientes, esto requeriría un mínimo de 17,5 minutos, pero la comprobación de 2 minutos puede salir antes si un cliente se conecta y el servidor pierde el desafío; por lo tanto, 15,5 minutos de tiempo de espera más la sobrecarga de tiempo de ejecución es la duración mínima. En este período, un gusano atacará al menos 8 hosts a través de las rutinas de infección de host, e intentará alrededor de 18 contraseñas para cada cuenta, atacando más hosts si se descifran las cuentas.