IntermediovulnerabilidadesCVEexploitunixhistórico

sendmail y fingerd: Los Exploits del Morris Worm (1988)

Análisis técnico de los tres vectores de ataque que utilizó el Morris Worm en 1988: la puerta trasera de sendmail en modo debug, el buffer overflow de fingerd con gets(), y el cracking de contraseñas débiles en /etc/passwd. Los primeros exploits reales de red que demostraron la fragilidad de Unix.

MalwareIntel Research··14 min lectura·2 técnicas ATT&CK

El día que Internet descubrió que era vulnerable

El 2 de noviembre de 1988, Internet tenía menos de 60.000 máquinas conectadas. No existían firewalls, ni sistemas de detección de intrusos, ni el concepto mismo de "ciberseguridad" como disciplina. Los sistemas Unix confiaban unos en otros por diseño. Esa noche, un programa de 99 líneas de C compiladas cambió para siempre la relación entre la informática y la seguridad.

Robert Tappan Morris, estudiante de posgrado de 23 años en Cornell (e hijo del jefe científico del National Computer Security Center de la NSA), lanzó desde una máquina del MIT un gusano que utilizaba tres vectores de ataque distintos para propagarse por ARPANET. En cuestión de horas, entre 2.000 y 6.000 máquinas (un 10% de Internet) estaban paralizadas: no destruidas, sino ahogadas bajo cientos de copias del mismo proceso consumiendo toda la CPU y la memoria disponible.

El Morris Worm no contenía payload destructivo. No borraba archivos ni robaba datos. Su único objetivo era replicarse. Pero los exploits que utilizó para conseguirlo representan los primeros ataques de red documentados contra software real en producción. Analizarlos hoy, casi cuatro décadas después, revela los fundamentos de la explotación de software que siguen siendo relevantes.

Los tres vectores de ataque

El gusano no dependía de un solo exploit. Morris diseñó tres vectores de propagación independientes, cada uno dirigido a un servicio diferente de Unix. Si uno fallaba, los otros podían tener éxito. Esta redundancia, combinada con un error en el mecanismo de control, fue lo que hizo al gusano tan efectivo.

Vector 1: sendmail en modo debug

sendmail era (y sigue siendo, en versiones modernas) el servidor de correo dominante en Unix. En 1988, la versión distribuida con BSD incluía una funcionalidad de diagnóstico conocida como "modo debug" que muchos administradores dejaban habilitada.

El modo debug permitía a un cliente remoto enviar comandos que el servidor ejecutaría en el sistema. El protocolo SMTP ya define una interacción por comandos (HELO, MAIL FROM, RCPT TO, DATA), pero el modo debug añadía la posibilidad de que el campo de destinatario contuviese un comando de shell.

El gusano se conectaba al puerto 25 (SMTP) de la máquina objetivo y enviaba una secuencia de comandos:

DEBUG
MAIL FROM:<morris>
RCPT TO:|/bin/sed -e '1,/^$/d' | /bin/sh
DATA
cd /tmp
cat > x14481910.c << 'EOF'
[código fuente del gusano bootstrap]
EOF
cc -o x14481910 x14481910.c
./x14481910 [argumentos de conexión]
.
QUIT

La clave está en la línea RCPT TO. En lugar de un buzón de correo, el destinatario es un pipe a /bin/sed que elimina las cabeceras del mensaje y pasa el cuerpo a /bin/sh. El "correo" que llega es en realidad un script de shell que compila y ejecuta el bootstrap del gusano.

Este ataque no explotaba un bug en el sentido estricto. Explotaba una funcionalidad legítima de depuración que nunca debería haber estado expuesta a la red. Es el equivalente histórico de una API de administración sin autenticación, un patrón que sigue apareciendo en vulnerabilidades modernas.

Vector 2: fingerd y el buffer overflow que cambió la historia

El segundo vector es el que tiene mayor relevancia técnica, porque constituye uno de los primeros buffer overflows explotados en la vida real. El daemon fingerd proporcionaba información sobre los usuarios del sistema (nombre real, último login, si tenían correo sin leer) y escuchaba en el puerto 79.

La implementación de fingerd en 4.3 BSD leía el nombre de usuario solicitado de la red usando la función gets() de la biblioteca estándar de C:

char buf[512];
gets(buf);

gets() lee caracteres de la entrada estándar hasta encontrar un salto de línea o EOF. No acepta un parámetro de longitud máxima. Si la entrada tiene más de 512 bytes, los caracteres adicionales sobrescriben la memoria contigua en la pila: primero las variables locales, después el frame pointer guardado y finalmente la dirección de retorno de la función.

