Intermedioincident responseplaybooksNISTautomatizaciónYAMLDevSecOps

Playbooks de Respuesta a Incidentes como Código: Diseño e Implementación

Diseño e implementación de playbooks de respuesta a incidentes como código: estructura NIST SP 800-61, templates YAML para ransomware, phishing, malware y acceso no autorizado, control de versiones, testing de playbooks y matriz de niveles de automatización.

MalwareIntel Research··13 min lectura

Por qué los playbooks deben ser código

La mayoría de SOCs tienen playbooks en documentos Word, wikis Confluence o PDFs que nadie lee. Cuando llega un incidente real, el analista abre el documento, busca el paso relevante, improvisa lo que no entiende y cierra el ticket. El playbook no se actualiza hasta la próxima auditoría.

Los playbooks como código cambian este paradigma:

  • Versionados: cada cambio tiene un commit con autor, fecha y motivo. Puedes ver cómo evolucionó el playbook y hacer rollback si una actualización introduce problemas.
  • Ejecutables: un motor de automatización interpreta el playbook y ejecuta los pasos que pueden automatizarse. El analista solo interviene donde se requiere criterio humano.
  • Testeables: puedes simular un incidente en un entorno de pruebas y verificar que el playbook funciona antes de necesitarlo en producción.
  • Medibles: cada ejecución genera métricas (tiempo por paso, pasos automatizados vs manuales, resultado).

Estructura de un playbook: NIST SP 800-61

NIST SP 800-61 (Computer Security Incident Handling Guide) define cuatro fases de respuesta a incidentes. Nuestros playbooks siguen esta estructura:

  1. Preparación: herramientas, accesos y configuraciones necesarias antes del incidente
  2. Detección y Análisis: cómo se detecta el incidente y qué triaje inicial se realiza
  3. Contención, Erradicación y Recuperación: acciones para limitar el daño, eliminar la amenaza y restaurar operaciones
  4. Actividad Post-Incidente: lecciones aprendidas y mejoras

Formato YAML del playbook

# playbook_schema.yaml — Estructura base de un playbook
---
metadata:
  id: PB-XXX-NNN
  name: "Nombre descriptivo del playbook"
  version: "1.0.0"
  author: "Equipo SOC"
  created: "2026-01-15"
  updated: "2026-06-01"
  severity_range: [medium, critical]
  mitre_techniques:
    - T1566     # Phishing
    - T1486     # Data Encrypted for Impact
  nist_phase: "all"
  tags:
    - ransomware
    - incident-response
  sla:
    triage: 15m
    containment: 1h
    eradication: 24h
    recovery: 48h

trigger:
  type: alert          # alert | manual | scheduled
  source: SIEM
  conditions:
    - field: alert.category
      operator: equals
      value: "malware"
    - field: alert.severity
      operator: gte
      value: "high"

phases:
  - name: triage
    automation_level: full    # full | semi | manual
    steps: []

  - name: containment
    automation_level: semi
    steps: []

  - name: eradication
    automation_level: manual
    steps: []

  - name: recovery
    automation_level: semi
    steps: []

  - name: post_mortem
    automation_level: manual
    steps: []

Estructura de un paso (step)

steps:
  - id: step_001
    name: "Recopilar información del host afectado"
    description: "Obtener hostname, IP, usuario, SO y estado del EDR"
    automation_level: full
    timeout: 60s
    tool: edr_api
    action: get_host_info
    parameters:
      host_id: "{{ trigger.host_id }}"
    output_variable: host_info
    on_success: step_002
    on_failure: step_001_fallback
    on_timeout: step_001_fallback

  - id: step_001_fallback
    name: "Recopilar info del host (manual)"
    automation_level: manual
    instructions: >
      Accede al EDR y busca el host manualmente.
      Documenta: hostname, IP, usuario, SO, ultimo check-in.
    notify:
      channel: slack
      target: "#soc-incidents"
      message: >
        Fallo en recopilacion automatica del host
        {{ trigger.host_id }}. Requiere accion manual.
    checkpoint: true
    output_variable: host_info
    on_complete: step_002

Playbook 1: Ransomware

El playbook más crítico de cualquier SOC. La velocidad de contención determina el impacto.

