Dev.to Security 🔐 Cybersecurity 👁 0 📖 6 min read

DAST y escaneo de contenedores en CI/CD: auditando una app Flask con Wapiti y Trivy

Autor: Stevie Gerald Marca Aguilar , Usher Damiron Halanocca Rojas Pipeline DevSecOps que construye una aplicación web, la ataca con un escáner dinámico, revisa su imagen Docker en busca de CVE y, al final, la publica au

Autor: Stevie Gerald Marca Aguilar , Usher Damiron Halanocca Rojas
Pipeline DevSecOps que construye una aplicación web, la ataca con un escáner dinámico, revisa su imagen Docker en busca de CVE y, al final, la publica automáticamente en la nube.

⚠️ La aplicación contiene vulnerabilidades a propósito (SQLi y XSS) con fines educativos. No la reutilices en un entorno real.

Tabla de contenidos
Contexto: seguridad desde el inicio
Herramientas elegidas
Flujo del pipeline
La aplicación de prueba
Workflow de GitHub Actions
Publicación en Render
Qué encontraron los escáneres
Cómo corregirlo
Ejecución local
Conclusiones

Enlaces

  1. Contexto: seguridad desde el inicio Dejar la seguridad para la víspera del despliegue ya no es viable. La práctica DevSecOps, también llamada Shift-Left Security, propone que los controles de seguridad se ejecuten de forma automática desde las primeras etapas del desarrollo, igual que las pruebas unitarias. OWASP mantiene un catálogo público de herramientas de escaneo de vulnerabilidades. Para este proyecto se combinaron dos enfoques complementarios: uno que prueba la aplicación mientras está en ejecución y otro que inspecciona el contenedor donde vive.
  2. Herramientas elegidas Herramienta Enfoque Qué detecta Wapiti DAST (caja negra) Envía payloads maliciosos a la app en ejecución para descubrir XSS, inyección SQL, inclusión de archivos, ejecución de comandos y cabeceras HTTP débiles Trivy (Aqua Security) Escaneo de contenedores y dependencias CVE conocidas en la imagen Docker, en los paquetes del sistema operativo base y en las librerías de Python Wapiti ve la aplicación como la vería un atacante desde fuera; Trivy mira hacia dentro, a los componentes con los que fue construida. Juntos cubren tanto los errores en el código propio como los heredados de terceros.
  3. Flujo del pipeline Cada push a main dispara tres etapas en secuencia. Si una falla, las siguientes no se ejecutan:
push a main
   │
   ▼
① Trivy   → construye la imagen y busca CVE en SO y dependencias
   │
   ▼
② Wapiti  → levanta el contenedor y lo audita dinámicamente (DAST)
   │
   ▼
③ Deploy  → llama al Deploy Hook de Render y publica la app
  1. La aplicación de prueba Para comprobar que los escáneres realmente detectan fallos, se creó una app mínima en Python 3.9 + Flask con una base SQLite en memoria. Incluye dos vulnerabilidades clásicas del OWASP Top 10 (categoría A03:2021 – Injection). app/app.py
from flask import Flask, request
import sqlite3

app = Flask(__name__)

def db():
    conn = sqlite3.connect(":memory:")
    conn.execute("CREATE TABLE users (id INTEGER, name TEXT)")
    conn.execute("INSERT INTO users VALUES (1, 'admin'), (2, 'maria')")
    return conn

@app.route("/")
def home():
    return "<h1>Demo App</h1><a href='/search?q=maria'>Buscar</a> | <a href='/user?id=1'>Usuario</a>"

# Fallo intencional: XSS reflejado (CWE-79)
@app.route("/search")
def search():
    q = request.args.get("q", "")
    return f"<p>Resultados para: {q}</p>"

# Fallo intencional: inyección SQL (CWE-89)
@app.route("/user")
def user():
    uid = request.args.get("id", "1")
    rows = db().execute(f"SELECT * FROM users WHERE id = {uid}").fetchall()
    return str(rows)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

Dockerfile

FROM python:3.9-slim

WORKDIR /app

COPY app/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app/ .

EXPOSE 8000
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]
  1. Workflow de GitHub Actions Archivo: .github/workflows/pipeline.yml
name: Build, Scan and Deploy

on:
  push:
    branches: [main]

