ML/AIMLE·AIMachine Learning Engineering
Proyecto · Redes y ciberseguridad · Completado

Detección de anomalías en tráfico de red

Aprender cómo es el tráfico normal para detectar ataques, incluidos los que nunca se han visto antes: Isolation Forest y un autoencoder entrenados sin una sola etiqueta de ataque, evaluados con un presupuesto realista de falsas alarmas.

Dataset NSL-KDD (148.517 conexiones) · scikit-learn 1.8 y PyTorch · random_state = 42 · Todas las cifras se reproducen con notebooks/anomalias_red.py

  1. Problema
  2. Datos
  3. Exploración
  4. Preprocesado
  5. Detectores
  6. Evaluación
  7. Por tipo de ataque
  8. Presupuesto de alertas
  9. ¿Por qué salta la alarma?
  10. Despliegue
  11. Resultados y conclusiones

01Problema

Los sistemas de detección de intrusiones clásicos funcionan con firmas: reglas escritas a mano para cada ataque conocido. Son precisos, pero ciegos ante un ataque que nadie ha visto todavía. La alternativa es darle la vuelta al problema: aprender cómo es el tráfico normal y avisar de todo lo que se aparte de él.

PreguntaRespuesta en este proyecto
Tipo de problemaDetección de anomalías (novelty detection): los modelos se entrenan solo con tráfico normal, sin ninguna etiqueta de ataque
Para qué sirven las etiquetasSolo para evaluar al final, y para un modelo supervisado de referencia
Métrica principalPorcentaje de ataques detectados con un presupuesto fijo de falsas alarmas (1 % del tráfico normal)
Pregunta clave¿Detecta ataques que no existían cuando se entrenó?

02Datos

Dataset NSL-KDD (Universidad de New Brunswick, 2009), una versión depurada del clásico KDD Cup 99. Cada fila es una conexión de red descrita con 41 variables en cuatro grupos:

GrupoEjemplos
BásicasDuración, protocolo (tcp, udp, icmp), servicio (http, ftp, telnet…), estado de la conexión, bytes enviados y recibidos
De contenidoIntentos fallidos de inicio de sesión, acceso de root, ficheros creados
De tráfico (últimos 2 s)Conexiones al mismo host o servicio, porcentaje de errores SYN o de rechazo
De host (últimas 100 conexiones)Cuántas van al mismo host de destino y con qué servicio
EntrenamientoTest
Normal67.3439.711
DoS (denegación de servicio)45.9277.458
Probe (escaneos)11.6562.421
R2L (acceso remoto no autorizado)9952.887
U2R (escalada a root)5267
Total125.97322.544

Lo que hace interesante este dataset: el test incluye 17 tipos de ataque que no aparecen en el entrenamiento (mscan, apache2, snmpguess, processtable…): 3.750 conexiones, el 29,2 % de los ataques del test. Es la situación real de un sistema en producción.

Limitación: NSL-KDD viene de un tráfico simulado de 1998. Sirve para aprender y comparar métodos, no para proteger una red actual. Para eso se usan datasets modernos como CIC-IDS2017 o UNSW-NB15, con la misma metodología de este proyecto.

03Exploración

Cada familia de ataques deja una huella distinta en las variables (medianas del entrenamiento):

CategoríaBytes enviadosBytes recibidosConexiones al mismo host (2 s)Tasa de errores SYNMismo servicio
Normal23337940100 %
DoS00172100 %7 %
Probe1010100 %
R2L334010100 %
U2R2732.90710100 %

Un DoS como neptune (inundación SYN) abre cientos de conexiones medio abiertas contra el mismo host: se ve a la legua en las variables de tráfico. En cambio, un ataque R2L es una única conexión con un aspecto parecido al normal: la diferencia está en su contenido (por ejemplo, contraseñas probadas), no en su forma. Esta observación anticipa el resultado principal del proyecto.

Otros hallazgos: los bytes tienen colas enormes (el 39,2 % de las conexiones envía 0 bytes y el máximo pasa de 1.300 millones), el 35,5 % de los escaneos usa ICMP (ping) y la variable num_outbound_cmds vale siempre 0, así que se descarta.

04Preprocesado

  • Logaritmo (log1p) en las 13 variables de cola larga (bytes, duración, contadores), para que un millón de bytes no aplaste todo lo demás. Después, estandarización.
  • One-hot de protocolo, servicio y estado: 76 columnas en total.
  • El preprocesado se ajusta solo con tráfico normal: el detector no debe saber nada de los ataques.
  • El tráfico normal del entrenamiento se divide en 53.874 conexiones para ajustar los modelos y 13.469 para fijar el umbral de alarma.
notebooks/anomalias_red.py
colas = ["duration", "src_bytes", "dst_bytes", "count", "srv_count", ...]
prep = ColumnTransformer([
    ("colas", Pipeline([("log", FunctionTransformer(np.log1p, feature_names_out="one-to-one")),
                        ("esc", StandardScaler())]), colas),
    ("num", StandardScaler(), resto),
    ("cat", OneHotEncoder(handle_unknown="ignore"), ["protocol_type", "service", "flag"]),
], sparse_threshold=0)

