saltar al contenido
volver al mapa
práctica
etapa: operate
3 min de lectura

A09 Security Logging and Alerting Failures

No puedes responder a lo que no puedes ver. Los logs sin alertas son un archivo; las alertas sin buenos logs son ruido. Ambos deben existir, y ninguno puede ser inyectable ni estar lleno de secretos.

OpenTelemetryLoki / Elastic / SplunkSigma rulesFalcoWazuhfail2ban

Qué es

Esta categoría cubre el logging, monitoreo y alertas de seguridad ausentes o deficientes: logins y fallos de control de acceso que no se registran, logs que viven solo en la máquina que controla el atacante, eventos sin ninguna alerta detrás y logs que filtran datos sensibles o que pueden falsificarse con la entrada del usuario.

Es el A09 en el :2025, con un nombre que enfatiza la alerta: un log por el que nadie recibe un aviso no es un control.

Cómo funciona el ataque

  • Una tanda de con miles de logins fallidos no se registra, o se registra y nunca se revisa.
  • Un atacante con un punto de apoyo borra los logs locales; sin copia central, el rastro desaparece.
  • : un nombre de usuario que contiene un salto de línea escribe una línea falsa de "login exitoso".
  • Los logs registran contraseñas, tokens o números de tarjeta, convirtiendo el almacén de logs en el objetivo más rico.

Código vulnerable

Logging de texto libre con entrada cruda del usuario, un secreto en el mensaje y ningún registro del fallo que importa.

login.pypython
def login(email, password):
    print(f"login attempt {email} / {password}")          # secreto en el log, posible inyección de saltos de línea
    user = authenticate(email, password)
    if not user:
        return None                                        # fallo no registrado
    return user
login.jsjavascript
console.log("login failed for " + req.body.email + " with token " + req.headers.authorization);

La corrección

Registra los eventos de seguridad como datos estructurados con nombres de campo estables: quién, qué, desde dónde, resultado y un . Nunca registres secretos; codifica la entrada del usuario para que no pueda falsificar líneas. Envía los logs fuera del host de inmediato y asocia reglas de alerta a los eventos que importan (picos de fallos, cambios de privilegio, acceso denegado a objetos sensibles).

login.pypython
import json, logging
log = logging.getLogger("security")

def event(action, outcome, **fields):
    log.info(json.dumps({"action": action, "outcome": outcome, **fields}))   # json.dumps hace escape de los saltos de línea

def login(email, password, ip, request_id):
    user = authenticate(email, password)
    if not user:
        event("login", "failure", email=email, ip=ip, request_id=request_id)   # sin contraseña, sin token
        return None
    event("login", "success", user_id=user.id, ip=ip, request_id=request_id)
    return user
login.js (pino)javascript
req.log.warn({ action: "login", outcome: "failure", email: req.body.email, ip: req.ip }, "auth");
// la redaction de pino mantiene los secretos fuera aunque alguien registre el objeto completo
const logger = pino({ redact: ["req.headers.authorization", "*.password", "*.token"] });
alert.yml (regla estilo Prometheus)yaml
- alert: LoginFailureSpike
  expr: sum(rate(auth_events_total{action="login",outcome="failure"}[5m])) > 5
  for: 5m
  labels: { severity: page }
  annotations:
    summary: "Fallos de login por encima de lo normal: posible credential stuffing"

Demuestra la corrección

Prueba que el evento se emite con los campos correctos, que no aparece ningún secreto y que la entrada no puede falsificar una línea. Luego prueba el propio camino de la alerta: reproduce una ráfaga y confirma que alguien recibe la notificación. Una alerta sin probar es una esperanza.

test_logging.py (pytest)python
def test_failed_login_is_logged_without_secrets(caplog):
    with caplog.at_level("INFO", logger="security"):
        login("bob@x.io\nlogin success", "hunter2", "203.0.113.9", "req-1")
    text = caplog.text
    assert "hunter2" not in text                      # sin secreto
    assert text.count("\n") == 1                       # el salto de línea inyectado no creó una línea
    assert '"outcome": "failure"' in text

def test_alert_fires_on_burst(alert_harness):
    for _ in range(200):
        login("bob@x.io", "bad", "203.0.113.9", "r")
    assert alert_harness.fired("LoginFailureSpike")

Checklist

  • Registra: autenticación, fallos de control de acceso, fallos de validación de entrada, acciones privilegiadas y cambios en la configuración de seguridad.
  • Usa un formato estructurado consistente; incluye hora, actor, acción, objetivo, resultado, origen y un correlation id.
  • Envía los logs rápidamente a un almacén central de solo anexado; restringe quién puede alterarlos o borrarlos.
  • No registres secretos, tokens ni números completos de tarjeta o documento; codifica la entrada no confiable.
  • Define alertas y un responsable para cada una; escribe la respuesta en un runbook; prueba el camino de la alerta.
  • Define una retención que cubra las necesidades legales y de investigación.
OWASP Top 10La lista de referencia más conocida de riesgos de seguridad en aplicaciones web (A01 control de acceso roto, A02 fallas criptográficas, A03 inyección...), revisada cada pocos años por la comunidad OWASP. Es un mapa educativo de categorías de riesgo, no un checklist de herramientas ni una certificación.ver en el glosario →Credential stuffingAtaque que prueba en masa pares usuario/contraseña filtrados de otros servicios. Firma: muchos fallos de inicio de sesión distribuidos en muchas IPs, con pocos intentos por cuenta.ver en el glosario →Log injectionLa entrada del usuario que contiene saltos de línea o caracteres de control falsifica entradas de log. Codifica la entrada o usa logging estructurado (JSON).ver en el glosario →Correlation idrequest idUn id único asociado a una solicitud y a cada línea de log que genera, que se muestra al usuario en los mensajes de error para que soporte encuentre los detalles sin filtrarlos.ver en el glosario →

conexiones en el mapa

actualizado el 14/10/2026 · fuentes para profundizar · reportar un error

en otros dominios

Estudia antes

Desbloquea

pon a prueba lo que entendiste

Cargando preguntas…