El Morris Worm enviaba exactamente 536 bytes al servicio fingerd:

  1. Bytes 1 a 512: llenaban el búfer legítimo
  2. Bytes 513 a 520: sobrescribían el frame pointer guardado
  3. Bytes 521 a 524: sobrescribían la dirección de retorno con una dirección que apuntaba al inicio del búfer (dentro de los primeros 512 bytes)

Dentro de esos primeros 512 bytes, el gusano había colocado un fragmento de código máquina (shellcode) específico para la arquitectura VAX. Este shellcode ejecutaba una sola llamada al sistema:

execve("/bin/sh", 0, 0);

Cuando la función principal de fingerd intentaba retornar, en lugar de volver al código que la había llamado, el procesador saltaba al shellcode dentro del búfer. El shellcode reemplazaba el proceso fingerd con un shell interactivo (/bin/sh) cuyos descriptores de archivo (entrada/salida) seguían conectados al socket de red. El resultado: el atacante remoto obtenía un shell con los privilegios de fingerd, que en la mayoría de sistemas se ejecutaba como root.

Desde ese shell, el gusano ejecutaba la misma secuencia de compilación y ejecución del bootstrap que usaba con sendmail.

¿Por qué funcionaba sin mitigaciones?

El exploit de fingerd funcionaba de manera limpia por razones que hoy parecen inconcebibles pero que eran el estado del arte en 1988:

Sin ASLR (Address Space Layout Randomization): cada proceso cargaba su pila en la misma dirección virtual. El atacante podía predecir exactamente dónde estaría el búfer y calcular la dirección de retorno con precisión.

Sin stack canaries: no existía ningún valor centinela entre las variables locales y la dirección de retorno que pudiera detectar la sobrescritura. El concepto de stack canary no aparecería hasta 1998 con StackGuard.

Sin DEP/NX (Data Execution Prevention): la pila era ejecutable. No había distinción a nivel de hardware entre páginas de memoria que contenían código y páginas que contenían datos. El procesador ejecutaba alegremente cualquier byte como instrucción, viniera de donde viniera.

Sin protección de la pila del compilador: los compiladores de la época no generaban código de verificación. gcc no tendría -fstack-protector hasta 2003.

fingerd ejecutado como root: en la filosofía Unix de 1988, los daemons de red se ejecutaban con privilegios máximos por simplicidad. El principio de mínimo privilegio existía en la teoría, pero raramente se aplicaba en la práctica.

Vector 3: contraseñas débiles y relaciones de confianza

El tercer vector no era un exploit técnico contra software vulnerable, sino un ataque contra la debilidad humana en la elección de contraseñas y contra el modelo de confianza entre máquinas Unix.

Cracking de /etc/passwd

En 1988, el archivo /etc/passwd era legible por todos los usuarios del sistema. Contenía los hashes de las contraseñas (cifradas con DES) junto al nombre de usuario, UID, GID, directorio home y shell. El gusano leía este archivo y ejecutaba un ataque de diccionario contra los hashes:

  1. Nombre de usuario como contraseña: probaba el propio login como password
  2. Variaciones del nombre: concatenaciones, inversiones, duplicaciones
  3. Diccionario interno: el gusano llevaba incorporado un diccionario de 432 palabras comunes
  4. Diccionario del sistema: usaba /usr/dict/words del propio sistema objetivo

El cifrado DES de la época era computacionalmente caro para 1988, pero el gusano tenía paciencia. Y las contraseñas de la era pre-política de seguridad eran notablemente débiles. Muchos usuarios usaban su propio nombre, "password", o palabras simples del diccionario.

Explotación de rsh y relaciones de confianza

Cuando el gusano conseguía descifrar una contraseña o acceder a una cuenta, leía dos archivos clave:

  • ~/.rhosts: listaba máquinas remotas desde las que el usuario podía conectarse sin contraseña
  • /etc/hosts.equiv: listaba máquinas que eran "equivalentes" al sistema local en términos de confianza

Estos archivos implementaban un modelo de confianza transitiva: si la máquina A confiaba en la máquina B, y el gusano comprometía B, podía usar rsh (remote shell) para ejecutar comandos en A sin necesidad de ningún exploit adicional. Era movimiento lateral puro, décadas antes de que el término existiera.

El protocolo rsh no cifraba las comunicaciones ni usaba autenticación criptográfica. La "autenticación" se basaba en la dirección IP de origen y el nombre de usuario. En una red donde los administradores configuraban relaciones de confianza amplias para facilitar el trabajo colaborativo entre universidades, este vector proporcionaba al gusano acceso a redes enteras de máquinas.