---
metadata:
  id: PB-IR-001
  name: "Respuesta a incidente de ransomware"
  version: "2.1.0"
  severity_range: [high, critical]
  mitre_techniques:
    - T1486     # Data Encrypted for Impact
    - T1490     # Inhibit System Recovery
    - T1489     # Service Stop
    - T1071     # Application Layer Protocol (C2)
  sla:
    triage: 15m
    containment: 30m
    eradication: 24h
    recovery: 72h

trigger:
  type: alert
  source: EDR
  conditions:
    - field: alert.category
      operator: in
      value: ["ransomware", "encryption", "ransom_note"]

phases:
  - name: triage
    automation_level: full
    steps:
      - id: t_001
        name: "Confirmar alerta de ransomware"
        automation_level: full
        tool: edr_api
        action: get_alert_details
        parameters:
          alert_id: "{{ trigger.alert_id }}"
        output_variable: alert_details

      - id: t_002
        name: "Identificar host y usuario afectado"
        automation_level: full
        tool: edr_api
        action: get_host_info
        parameters:
          host_id: "{{ alert_details.host_id }}"
        output_variable: host_info

      - id: t_003
        name: "Verificar extensiones de archivos cifrados"
        automation_level: full
        tool: edr_api
        action: search_files
        parameters:
          host_id: "{{ alert_details.host_id }}"
          pattern: "*.encrypted|*.locked|*.crypt|*DECRYPT*"
        output_variable: encrypted_files

      - id: t_004
        name: "Buscar nota de rescate"
        automation_level: full
        tool: edr_api
        action: search_files
        parameters:
          host_id: "{{ alert_details.host_id }}"
          pattern: "*README*ransom*|*DECRYPT*|*RECOVER*"
        output_variable: ransom_note

      - id: t_005
        name: "Notificar al equipo"
        automation_level: full
        tool: slack
        action: send_message
        parameters:
          channel: "#soc-critical"
          message: >
            RANSOMWARE DETECTADO
            Host: {{ host_info.hostname }}
            ({{ host_info.ip }})
            Usuario: {{ host_info.user }}
            Archivos cifrados: {{ encrypted_files.count }}
            Familia: {{ alert_details.malware_family }}

  - name: containment
    automation_level: semi
    steps:
      - id: c_001
        name: "Aislar host de la red"
        automation_level: full
        tool: edr_api
        action: isolate_host
        parameters:
          host_id: "{{ alert_details.host_id }}"
          reason: "Ransomware containment - PB-IR-001"
        critical: true

      - id: c_002
        name: "Buscar movimiento lateral"
        automation_level: full
        tool: siem_api
        action: search
        parameters:
          query: >
            src_ip="{{ host_info.ip }}"
            AND (dest_port=445 OR dest_port=135
            OR dest_port=3389 OR dest_port=5985)
          timerange: "-2h"
        output_variable: lateral_movement

      - id: c_003
        name: "Aislar hosts contactados"
        automation_level: semi
        checkpoint: true
        instructions: >
          Revisar los {{ lateral_movement.count }} hosts
          contactados. Confirmar aislamiento de los que
          muestren signos de compromiso.
        tool: edr_api
        action: isolate_hosts
        parameters:
          host_ids: "{{ lateral_movement.dest_hosts }}"

      - id: c_004
        name: "Bloquear IOCs en firewall"
        automation_level: full
        tool: firewall_api
        action: block_iocs
        parameters:
          ips: "{{ alert_details.c2_ips }}"
          domains: "{{ alert_details.c2_domains }}"
          duration: "permanent"

      - id: c_005
        name: "Deshabilitar cuenta comprometida"
        automation_level: semi
        checkpoint: true
        instructions: >
          Confirmar deshabilitacion de la cuenta
          {{ host_info.user }} en Active Directory.
          Verificar que no es una cuenta de servicio critica.
        tool: ad_api
        action: disable_account
        parameters:
          username: "{{ host_info.user }}"

  - name: eradication
    automation_level: manual
    steps:
      - id: e_001
        name: "Identificar familia de ransomware"
        automation_level: semi
        tool: malware_analysis
        action: identify_family
        parameters:
          ransom_note: "{{ ransom_note.content }}"
          file_extension: "{{ encrypted_files.extension }}"
          hashes: "{{ alert_details.file_hashes }}"

      - id: e_002
        name: "Buscar decryptor disponible"
        automation_level: full
        tool: http_request
        action: check_nomoreransom
        parameters:
          family: "{{ e_001.family_name }}"
        output_variable: decryptor_available

      - id: e_003
        name: "Recopilar evidencia forense"
        automation_level: manual
        instructions: >
          1. Crear imagen forense del disco del host
          2. Exportar logs del EDR (ultimas 48h)
          3. Capturar memoria RAM si el host sigue encendido
          4. Documentar timeline de la infeccion
          5. Preservar la nota de rescate

      - id: e_004
        name: "Limpiar host o restaurar desde imagen"
        automation_level: manual
        instructions: >
          Opcion A: Si el decryptor existe, aplicarlo.
          Opcion B: Restaurar desde backup limpio.
          Opcion C: Reimagen completa del sistema.
          En todos los casos, verificar que la amenaza
          esta eliminada antes de reconectar a la red.

  - name: recovery
    automation_level: semi
    steps:
      - id: r_001
        name: "Restaurar datos desde backup"
        automation_level: manual
        instructions: >
          Verificar integridad del backup antes de restaurar.
          Confirmar que el backup es anterior a la infeccion.
          Restaurar datos en el sistema limpio.

      - id: r_002
        name: "Reconectar host a la red"
        automation_level: semi
        checkpoint: true
        tool: edr_api
        action: unisolate_host
        parameters:
          host_id: "{{ alert_details.host_id }}"

      - id: r_003
        name: "Restablecer credenciales"
        automation_level: semi
        tool: ad_api
        action: reset_password
        parameters:
          username: "{{ host_info.user }}"
        instructions: >
          Comunicar al usuario el cambio de contrasena.
          Verificar que no hay sesiones activas con
          credenciales antiguas.

      - id: r_004
        name: "Monitoreo intensivo 72h"
        automation_level: full
        tool: siem_api
        action: create_watchlist
        parameters:
          hosts: ["{{ host_info.hostname }}"]
          duration: "72h"
          alert_threshold: "low"

  - name: post_mortem
    automation_level: manual
    steps:
      - id: pm_001
        name: "Documentar timeline completa"
        instructions: >
          Crear documento con:
          - Fecha y hora de cada fase
          - Vector de entrada inicial
          - Sistemas afectados
          - Datos comprometidos
          - Acciones de contencion tomadas
          - Tiempo de respuesta por fase

      - id: pm_002
        name: "Lecciones aprendidas"
        instructions: >
          Reunion post-mortem con el equipo (max 48h despues).
          Documentar: que funciono, que no, que cambiar
          en el playbook para la proxima vez.

      - id: pm_003
        name: "Actualizar playbook"
        instructions: >
          Aplicar las lecciones aprendidas:
          commit en Git con los cambios al playbook.
          Tag con nueva version.

