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
- Por qué automatizar el DAST
- Los scripts empaquetados: baseline scan y full scan con Docker
- El fichero de reglas para gestionar falsos positivos
- El Automation Framework (YAML con jobs)
- La API y el modo daemon de ZAP
- Integración en GitHub Actions
- Integración en GitLab CI
- Umbrales, fallo de build e informes como artefactos
- Un pipeline DAST completo para BazarNube
- Errores comunes y consejos
- Ejercicios
- 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 | Sí | Largo (decenas de min) | Nocturno o pre-release |
zap-api-scan.py |
Escaneo guiado por definición OpenAPI/SOAP/GraphQL | Sí | 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.confFlags clave:
-tURL objetivo (siempre staging propio).-r/-J/-winformes en HTML, JSON y Markdown.-cfichero de reglas (falsos positivos, ver abajo).-Ino fallar el build ante alertas de nivel Info (solo devolver error por WARN/FAIL).-lnivel 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.confEl 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 buildAcciones 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.jsonSe ejecuta con:
docker run --rm -v "$(pwd):/zap/wrk/:rw" \
-t zaproxy/zap-stable zap.sh -cmd -autorun /zap/wrk/zap-bazarnube.yamlNó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-daemonarranca 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.mdfail_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 daysAquí 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 anteFAIL(SQLi/XSS confirmadas). - Nocturno:
zap-full-scan.pyo el Automation Framework autenticado (lento, agresivo) contra staging dedicado. No bloquea a nadie; sus hallazgos entran como ticketsBZN-xxxen 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: alwayspierdes 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:
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
- OWASP Top Ten
- OWASP ASVS (Application Security Verification Standard)
- OWASP SAMM (Software Assurance Maturity Model)
- OWASP ZAP (Zed Attack Proxy)
- Otros Proyectos Clave: WSTG, Cheat Sheets y Dependency-Check
Módulo 3: OWASP Top Ten 2021 en Profundidad
- A01:2021 – Pérdida de Control de Acceso
- A02:2021 – Fallos Criptográficos y Exposición de Datos Sensibles
- A03:2021 – Inyección
- Cross-Site Scripting (XSS) en Profundidad
- A04:2021 – Diseño Inseguro
- A05:2021 – Configuración de Seguridad Incorrecta
- Entidades Externas XML (XXE)
- A06:2021 – Componentes Vulnerables y Desactualizados
- A07:2021 – Fallos de Identificación y Autenticación
- A08:2021 – Fallos de Integridad de Software y Datos (Deserialización Insegura)
- A09:2021 – Fallos de Registro y Monitorización
- A10:2021 – Server-Side Request Forgery (SSRF)
Módulo 4: OWASP ASVS (Application Security Verification Standard)
- Introducción a ASVS
- Niveles de Verificación
- Requisitos de Seguridad
- Implementación de ASVS en Proyectos
Módulo 5: OWASP SAMM (Software Assurance Maturity Model)
Módulo 6: OWASP ZAP (Zed Attack Proxy)
- Introducción a ZAP
- Instalación y Configuración
- Escaneo de Vulnerabilidades
- Automatización de Pruebas de Seguridad
Módulo 7: Buenas Prácticas y Recomendaciones
- Ciclo de Vida de Desarrollo Seguro (SDLC)
- Modelado de Amenazas (Threat Modeling)
- Integración de Seguridad en DevOps (DevSecOps)
- Capacitación y Concienciación en Seguridad
- Herramientas y Recursos Adicionales
Módulo 8: Ejercicios Prácticos y Casos de Estudio
- Ejercicio 1: Identificación de Vulnerabilidades
- Ejercicio 2: Implementación de Controles de Seguridad
- Caso de Estudio 1: Análisis de un Incidente de Seguridad
- Caso de Estudio 2: Mejora de la Seguridad en una Aplicación Web
