Independiente / Caso práctico
Tras la pista de un acceso SSH
Demostración de DFIR: construir una cronología, correlacionar intentos fallidos y distinguir evidencias de hipótesis con registros sintéticos.
En este writeup
Contexto y objetivo
Un equipo recibe una alerta por varios intentos de autenticación SSH, seguidos de un acceso aceptado. El objetivo es reconstruir la secuencia y definir qué evidencia adicional permitiría decidir si ocurrió un incidente.
Trabajaremos con siete líneas inventadas. Usaremos Python 3 y su biblioteca estándar: no necesitamos instalar un servidor SSH, probar contraseñas ni conectarnos a las direcciones del ejemplo. Crea una carpeta vacía para guardar los archivos del ejercicio.
La pregunta inicial es concreta: ¿qué origen presenta fallos y después una autenticación aceptada, para qué cuenta y en qué intervalo? Responderla permite priorizar la investigación; todavía no atribuye el acceso a una persona.
Evidencia y herramientas
Guarda este código como investigacion.py. Al ejecutarlo, creará auth-demo.log en la carpeta actual. Las marcas temporales utilizan UTC y un formato normalizado para simplificar la lectura; un registro real puede variar según el sistema y su configuración.
from collections import Counter
from hashlib import sha256
from pathlib import Path
import re
sample = """2026-09-09T10:00:00Z lab sshd[101]: Failed password for invalid user admin from 192.0.2.44 port 51001 ssh2
2026-09-09T10:00:10Z lab sshd[102]: Failed password for invalid user admin from 192.0.2.44 port 51002 ssh2
2026-09-09T10:00:20Z lab sshd[103]: Failed password for invalid user admin from 192.0.2.44 port 51003 ssh2
2026-09-09T10:01:00Z lab sshd[104]: Failed password for analyst from 192.0.2.44 port 51004 ssh2
2026-09-09T10:01:10Z lab sshd[105]: Failed password for analyst from 192.0.2.44 port 51005 ssh2
2026-09-09T10:02:00Z lab sshd[106]: Accepted password for analyst from 192.0.2.44 port 51006 ssh2
2026-09-09T10:03:00Z lab sshd[107]: Accepted password for backup from 192.0.2.18 port 51007 ssh2
"""
path = Path("auth-demo.log")
path.write_bytes(sample.encode("utf-8"))
raw = path.read_bytes()
print("SHA-256:", sha256(raw).hexdigest())
pattern = re.compile(
r"(Failed|Accepted) password for (?:invalid user )?(\S+) "
r"from (\S+) port"
)
failures = Counter()
accepted = []
unparsed = []
for line in raw.decode("utf-8").splitlines():
match = pattern.search(line)
if not match:
unparsed.append(line)
continue
result, user, source = match.groups()
if result == "Failed":
failures[(source, user)] += 1
else:
accepted.append((line.split()[0], source, user))
for (source, user), count in sorted(failures.items()):
print("FALLOS", source, user, count)
for timestamp, source, user in accepted:
print("ACEPTADO", timestamp, source, user)
print("LINEAS SIN PARSEAR", len(unparsed))
Ejecuta el análisis desde esa carpeta:
python investigacion.py
Si tu sistema ofrece Python mediante python3 o py, sustituye únicamente el nombre del ejecutable.
El hash identifica los bytes analizados. En una investigación real registraríamos además la procedencia, la fecha de adquisición y las transformaciones realizadas. Un hash por sí solo no acredita quién produjo el archivo ni que su contenido sea verdadero.
Análisis y cronología
Después del hash, el resultado esperado es:
FALLOS 192.0.2.44 admin 3
FALLOS 192.0.2.44 analyst 2
ACEPTADO 2026-09-09T10:02:00Z 192.0.2.44 analyst
ACEPTADO 2026-09-09T10:03:00Z 192.0.2.18 backup
LINEAS SIN PARSEAR 0
| Hora UTC | Observación |
|---|---|
| 10:00:00–10:00:20 | Tres fallos para admin desde 192.0.2.44. |
| 10:01:00–10:01:10 | Dos fallos para analyst desde el mismo origen. |
| 10:02:00 | Autenticación aceptada para analyst. |
| 10:03:00 | Autenticación aceptada para backup desde otro origen. |
La secuencia principal dura dos minutos. Es compatible con varias explicaciones: pruebas de credenciales, errores de un usuario legítimo o actividad automatizada. El archivo no permite elegir entre ellas.
Accepted representa una autenticación aceptada. La preparación de la sesión y la ejecución posterior son etapas adicionales; necesitamos otros registros para reconstruirlas. El manual de OpenSSH describe esa secuencia.
Hallazgos y límites
Podemos afirmar que un origen acumula cinco fallos sobre dos cuentas y después autentica a analyst. No podemos afirmar que obtuvo privilegios, ejecutó comandos o extrajo información. Tampoco debemos considerar legítima la cuenta backup solo por su nombre.
El contador de líneas sin parsear evita ocultar silenciosamente eventos que nuestro patrón no entiende. Este analizador cubre el formato del ejercicio; necesitaría ampliarse para claves públicas, IPv6, otros mensajes y archivos con marcas temporales diferentes.
Implicación defensiva y próximos pasos
La detección útil correlacionaría fallos y accesos por origen, cuenta y ventana temporal. El umbral debe considerar el comportamiento habitual: un número fijo de fallos produce alertas difíciles de interpretar sin contexto.
Para continuar, solicitaríamos registros de sesión, telemetría de procesos, inventario de cuentas y validación del propietario. Mantendríamos por separado hechos, hipótesis y evidencia pendiente.
Las medidas preventivas incluyen revisar quién puede autenticarse y qué métodos necesita. Las opciones de autenticación y acceso están documentadas en sshd_config; cualquier cambio operativo requiere probar que los administradores conservan acceso.
Aprendizaje
Una cronología pequeña y verificable ayuda a formular mejores preguntas. El resultado del ejercicio es una hipótesis priorizada con límites explícitos, no una declaración de compromiso basada únicamente en una IP.