Playbook 2: Phishing confirmado

---
metadata:
  id: PB-IR-002
  name: "Respuesta a phishing confirmado"
  version: "1.3.0"
  severity_range: [medium, high]
  mitre_techniques:
    - T1566.001  # Spearphishing Attachment
    - T1566.002  # Spearphishing Link
    - T1204.001  # User Execution: Malicious Link
  sla:
    triage: 10m
    containment: 30m
    eradication: 4h

phases:
  - name: triage
    automation_level: full
    steps:
      - id: t_001
        name: "Analizar email reportado"
        tool: email_analyzer
        action: full_analysis
        parameters:
          email_id: "{{ trigger.email_id }}"
        output_variable: email_analysis

      - id: t_002
        name: "Verificar si el usuario hizo clic"
        tool: proxy_logs
        action: search
        parameters:
          user: "{{ trigger.reporter }}"
          urls: "{{ email_analysis.urls }}"
          timerange: "-24h"
        output_variable: click_evidence

      - id: t_003
        name: "Verificar si otros usuarios recibieron el mismo email"
        tool: email_gateway
        action: search_similar
        parameters:
          sender: "{{ email_analysis.sender }}"
          subject: "{{ email_analysis.subject }}"
          timerange: "-48h"
        output_variable: affected_users

  - name: containment
    automation_level: full
    steps:
      - id: c_001
        name: "Bloquear sender en email gateway"
        tool: email_gateway
        action: block_sender
        parameters:
          sender: "{{ email_analysis.sender }}"

      - id: c_002
        name: "Eliminar email de todos los buzones"
        tool: email_gateway
        action: purge_message
        parameters:
          message_id: "{{ email_analysis.message_id }}"

      - id: c_003
        name: "Bloquear URLs en proxy"
        tool: proxy_api
        action: block_urls
        parameters:
          urls: "{{ email_analysis.malicious_urls }}"

      - id: c_004
        name: "Si usuario hizo clic, resetear credenciales"
        automation_level: semi
        condition: "{{ click_evidence.clicked == true }}"
        checkpoint: true
        tool: ad_api
        action: reset_password
        parameters:
          username: "{{ trigger.reporter }}"