La cadena completa de infección

Cuando el gusano conseguía acceso por cualquiera de los tres vectores, la secuencia de infección seguía estos pasos:

  1. Bootstrap: compilar y ejecutar un programa pequeño en C (el "grappling hook") que establecía comunicación con la máquina de origen
  2. Transferencia del cuerpo: el bootstrap descargaba el binario principal del gusano desde la máquina que lo había infectado, usando una conexión TCP personalizada
  3. Verificación de arquitectura: el gusano principal determinaba si la máquina era VAX o Sun-3 y seleccionaba el binario apropiado
  4. Comprobación de reinfección: el gusano verificaba si ya había una copia ejecutándose en el sistema. Aquí estaba el error fatal.

El bug que convirtió un experimento en un desastre

Morris incluyó un mecanismo para evitar la reinfección: cuando el gusano detectaba que ya existía otra instancia en el sistema, decidía si terminarse o continuar. Pero temía que los administradores de sistemas pudieran crear un proceso falso que respondiera "sí, ya estoy aquí" para inmunizar sus máquinas. Para contrarrestar esa posible defensa, Morris programó al gusano para que ignorara la señal de "ya infectado" en 1 de cada 7 casos.

Esa probabilidad del 14,3% de reinfección fue catastrófica. En un sistema con múltiples vectores de ataque y conexiones a varias máquinas, las reinfecciones se acumulaban exponencialmente. Cada copia del gusano consumía CPU y memoria, y al haber docenas o cientos de copias ejecutándose simultáneamente, los sistemas se volvían inutilizables por agotamiento de recursos.

El contexto técnico: ¿por qué Unix era tan vulnerable?

La vulnerabilidad de Unix en 1988 no era un fallo puntual sino un reflejo de la filosofía de diseño de la época:

Confianza por defecto: Unix se diseñó en los laboratorios Bell de AT&T para un entorno de investigadores cooperativos. Las redes universitarias que formaban ARPANET compartían esa filosofía. Los servicios de red no estaban diseñados para resistir a usuarios maliciosos porque se asumía que todos los usuarios eran legítimos.

Privilegios excesivos: los daemons de red se ejecutaban como root porque era más simple y porque el modelo de amenazas no contemplaba la posibilidad de que un servicio de red fuera comprometido. La separación de privilegios era un concepto teórico, no una práctica estándar.

C como lenguaje dominante: el lenguaje C, creado en los mismos laboratorios Bell, ofrece control total sobre la memoria pero ninguna protección contra su mal uso. Las funciones de la biblioteca estándar como gets(), strcpy() y sprintf() operan sin límites de longitud porque la verificación se deja al programador. En la práctica, los programadores rara vez verificaban.

Sin separación red/sistema: no existía el concepto de firewall. Todos los servicios eran accesibles desde cualquier máquina de la red. La distinción entre "red interna" y "red externa" era inexistente porque Internet era una sola red de confianza.

Sin auditoría ni monitorización: no había logs centralizados, ni sistemas de detección de intrusos, ni alertas automatizadas. Los administradores descubrían la infección cuando sus máquinas se volvían inutilizables por consumo de recursos. No había herramientas para detectar tráfico anómalo porque nadie había definido qué era "anómalo".

Impacto y legado

Respuesta inmediata

El caos duró aproximadamente 72 horas. Los administradores de sistemas de universidades y centros de investigación se coordinaron por teléfono (ya que el correo electrónico dependía de las máquinas infectadas) para analizar el gusano y desarrollar parches. Peter Yee de la NASA envió el primer aviso público a la lista de correo tcp-ip. Equipos en Berkeley, MIT y Purdue descompilaron el gusano y crearon herramientas de detección y limpieza.

Consecuencias institucionales

Creación del CERT/CC: DARPA creó el Computer Emergency Response Team Coordination Center en Carnegie Mellon, el primer equipo formal de respuesta a incidentes. Este modelo se replicó en todo el mundo: FIRST (Forum of Incident Response and Security Teams) se fundó en 1990.

Primer proceso bajo la CFAA: Robert Tappan Morris fue el primer condenado bajo el Computer Fraud and Abuse Act (1986). Recibió tres años de libertad condicional, 400 horas de servicio comunitario y una multa de 10.050 dólares. No fue a prisión.