normal = train[train["ataque"] == 0]
n_fit, n_val = train_test_split(normal, test_size=0.2, random_state=42)
prep.fit(n_fit)

05Detectores

Cuatro formas de medir «lo raro» que es una conexión, de la más simple a la más elaborada:

DetectorIdeaPuntuación de anomalía
Distancia a la mediaLo normal está cerca del centroDistancia euclídea al promedio del tráfico normal
PCAEl tráfico normal vive en un subespacio de 25 dimensiones (95 % de la varianza)Error al proyectar y reconstruir
Isolation ForestLo raro se aísla con pocos cortes al azarProfundidad media a la que queda aislado (300 árboles)
AutoencoderUna red que comprime y reconstruye; solo sabe reconstruir bien lo normalError de reconstrucción (red 76 → 64 → 16 → 64 → 76)
notebooks/anomalias_red.py
ae = nn.Sequential(nn.Linear(76, 64), nn.ReLU(), nn.Linear(64, 16), nn.ReLU(),
                   nn.Linear(16, 64), nn.ReLU(), nn.Linear(64, 76))
for epoca in range(60):                           # 18 s en CPU
    for b in lotes(Xf, 256):
        loss = ((ae(b) - b) ** 2).mean()          # aprende a copiar el tráfico normal
        opt.zero_grad(); loss.backward(); opt.step()

anomalia = ((ae(X) - X) ** 2).sum(1)              # lo que no sabe copiar, es raro

El umbral de cada detector se fija sin mirar ningún ataque: es el percentil 99 de las puntuaciones del tráfico normal de validación. Por construcción, deberían saltar falsas alarmas en el 1 % de las conexiones normales.

La idea del autoencoder, comprimir y reconstruir, es la misma que la de las redes neuronales del portal con una capa estrecha en medio. Isolation Forest es primo del Random Forest.

06Evaluación

DetectorROC-AUCDetecta (1 % de falsas alarmas)Con 2 %Con 5 %Falsas alarmas reales en test
Distancia a la media0,94126,0 %29,5 %61,8 %0,4 %
PCA0,9586,5 %39,9 %68,6 %0,6 %
Isolation Forest0,95057,1 %62,1 %65,2 %1,6 %
Autoencoder0,96335,0 %57,1 %84,0 %1,4 %
Isolation Forest + Autoencoder0,96160,7 %68,1 %78,9 %1,9 %

Primera lección: el ROC-AUC engaña aquí. Todos los detectores superan 0,94, pero con el presupuesto real de falsas alarmas el PCA solo detecta el 6,5 % de los ataques y el Isolation Forest, el 57,1 %. El AUC promedia todos los umbrales posibles, incluidos los que nadie usaría; en seguridad solo importa la esquina izquierda de la curva, la de muy pocas falsas alarmas.

0 %25 %50 %75 %100 %0,1 %0,5 %1 %2 %5 %10 %presupuesto de falsas alarmas (sobre el tráfico normal)
AutoencoderIsolation Forest
Porcentaje de ataques del test detectados según el presupuesto de falsas alarmas. Isolation Forest es mejor con presupuestos muy estrictos; el autoencoder, con presupuestos más holgados.

Segunda lección: los detectores se complementan. Por eso el modelo final los combina: cada puntuación se convierte en su percentil dentro del tráfico normal (así son comparables) y se promedian. Con un 1 % de presupuesto detecta el 60,7 %, más que cualquiera de los dos por separado, aunque también da algo más de falsas alarmas reales (1,9 % frente a 1,6 % y 1,4 %).

Tercera lección: el tráfico normal cambia. El umbral prometía un 1 % de falsas alarmas y en el test salen un 1,9 %: el tráfico normal del test no es idéntico al del entrenamiento. En producción pasa siempre; por eso hay que medir las falsas alarmas reales y reajustar el umbral.

07Por tipo de ataque

DoS74,3 %
Probe83,9 %
R2L7,1 %
U2R28,4 %
Porcentaje detectado de cada familia con el modelo final (1 % de presupuesto de falsas alarmas).

Tal como anticipaba la exploración: los ataques que cambian la forma del tráfico (DoS, escaneos) se detectan bien; los que se esconden en una conexión de aspecto normal (R2L: adivinar contraseñas, subir ficheros por FTP…) apenas se ven. Para esos hacen falta otras fuentes de datos, como los registros de autenticación de los servidores.

Comparación con un modelo supervisado

Un Random Forest entrenado con las etiquetas de ataque del entrenamiento tiene el mismo ROC-AUC (0,962), pero su comportamiento es muy distinto:

Ataques conocidosAtaques nuevosFalsas alarmas
Random Forest supervisado76,7 %31,2 %2,7 %
Isolation Forest + Autoencoder63,6 %53,9 %1,9 %

El supervisado es mejor con lo que ya ha visto y mucho peor con lo nuevo: aprendió «cómo son los ataques de 1998», no «cómo es lo normal». En la práctica se usan los dos juntos: el supervisado (o las firmas) para lo conocido y el detector de anomalías como red de seguridad para lo desconocido.