Playbook 3: Detección de malware

---
metadata:
  id: PB-IR-003
  name: "Respuesta a deteccion de malware en endpoint"
  version: "1.2.0"
  severity_range: [medium, high]
  mitre_techniques:
    - T1059     # Command and Scripting Interpreter
    - T1055     # Process Injection
    - T1547     # Boot or Logon Autostart Execution

phases:
  - name: triage
    automation_level: full
    steps:
      - id: t_001
        name: "Obtener detalles de la deteccion"
        tool: edr_api
        action: get_detection
        parameters:
          detection_id: "{{ trigger.detection_id }}"
        output_variable: detection

      - id: t_002
        name: "Clasificar la amenaza"
        tool: threat_intel
        action: lookup_hash
        parameters:
          sha256: "{{ detection.file_hash }}"
        output_variable: threat_info

      - id: t_003
        name: "Verificar si es un falso positivo conocido"
        tool: whitelist
        action: check
        parameters:
          hash: "{{ detection.file_hash }}"
          path: "{{ detection.file_path }}"
          signer: "{{ detection.signer }}"
        output_variable: fp_check

  - name: containment
    automation_level: semi
    steps:
      - id: c_001
        name: "Cuarentenar el archivo"
        condition: "{{ fp_check.is_false_positive == false }}"
        tool: edr_api
        action: quarantine_file
        parameters:
          host_id: "{{ detection.host_id }}"
          file_path: "{{ detection.file_path }}"

      - id: c_002
        name: "Matar proceso malicioso"
        condition: "{{ detection.process_running == true }}"
        tool: edr_api
        action: kill_process
        parameters:
          host_id: "{{ detection.host_id }}"
          pid: "{{ detection.process_id }}"

Playbook 4: Acceso no autorizado

---
metadata:
  id: PB-IR-004
  name: "Respuesta a acceso no autorizado"
  version: "1.1.0"
  severity_range: [high, critical]
  mitre_techniques:
    - T1078     # Valid Accounts
    - T1110     # Brute Force
    - T1021     # Remote Services

phases:
  - name: triage
    automation_level: full
    steps:
      - id: t_001
        name: "Recopilar eventos de autenticacion"
        tool: siem_api
        action: search
        parameters:
          query: >
            user="{{ trigger.username }}"
            AND (EventCode=4624 OR EventCode=4625
            OR EventCode=4648 OR EventCode=4672)
          timerange: "-24h"
        output_variable: auth_events

      - id: t_002
        name: "Analizar patrones de acceso"
        tool: ueba
        action: analyze_user
        parameters:
          username: "{{ trigger.username }}"
          timerange: "-7d"
        output_variable: behavior_analysis

      - id: t_003
        name: "Geolocalizacion de IPs de acceso"
        tool: enrichment
        action: geolocate_ips
        parameters:
          ips: "{{ auth_events.source_ips }}"
        output_variable: geo_data

  - name: containment
    automation_level: semi
    steps:
      - id: c_001
        name: "Forzar cierre de sesiones activas"
        checkpoint: true
        tool: ad_api
        action: revoke_sessions
        parameters:
          username: "{{ trigger.username }}"

      - id: c_002
        name: "Bloquear IPs sospechosas"
        tool: firewall_api
        action: block_ips
        parameters:
          ips: "{{ behavior_analysis.anomalous_ips }}"

Control de versiones

Los playbooks como código se gestionan en Git como cualquier otro código:

playbooks/
  PB-IR-001-ransomware.yaml
  PB-IR-002-phishing.yaml
  PB-IR-003-malware.yaml
  PB-IR-004-unauthorized-access.yaml
  CHANGELOG.md
  tests/
    test_pb_ir_001.py
    test_pb_ir_002.py
    fixtures/
      sample_ransomware_alert.json
      sample_phishing_email.json