jobs:
  scan-dependencies-and-image:
    name: Trivy — Container and Dependency Scan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build image
        run: docker build -t demo-app:latest .

      - name: Trivy — escaneo de imagen
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: demo-app:latest
          format: table
          output: trivy-report.txt
          exit-code: 0

      - uses: actions/upload-artifact@v4
        with:
          name: trivy-report
          path: trivy-report.txt

  dast-scan:
    name: Wapiti — Dynamic Application Security Testing (DAST)
    runs-on: ubuntu-latest
    needs: scan-dependencies-and-image
    steps:
      - uses: actions/checkout@v4

      - name: Levantar app
        run: |
          docker build -t demo-app .
          docker run -d -p 8000:8000 demo-app
          sleep 8

      - name: Wapiti — escaneo dinámico
        run: |
          pip install wapiti3
          wapiti -u http://localhost:8000/ -f html -o wapiti-report --scope folder

      - uses: actions/upload-artifact@v4
        with:
          name: wapiti-report
          path: wapiti-report

  deploy:
    name: Deploy to Cloud (Render)
    runs-on: ubuntu-latest
    needs: dast-scan
    steps:
      - name: Disparar despliegue en Render
        run: curl -X POST "${{ secrets.RENDER_DEPLOY_HOOK }}"

Los reportes de Trivy y Wapiti quedan disponibles como artefactos descargables en cada ejecución.

  1. Publicación en Render En Render se creó un Web Service con entorno Docker, vinculado al repositorio público. En la configuración avanzada del servicio se generó una Deploy Hook URL. Esa URL se guardó en GitHub como secret RENDER_DEPLOY_HOOK. Así, el pipeline puede ordenar el despliegue al terminar los escaneos sin exponer ninguna credencial en el código. App en producción: https://vuln-scan-demo.onrender.com
  2. Qué encontraron los escáneres Wapiti (DAST) Hallazgo Severidad Dónde Detalle Inyección SQL Alta /user?id= Al enviar valores como ' OR '1'='1, la consulta cambia su lógica y devuelve todos los usuarios XSS reflejado Media /search?q= El parámetro se devuelve en el HTML sin codificar, por lo que un script inyectado se ejecuta en el navegador Cabeceras de seguridad ausentes Baja / Info Todas las rutas Faltan Content-Security-Policy, X-Frame-Options (riesgo de clickjacking) y X-Content-Type-Options: nosniff Trivy (contenedor y dependencias) Vulnerabilidades de severidad media y baja en paquetes del sistema base Debian de python:3.9-slim, como libssl, glibc y zlib. CVE conocidas en la versión fijada de flask y en su dependencia Werkzeug.
  3. Cómo corregirlo Inyección SQL → consultas parametrizadas
@app.route("/user")
def user():
    uid = request.args.get("id", "1")
    rows = db().execute("SELECT * FROM users WHERE id = ?", (uid,)).fetchall()
    return str(rows)

XSS → escapar la salida (o usar plantillas Jinja2, que escapan automáticamente)

from markupsafe import escape

@app.route("/search")
def search():
    q = request.args.get("q", "")
    return f"<p>Resultados para: {escape(q)}</p>"

Cabeceras HTTP → Flask-Talisman

from flask_talisman import Talisman

Talisman(app, content_security_policy={"default-src": "'self'"})

Hallazgos de Trivy
Migrar la imagen base a una versión con soporte, como python:3.12-slim o una variante Alpine.
Actualizar Flask (flask>=3.0.0) y Werkzeug, y añadir pip-audit al pipeline para vigilar las dependencias.

  1. Ejecución local
# Clonar
git clone https://github.com/UsherHalanoccaRojas/vuln-scan-demo.git
cd vuln-scan-demo

# Construir y ejecutar
docker build -t demo-app .
docker run -d -p 8000:8000 demo-app

# Trivy
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy:latest image demo-app

# Wapiti
pip install wapiti3
wapiti -u http://localhost:8000/ -f html -o wapiti-report --scope folder

Configuración en GitHub
Secret Valor
RENDER_DEPLOY_HOOK Deploy Hook URL generada en Render

  1. Conclusiones Con herramientas open source como Wapiti y Trivy se puede montar una defensa sólida sin pagar licencias costosas. El escaneo continuo expone fallos graves, como SQLi y XSS, antes de que lleguen a producción. Los reportes guardados como artefactos dejan un registro de cada CVE detectada en la imagen y en las dependencias. El despliegue solo ocurre después de que los controles de seguridad se ejecutan. Enlaces

Repositorio: https://github.com/UsherHalanoccaRojas/vuln-scan-demo

App desplegada (Render): https://vuln-scan-demo.onrender.com

Video demostrativo: https://www.youtube.com/watch?v=04p1_29t7GQ

Catálogo OWASP de herramientas: https://community.owasp.org/Vulnerability_Scanning_Tools

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.