08Presupuesto de alertas

¿Cuántas falsas alarmas puede revisar el equipo de seguridad? Mueve el presupuesto y observa qué se detecta y qué precisión tendrían las alertas según lo frecuentes que sean los ataques en tu red.

–de los ataques detectados
–de las alertas son ataques reales
–falsas alarmas por cada millón de conexiones normales

La trampa de la tasa base. En el test, el 97,7 % de las alertas son ataques reales, pero es porque allí más de la mitad del tráfico es ataque. En una red real, donde los ataques son quizá el 0,1 % de las conexiones, solo el 3,1 % de las alertas sería un ataque real: el mismo fenómeno que los falsos positivos de un test médico del nivel 1. Con el presupuesto del 1 %, cada millón de conexiones normales genera unas 19.200 falsas alarmas.

Por eso ningún equipo revisa alertas sueltas: se agrupan por host y ventana de tiempo (un escaneo son cientos de conexiones anómalas seguidas desde la misma IP), se ordenan por puntuación y solo las más graves llegan a una persona.

09¿Por qué salta la alarma?

Una alerta sin explicación no sirve a un analista. El autoencoder permite ver qué variables no ha sabido reconstruir: son las que hacen rara a esa conexión.

Conexión (tipo real)Puntuación · umbralVariables que más contribuyen
guess_passwd (adivinar contraseñas)10,1 · 9,1Porcentaje de conexiones al mismo servicio del host (19 %), número de conexiones al host (13 %), duración (8 %), servicio telnet (7 %)
mscan (escaneo, ataque nuevo)9,7 · 9,1Tasa de conexiones rechazadas (22 %), tasa de errores SYN (13 %), cierre por reset (10 %), servicio telnet (9 %)

Las dos explicaciones tienen sentido para un experto: el escaneo provoca rechazos y conexiones incompletas; el ataque de contraseñas, sesiones telnet largas y repetidas. Y el escaneo mscan no existía en el entrenamiento.

10Despliegue

  1. Captura: un sensor (Zeek, NetFlow o CICFlowMeter) resume cada conexión en variables como las de este dataset.
  2. Puntuación: un servicio aplica el mismo preprocesado y los dos detectores. Es barato: en nuestra prueba, preprocesado incluido, puntuó unas 60.000 conexiones por segundo en una CPU.
  3. Agregación: las conexiones anómalas se agrupan por IP de origen y ventana de tiempo antes de crear una alerta.
  4. SIEM: las alertas llegan a la consola del equipo de seguridad con su puntuación y sus variables explicativas.
  5. Retroalimentación: lo que el analista confirma como ataque alimenta al modelo supervisado; lo que descarta ayuda a recalibrar el umbral.
notebooks/anomalias_red.py
UMBRAL = np.quantile(combinar(Xv), 0.99)               # 1 % de falsas alarmas previstas

def puntuar(conexiones: pd.DataFrame) -> pd.DataFrame:
    s = combinar(prep.transform(conexiones).astype(np.float32))
    return pd.DataFrame({"puntuacion": s.round(3), "alerta": s > UMBRAL})

#    puntuacion  alerta          real
#         0.990    True       neptune
#         0.378   False        normal
#         0.850   False   warezmaster     ← R2L: el punto débil
#         0.856   False  guess_passwd

Mantenimiento: el tráfico normal cambia (nuevas aplicaciones, teletrabajo, un servicio en la nube), y con él suben las falsas alarmas, como ya pasa entre el entrenamiento y el test. Hay que medir cada semana la tasa real de falsas alarmas y reentrenar con tráfico normal reciente, comprobando antes que ese tráfico no esté contaminado por un ataque en curso.

11Resultados y conclusiones

Resultado
Modelo finalIsolation Forest + autoencoder, entrenados solo con tráfico normal
Detección (1 % de falsas alarmas)60,7 % de los ataques: DoS 74,3 %, escaneos 83,9 %, R2L 7,1 %, U2R 28,4 %
Ataques nuevos53,9 % detectados, frente al 31,2 % de un Random Forest supervisado
Falsas alarmas reales1,9 % en test (previsto: 1 %) por cambios en el tráfico normal
Con ataques raros (0,1 %)Solo el 3,1 % de las alertas sería un ataque: hay que agregar y priorizar

Lo que enseña este proyecto

  1. Evalúa en el punto de operación. Un ROC-AUC de 0,96 puede esconder un detector que casi no detecta nada con un presupuesto realista.
  2. Lo no supervisado protege frente a lo desconocido, a cambio de detectar peor lo conocido. Supervisado y no supervisado se complementan.
  3. Los datos ponen el límite. Si un ataque no cambia las variables que mides (R2L), ningún modelo lo verá: hacen falta otras fuentes de datos.
  4. La tasa base manda. Con ataques raros, incluso un buen detector genera sobre todo falsas alarmas.

Código completo, ejecutable en Colab (descarga NSL-KDD de una réplica pública):