Cada cambio sigue un proceso:

  1. Branch con nombre descriptivo: feat/pb-001-add-lateral-check
  2. Modificar el playbook
  3. Actualizar la version en metadata (semver)
  4. Ejecutar tests
  5. Pull request con revisión del equipo
  6. Merge a main
  7. Tag con la nueva versión

Testing de playbooks

Testear un playbook no es testear código: es simular un incidente y verificar que el playbook responde correctamente.

"""
test_playbook.py — Tests de playbooks de respuesta
"""
import yaml
import pytest


def load_playbook(path: str) -> dict:
    with open(path) as f:
        return yaml.safe_load(f)


class TestRansomwarePlaybook:
    """Tests del playbook PB-IR-001."""

    @pytest.fixture
    def playbook(self):
        return load_playbook("playbooks/PB-IR-001-ransomware.yaml")

    def test_has_all_phases(self, playbook):
        phase_names = [p["name"] for p in playbook["phases"]]
        required = [
            "triage", "containment",
            "eradication", "recovery", "post_mortem",
        ]
        for phase in required:
            assert phase in phase_names, (
                f"Missing phase: {phase}"
            )

    def test_containment_isolates_host(self, playbook):
        containment = next(
            p for p in playbook["phases"]
            if p["name"] == "containment"
        )
        actions = [
            s.get("action") for s in containment["steps"]
        ]
        assert "isolate_host" in actions, (
            "Containment must isolate the affected host"
        )

    def test_sla_defined(self, playbook):
        sla = playbook["metadata"]["sla"]
        assert "triage" in sla
        assert "containment" in sla

    def test_no_step_without_timeout_or_checkpoint(
        self, playbook
    ):
        for phase in playbook["phases"]:
            for step in phase["steps"]:
                if step.get("automation_level") == "full":
                    assert "timeout" in step or "on_timeout" in step, (
                        f"Step {step['id']} needs timeout"
                    )

    def test_critical_steps_have_fallback(self, playbook):
        for phase in playbook["phases"]:
            for step in phase["steps"]:
                if step.get("critical"):
                    assert "on_failure" in step, (
                        f"Critical step {step['id']} "
                        f"needs failure handler"
                    )

Matriz de niveles de automatización

No todo se puede (ni se debe) automatizar. La matriz define qué nivel de automatización aplica a cada tipo de acción:

AcciónFullSemiManualJustificación
Recopilar contexto (host, usuario, logs)XSin riesgo, siempre beneficioso
Enriquecer IOCs (reputación, TI)XSin riesgo, ahorra tiempo
Notificar al equipo (Slack, email)XSin riesgo
Aislar host de la redXImpacto en disponibilidad, requiere confirmación
Bloquear IP/dominio en firewallXRiesgo de FP, requiere revisión
Deshabilitar cuenta de usuarioXImpacto en usuario, requiere confirmación
Restaurar desde backupXDecisión de negocio, riesgo de pérdida de datos
Eliminar malware / reimaginar hostXRequiere análisis forense previo
Comunicar a direcciónXRequiere criterio y contexto
Notificar a autoridades (AEPD, INCIBE)XImplicaciones legales

La progresión natural es: empezar con pocos pasos automatizados (full), aumentar la cobertura a medida que el equipo gana confianza y reducir los checkpoint (semi) a medida que se reducen los falsos positivos.

Recursos

  • NIST SP 800-61 Rev. 2 (Computer Security Incident Handling Guide): el estándar de referencia para respuesta a incidentes. Define las cuatro fases que estructuran nuestros playbooks.
  • CISA Incident Response Playbooks: playbooks de referencia publicados por CISA (Cybersecurity and Infrastructure Security Agency) para diferentes tipos de incidentes. Disponibles en cisa.gov.
  • RE&CT Framework: framework open source que mapea técnicas de respuesta a incidentes contra la matriz ATT&CK. Útil para verificar que tus playbooks cubren las técnicas relevantes.
  • Swimlane Turbine (ex-SOAR): plataforma SOAR que ejecuta playbooks definidos en código. Referencia de cómo los SOAR enterprise interpretan playbooks YAML/JSON.
  • MITRE ATT&CK (Impact Tactic): técnicas de impacto relevantes para playbooks de ransomware (T1486, T1490, T1489) y otros tipos de incidentes destructivos.
  • Palo Alto Cortex XSOAR Playbook Guide: documentación de diseño de playbooks de Cortex XSOAR. Aunque es una herramienta comercial, la guía de diseño es aplicable a cualquier formato.

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.