Deprecación de gets(): la función gets() comenzó su largo camino hacia la obsolescencia. Se marcó como obsoleta en C99 y se eliminó del estándar en C11, más de dos décadas después del Morris Worm. La función recomendada, fgets(), acepta un parámetro de longitud máxima.

Lecciones técnicas permanentes

Las lecciones del Morris Worm se codificaron gradualmente en la práctica de la ingeniería de software:

Principio de mínimo privilegio: los daemons de red dejaron de ejecutarse como root. fingerd se reescribió para ejecutarse con un usuario sin privilegios. Este principio es hoy fundamental en el diseño de servicios.

Eliminación de funciones inseguras: la familia de funciones sin límites (gets, strcpy, sprintf) se sustituyó por versiones con límites (fgets, strncpy, snprintf). Microsoft creó la familia _s (safe) en su implementación de C.

Defensa en profundidad: la idea de que un solo fallo no debería comprometer todo el sistema llevó al desarrollo de firewalls, segmentación de red, y eventualmente a las mitigaciones de memoria que hoy damos por sentadas (ASLR, DEP, stack canaries).

Seguridad como proceso, no como producto: antes del Morris Worm, la seguridad informática era un campo académico marginal. Después, se convirtió en una disciplina industrial con equipos dedicados, auditorías regulares y respuesta a incidentes organizada.

Relevancia para la seguridad moderna

Los tres vectores del Morris Worm (funcionalidad de debug expuesta, buffer overflow en servicios de red, y contraseñas débiles con relaciones de confianza excesivas) siguen siendo categorías activas de vulnerabilidad casi cuatro décadas después:

Funcionalidades de debug en producción: APIs de administración sin autenticación, consolas de depuración expuestas a Internet, endpoints de healthcheck que revelan información sensible. El patrón de sendmail debug se repite constantemente en aplicaciones modernas.

Buffer overflows: aunque las mitigaciones modernas (ASLR, DEP, stack canaries, CFI) han hecho la explotación mucho más difícil, los overflows siguen apareciendo en código C/C++ y siguen siendo explotables cuando se encadenan con otros bugs para evadir las mitigaciones.

Credenciales débiles y confianza excesiva: las relaciones de confianza entre máquinas (rsh, .rhosts) han sido reemplazadas por SSH con claves, pero el modelo de confianza lateral sigue siendo un problema. Active Directory, tokens de servicio, y service accounts con permisos excesivos son los herederos directos de /etc/hosts.equiv.

El Morris Worm demostró en 1988 lo que sigue siendo cierto hoy: la seguridad de un sistema no la determina su componente más fuerte, sino el más débil. Y cuando la defensa falla, la velocidad de propagación convierte un problema técnico en una crisis sistémica.

Análisis MITRE ATT&CK

Aunque el framework ATT&CK no existía en 1988, los vectores del Morris Worm se mapean directamente a técnicas modernas:

Vector del gusanoTécnica ATT&CKDescripción
sendmail debugT1190 (Exploit Public-Facing Application)Explotación de funcionalidad expuesta en servicio de red
sendmail debugT1059 (Command and Scripting Interpreter)Ejecución de comandos shell a través de SMTP
fingerd overflowT1190 (Exploit Public-Facing Application)Buffer overflow en daemon de red
/etc/passwd crackingT1110.002 (Password Cracking)Ataque de diccionario contra hashes
rsh/rhostsT1021.004 (Remote Services: SSH)Movimiento lateral por relaciones de confianza

Conclusión: el primer disparo

El Morris Worm fue el primer incidente que demostró que Internet no era solo una red de comunicación académica, sino una infraestructura vulnerable a ataques automatizados. Cada gusano de red que vino después (Code Red, Nimda, Slammer, Blaster, Sasser, Conficker) utilizó variaciones de los mismos principios: explotar servicios de red vulnerables para conseguir ejecución remota de código sin interacción del usuario.

La diferencia entre 1988 y hoy no es que los problemas hayan desaparecido. Es que ahora tenemos capas de mitigación (firewalls, ASLR, DEP, sandboxing, autenticación multifactor) que hacen la explotación más costosa. Pero el juego fundamental sigue siendo el mismo: encontrar el punto de entrada más débil y usarlo para ganar acceso a un sistema que confiaba demasiado en su entorno.

Técnicas MITRE ATT&CK referenciadas

Preguntas frecuentes

Artículos relacionados

Este contenido tiene fines exclusivamente educativos y de investigación en ciberseguridad defensiva. No se proporcionan binarios maliciosos ni payloads ejecutables. El uso indebido de esta información es responsabilidad exclusiva del usuario. Leer disclaimer completo.