En la lección anterior recorrimos el escaneo dinámico de BazarNube a mano: explorar, escanear pasivo y activo, interpretar alertas y generar un informe. Funciona, pero no escala: nadie va a abrir la GUI de ZAP cada vez que Marc hace un push. La seguridad que solo ocurre "cuando alguien se acuerda" no es seguridad. En esta última lección del módulo llevamos ZAP al pipeline de CI/CD: convertimos el escaneo en un paso automático que corre en cada cambio, publica el informe como artefacto y rompe el build cuando aparecen vulnerabilidades por encima del umbral acordado. Este es el DAST dentro de un flujo DevSecOps.

Nota ético-legal (sigue vigente). Automatizar no cambia las reglas: el escaneo activo ataca de verdad. El pipeline debe apuntar siempre al staging propio y efímero de BazarNube (https://staging.bazarnube.local), nunca a producción ni a servicios de terceros. Automatizar un ataque contra un sistema ajeno lo hace más peligroso, no menos.

Contenido

  1. Por qué automatizar el DAST
  2. Los scripts empaquetados: baseline scan y full scan con Docker
  3. El fichero de reglas para gestionar falsos positivos
  4. El Automation Framework (YAML con jobs)
  5. La API y el modo daemon de ZAP
  6. Integración en GitHub Actions
  7. Integración en GitLab CI
  8. Umbrales, fallo de build e informes como artefactos
  9. Un pipeline DAST completo para BazarNube
  10. Errores comunes y consejos
  11. Ejercicios
  12. Conclusión

Por qué automatizar el DAST

Automatizar el escaneo dinámico persigue tres objetivos:

  • Repetibilidad: el mismo escaneo, con la misma política, cada vez. Sin pasos olvidados.
  • Detección temprana: una vulnerabilidad encontrada en el pipeline cuesta mucho menos que una encontrada en producción.
  • Gobernanza: el pipeline puede bloquear un despliegue si aparece algo grave, aplicando una política objetiva en vez de un criterio individual.
flowchart LR
    A[Push / MR a BazarNube] --> B[Build y deploy a staging]
    B --> C[ZAP DAST en contenedor]
    C --> D{Alertas > umbral?}
    D -->|Si| E[Build FAIL, bloquea deploy]
    D -->|No| F[Build OK, publica informe]
    E --> G[Informe artefacto]
    F --> G

Los scripts empaquetados: baseline scan y full scan con Docker

ZAP publica scripts listos para CI dentro de la imagen zaproxy/zap-stable (antes owasp/zap2docker-stable). Los dos que importan:

Script Qué hace Ataca activamente Duración Cuándo usarlo
zap-baseline.py Spider + escaneo pasivo; reporta cabeceras, cookies, fugas No Corto (minutos) En cada PR/commit, gate rápido
zap-full-scan.py Spider + AJAX Spider + escaneo activo completo Largo (decenas de min) Nocturno o pre-release
zap-api-scan.py Escaneo guiado por definición OpenAPI/SOAP/GraphQL Variable APIs (la de BazarNube)

Estrategia para BazarNube: zap-baseline.py en cada Pull Request (rápido, no invasivo, apto como gate) y zap-full-scan.py en un job nocturno contra el staging dedicado (lento y agresivo, pero fuera del camino crítico de los desarrolladores).

Ejemplo de baseline contra staging, montando un volumen para recoger informes:

docker run --rm -v "$(pwd)/zap-out:/zap/wrk/:rw" \
  -t zaproxy/zap-stable zap-baseline.py \
  -t https://staging.bazarnube.local \
  -r baseline-report.html \
  -J baseline-report.json \
  -w baseline-report.md \
  -c bazarnube-rules.conf

Flags clave:

  • -t URL objetivo (siempre staging propio).
  • -r/-J/-w informes en HTML, JSON y Markdown.
  • -c fichero de reglas (falsos positivos, ver abajo).
  • -I no fallar el build ante alertas de nivel Info (solo devolver error por WARN/FAIL).
  • -l nivel mínimo que provoca fallo (PASS, IGNORE, INFO, WARN, FAIL).

El full scan es idéntico en forma, cambiando el script:

docker run --rm -v "$(pwd)/zap-out:/zap/wrk/:rw" \
  -t zaproxy/zap-stable zap-full-scan.py \
  -t https://staging.bazarnube.local \
  -r full-report.html -J full-report.json \
  -c bazarnube-rules.conf

El fichero de reglas para gestionar falsos positivos

En 06-03 hicimos triage manual marcando falsos positivos en la GUI. En CI eso no vale: necesitamos que la decisión sea código versionado. El fichero de reglas (-c) permite fijar el tratamiento de cada regla por su ID. Formato: ID<TAB>acción<TAB>URL-regex(opcional).

# bazarnube-rules.conf
# ID	Accion	URL (opcional)
10096	IGNORE	# Timestamp Disclosure: ruido, no aplica a BazarNube
10021	IGNORE	# X-Content-Type-Options en assets estaticos del CDN
10035	WARN	# Strict-Transport-Security: avisar, no romper build todavia
40012	FAIL	# Reflected XSS: siempre rompe el build
40018	FAIL	# SQL Injection: siempre rompe el build

Acciones posibles: IGNORE (no reportar), WARN (reportar sin fallar el build) y FAIL (reportar y romper el build). Así el tratamiento de los falsos positivos vive en el repositorio, se revisa en un PR y queda auditado: nada de decisiones invisibles.

El Automation Framework (YAML con jobs)

Los scripts son cómodos pero rígidos. Para escaneos con contexto, autenticación y política a medida (como los que definimos en 06-02 y 06-03), ZAP ofrece el Automation Framework: un único fichero YAML que describe el escaneo como una lista de jobs encadenados. Es la forma recomendada para casos serios.

# zap-bazarnube.yaml
env:
  contexts:
    - name: BazarNube
      urls:
        - https://staging.bazarnube.local
      includePaths:
        - "https://staging.bazarnube.local.*"
      excludePaths:
        - "https://staging.bazarnube.local/logout.*"
        - ".*pay\\.tercero\\.com.*"
      authentication:
        method: form
        parameters:
          loginPageUrl: https://staging.bazarnube.local/login
          loginRequestUrl: https://staging.bazarnube.local/api/auth/login
          loginRequestBody: "username={%username%}&password={%password%}"
      users:
        - name: vendedor
          credentials:
            username: [email protected]
            password: ${BZN_TEST_PASS}
  parameters:
    failOnError: true

jobs:
  - type: spider
    parameters:
      context: BazarNube
      user: vendedor
  - type: spiderAjax
    parameters:
      context: BazarNube
      user: vendedor
  - type: passiveScan-wait
  - type: activeScan
    parameters:
      context: BazarNube
      user: vendedor
      policy: BazarNube-Web
  - type: report
    parameters:
      template: traditional-html
      reportFile: zap-bazarnube.html
  - type: report
    parameters:
      template: traditional-json
      reportFile: zap-bazarnube.json

Se ejecuta con:

docker run --rm -v "$(pwd):/zap/wrk/:rw" \
  -t zaproxy/zap-stable zap.sh -cmd -autorun /zap/wrk/zap-bazarnube.yaml

Nótese que la contraseña de prueba llega por variable de entorno (${BZN_TEST_PASS}), inyectada desde el gestor de secretos del CI: nunca credenciales en el YAML.

La API y el modo daemon de ZAP

Para control total (integrar ZAP con scripts propios, orquestar desde Python/Node), se arranca ZAP como daemon sin GUI y se le habla por su API REST:

# ZAP headless escuchando en 8080, con clave de API obligatoria
docker run -u zap -p 8080:8080 -d --name zap zaproxy/zap-stable \
  zap.sh -daemon -host 0.0.0.0 -port 8080 \
  -config api.key=$ZAP_KEY \
  -config api.addrs.addr.name=.* -config api.addrs.addr.regex=true
  • -daemon arranca sin interfaz gráfica.
  • -config api.key=... protege la API con una clave (obligatoria; no la dejes vacía).
  • Endpoints REST bajo /JSON/... para lanzar spider, active scan, leer alertas y generar informes (ya los usamos en 06-03).

Muchos equipos usan la librería cliente oficial en vez de curl crudo:

from zapv2 import ZAPv2
zap = ZAPv2(apikey='CLAVE', proxies={'http': 'http://localhost:8080'})
zap.spider.scan('https://staging.bazarnube.local')
zap.ascan.scan('https://staging.bazarnube.local', scanpolicyname='BazarNube-Web')
alerts = zap.core.alerts(baseurl='https://staging.bazarnube.local')

Integración en GitHub Actions

En GitHub, el flujo típico es levantar staging, ejecutar el baseline en cada PR y subir el informe como artefacto. Existe la acción oficial zaproxy/action-baseline:

# .github/workflows/dast.yml
name: DAST BazarNube
on: [pull_request]

jobs:
  zap-baseline:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Levantar staging de BazarNube
        run: docker compose -f docker-compose.staging.yml up -d --wait

      - name: ZAP Baseline Scan
        uses: zaproxy/[email protected]
        with:
          target: 'https://staging.bazarnube.local'
          rules_file_name: 'bazarnube-rules.conf'
          cmd_options: '-J baseline-report.json -w baseline-report.md'
          fail_action: true

      - name: Publicar informe como artefacto
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: zap-report
          path: |
            report_html.html
            baseline-report.json
            baseline-report.md

fail_action: true hace que las alertas marcadas como FAIL rompan el job (y por tanto bloqueen el merge si el check es obligatorio). El paso upload-artifact con if: always() garantiza que el informe se guarda aunque el build falle, que es justo cuando más lo necesitas.

Integración en GitLab CI

En GitLab el patrón es equivalente usando la imagen de ZAP como imagen del job:

# .gitlab-ci.yml
dast_baseline:
  stage: test
  image: zaproxy/zap-stable
  variables:
    TARGET: "https://staging.bazarnube.local"
  script:
    - mkdir -p zap-out
    - zap-baseline.py -t "$TARGET"
        -c bazarnube-rules.conf
        -r zap-out/report.html -J zap-out/report.json
        || EXIT=$?
    - echo "ZAP exit code $EXIT"
    - '[ "${EXIT:-0}" -lt 2 ] || exit 1'   # 2 = WARN, 3 = FAIL -> rompe build
  artifacts:
    when: always
    paths:
      - zap-out/
    expire_in: 30 days

Aquí gestionamos el código de salida a mano: ZAP devuelve 0 (todo OK), 1 (error interno), 2 (hay WARN) y 3 (hay FAIL). Decidimos el umbral en la propia lógica del job. artifacts: when: always cumple la misma función que en GitHub: conservar el informe aunque el job falle.

Umbrales, fallo de build e informes como artefactos

La política de "cuándo romper el build" es una decisión de equipo, no técnica. Una progresión sana para BazarNube:

Fase de adopción Qué rompe el build Objetivo
Introducción Nada (WARN en todo) Ganar visibilidad sin frustrar al equipo
Consolidación Solo High confirmadas (SQLi, XSS) Bloquear lo grave, tolerar el ruido
Madurez High y Medium según reglas Elevar el listón progresivamente

Códigos de salida de los scripts, para configurar el gate:

Código Significado Acción recomendada en CI
0 Sin hallazgos por encima del umbral Continuar
1 Error de ejecución de ZAP Fallar e investigar
2 Al menos un WARN Según política (avisar o fallar)
3 Al menos un FAIL Fallar el build

Y siempre publica los informes (HTML para humanos, JSON para comparar entre builds, Markdown para pegar en el ticket) como artefactos con if: always() / when: always.

Un pipeline DAST completo para BazarNube

Juntando todo, la estrategia de BazarNube combina dos gates de distinto coste:

flowchart TD
    A[PR abierto] --> B[Deploy efimero a staging]
    B --> C[zap-baseline.py con rules.conf]
    C --> D{FAIL?}
    D -->|Si| E[Bloquea merge + artefacto]
    D -->|No| F[Merge permitido + artefacto]
    G[Cron nocturno] --> H[Deploy staging estable]
    H --> I[zap-full-scan.py autenticado]
    I --> J[Informe al backlog: BZN-xxx]
  • Por PR: zap-baseline.py (rápido, no invasivo) como gate obligatorio. Solo rompe el build ante FAIL (SQLi/XSS confirmadas).
  • Nocturno: zap-full-scan.py o el Automation Framework autenticado (lento, agresivo) contra staging dedicado. No bloquea a nadie; sus hallazgos entran como tickets BZN-xxx en el backlog, ya mapeados al Top Ten (06-03).

Así el desarrollador tiene feedback rápido en su PR y el escaneo profundo ocurre sin frenar el flujo de trabajo. La SRE mantiene el staging efímero; Lucía y Marc reciben tickets accionables en vez de un PDF enorme cada trimestre.

Errores Comunes y Consejos

  • Escanear un staging que no está listo. Si el DAST arranca antes de que la app responda, no explora nada. Usa --wait/healthchecks antes de lanzar ZAP.
  • Dejar la API sin clave. -config api.key= vacío expone el daemon. Genera una clave y pásala por secreto del CI.
  • Credenciales o claves en el YAML. Todo secreto (contraseña de prueba, ZAP_KEY) va por variables/secretos del CI, nunca versionado.
  • Poner el full scan como gate de cada PR. Es demasiado lento y agresivo; frustra al equipo y ralentiza el merge. Baseline en PR, full scan nocturno.
  • No publicar el informe cuando el build falla. Sin if: always()/when: always pierdes justo el informe del fallo.
  • Gestionar falsos positivos "a ojo" cada ejecución. Ponlos en el fichero de reglas versionado; que se revisen en un PR.
  • Poner el listón al máximo desde el día uno. Si todo rompe el build de golpe, el equipo desactivará el escaneo. Sube el umbral por fases.

Ejercicios

Ejercicio 1. Marc propone poner zap-full-scan.py como gate obligatorio en cada Pull Request de BazarNube. Explica por qué no es buena idea y qué estrategia de dos niveles propondrías en su lugar.

Ejercicio 2. El baseline reporta insistentemente la alerta 10096 (Timestamp Disclosure), que el equipo ya ha verificado como falso positivo. Escribe la línea del fichero bazarnube-rules.conf para silenciarla y explica por qué esto es mejor que marcarlo en la GUI.

Ejercicio 3. En GitLab, tu job de ZAP termina con código de salida 3 pero el pipeline aparece en verde. ¿Qué está pasando y cómo lo corriges para que ese caso rompa el build y conserve el informe?

Soluciones

Solución 1. El full scan hace escaneo activo completo con Spider y AJAX Spider: tarda decenas de minutos y ataca de verdad, por lo que como gate de cada PR ralentizaría el desarrollo y generaría fricción. Estrategia recomendada: baseline en cada PR (rápido, pasivo, rompe el build solo ante FAIL) y full scan nocturno contra un staging dedicado, cuyos hallazgos entran al backlog como tickets sin bloquear a nadie.

Solución 2. Línea del fichero de reglas:

10096	IGNORE	# Timestamp Disclosure: falso positivo verificado por el equipo

Es mejor que la GUI porque la decisión queda versionada en el repositorio: se revisa en un PR, tiene autor y fecha, se aplica idéntica en cada ejecución del CI y es auditable. Un Mark as False Positive en la GUI vive solo en la sesión local de una persona y no se propaga al pipeline.

Solución 3. El script devuelve 3 (hay alertas FAIL), pero el job no propaga ese código porque probablemente se capturó con || true o no se traduce a exit 1. Hay que evaluar el código de salida y forzar el fallo, además de conservar artefactos siempre:

script:
  - zap-baseline.py -t "$TARGET" -c bazarnube-rules.conf -r zap-out/report.html || EXIT=$?
  - '[ "${EXIT:-0}" -lt 2 ] || exit 1'
artifacts:
  when: always
  paths: [ zap-out/ ]

Con [ "${EXIT:-0}" -lt 2 ] || exit 1, cualquier código >= 2 (WARN/FAIL) rompe el build, y when: always garantiza que el informe se sube incluso en el fallo.

Conclusión

Con esta lección cierras el Módulo 6. Has pasado de ejecutar ZAP a mano a integrarlo en un pipeline DevSecOps: baseline y full scan con Docker, el Automation Framework para escaneos autenticados a medida, la API en modo daemon, pipelines reales en GitHub Actions y GitLab CI, gestión de umbrales y fallo de build, informes como artefactos y un fichero de reglas versionado para los falsos positivos. El resultado es un DAST que corre solo, en cada cambio, y protege a BazarNube sin depender de que alguien se acuerde.

Este es también el punto donde todo el curso converge. ZAP no vive aislado: es una pieza dentro de un SDLC seguro. En el Módulo 7 damos ese salto: el ciclo de vida de desarrollo seguro, el modelado de amenazas para decidir qué proteger antes de escribir código, y DevSecOps como filosofía que hilvana todo lo aprendido. Ahí encajarás las piezas: el Top Ten 2021 (M3) como catálogo de riesgos, el ASVS (M4) como requisitos verificables, SAMM (M5) como modelo de madurez del programa, y ZAP (M6) como la verificación dinámica automatizada dentro del pipeline. La seguridad deja de ser una fase final para convertirse en una propiedad continua del proceso.

Curso de OWASP: Directrices y Estándares para la Seguridad en Aplicaciones Web

Módulo 1: Introducción a OWASP

Módulo 2: Principales Proyectos de OWASP

Módulo 3: OWASP Top Ten 2021 en Profundidad

Módulo 4: OWASP ASVS (Application Security Verification Standard)

Módulo 5: OWASP SAMM (Software Assurance Maturity Model)

Módulo 6: OWASP ZAP (Zed Attack Proxy)

Módulo 7: Buenas Prácticas y Recomendaciones

Módulo 8: Ejercicios Prácticos y Casos de Estudio

Módulo 9: Evaluación y Certificación

© Copyright 2026. Todos los derechos reservados