TP22 : Tests dynamiques de sécurité (OWASP ZAP) et tests de charge (k6)

Les scanners du TP10 lisent le code (SAST, SCA). Ils ne voient ni la configuration réelle du serveur, ni les en-têtes renvoyés, ni le comportement sous charge. Ce TP ajoute deux contrôles sur l’application en fonctionnement :

  • le DAST (Dynamic Application Security Testing) avec OWASP ZAP, qui explore et attaque l’application comme le ferait un attaquant externe ;
  • les tests de charge avec k6, dont les seuils (thresholds) peuvent bloquer une promotion vers la production.

⚠️ La cible, OWASP Juice Shop, est une application volontairement vulnérable. Ne scannez jamais une application qui ne vous appartient pas : un scan actif est une attaque.


Fichiers de départ : lab-starter.zip

1. Objectifs pédagogiques

À l’issue du TP, vous serez capable de :

  • situer le DAST par rapport au SAST et à la SCA, et connaître ses limites ;
  • lancer les scans ZAP baseline (passif), full (actif) et API (OpenAPI) en conteneur ;
  • lire un rapport ZAP, corriger une alerte et paramétrer les règles bloquantes (rules.tsv) ;
  • écrire un test k6 : utilisateurs virtuels, paliers, checks, groups, thresholds ;
  • intégrer ZAP et k6 dans un pipeline avec un code retour bloquant.

Les images de cette page sont des schémas explicatifs, pas des captures d’écran.

2. Environnement du TP

  • VM de lab Ubuntu Server 26.04 avec Docker et Compose (TP7), au moins 4 Go de RAM libres.

  • Démarrer la cible depuis lab-starter/ :

    cd lab-starter
    docker compose up -d
    curl -sI http://localhost:3000 | head -5      # accès direct
    curl -sI http://localhost:8080 | head -5      # via le proxy Nginx

SAST, DAST et k6 : trois angles complémentaires SAST, DAST et k6 : trois angles complémentaires

Mode ZAP Ce qu’il fait Durée Risque pour la cible
Baseline Spider + analyse passive des réponses (en-têtes, cookies, fuites) minutes nul
Full scan Baseline + scan actif : injections SQL, XSS, traversée de répertoires… longue élevé (données modifiées)
API scan Comme le full scan, guidé par une définition OpenAPI / GraphQL moyenne élevé

3. Partie 1 : Scan baseline

  • Lancer un scan baseline directement sur Juice Shop :

    mkdir -p reports && chmod 777 reports
    docker run --rm --network host -v "$PWD/reports:/zap/wrk:rw" ghcr.io/zaproxy/zaproxy:stable \
      zap-baseline.py -t http://localhost:3000 -r baseline-direct.html -J baseline-direct.json
    echo "code retour : $?"      # 0 = OK, 1 = au moins un FAIL, 2 = au moins un WARN
  • Ouvrir le rapport HTML. Pour trois alertes, noter : identifiant, niveau de risque, URL, preuve (evidence), solution proposée, référence CWE.

  • Parmi les alertes, lesquelles relèvent de la configuration (en-têtes, cookies) et lesquelles du code ?

4. Partie 2 : Scan actif et scan d’API

  • Lancer un full scan sur Juice Shop (comptez 15 à 30 minutes ; -m 5 limite la durée du spider) :

    docker run --rm --network host -v "$PWD/reports:/zap/wrk:rw" ghcr.io/zaproxy/zaproxy:stable \
      zap-full-scan.py -t http://localhost:3000 -m 5 -r full.html
  • Comparer avec le baseline : quelles alertes n’apparaissent qu’avec le scan actif (injection SQL, XSS…) ?

  • Lancer un scan d’API contre l’API de votre contact-api (TP8) à partir d’une définition OpenAPI que vous écrivez (openapi.yaml décrivant GET /api/health et POST /api/contact) :

    docker run --rm --network host -v "$PWD:/zap/wrk:rw" ghcr.io/zaproxy/zaproxy:stable \
      zap-api-scan.py -t /zap/wrk/openapi.yaml -f openapi -r reports/api.html

    Votre validation Zod et votre gestion d’erreurs résistent-elles ?

5. Partie 3 : Corriger des alertes

  • Compléter nginx/default.conf : server_tokens off, en-têtes de sécurité, proxy_hide_header X-Powered-By, CSP en mode Report-Only. Recharger : docker compose restart proxy.
  • Relancer le baseline via le proxy (-t http://localhost:8080) et comparer le nombre d’alertes avec le scan direct :
Alerte Direct (3000) Via proxy durci (8080)
X-Content-Type-Options manquant
Anti-clickjacking
Divulgation de version
CSP absente
  • Pourquoi la CSP passe-t-elle d’abord par Content-Security-Policy-Report-Only ? Que risque-t-on avec une CSP stricte appliquée sans test ?

6. Partie 4 : Règles bloquantes

  • Générer la liste des règles (-g gen.conf), puis compléter zap/rules.tsv : FAIL pour ce qui doit bloquer, WARN pour le reste, IGNORE justifié par un commentaire.
  • Relancer le baseline avec -c rules.tsv : le code retour doit être 1 sur le scan direct et 0 sur le proxy durci.

7. Partie 5 : Tests de charge avec k6

Scénario k6 : montée, plateau, descente, seuils, code retour Scénario k6 : montée, plateau, descente, seuils, code retour

Notion Signification
VU Virtual User : boucle qui exécute la fonction default en continu.
stages Paliers de charge (durée, nombre de VUs cible).
check Assertion sur une réponse ; un check échoué n’arrête pas le test.
threshold Critère de réussite global ; un seuil franchi donne un code retour non nul (99).
  • Lancer le test smoke fourni :

    docker run --rm -i --network host -e BASE_URL=http://localhost:8080 grafana/k6 run - < k6/smoke.js
  • Compléter k6/load.js : paliers, groupe recherche, seuils (dont un seuil par groupe et abortOnFail). Lancer le test et lire le résumé : http_req_duration p(90), p(95), http_req_failed, iterations.

  • Trouver le point de rupture : augmenter progressivement les VUs (20, 50, 100…) jusqu’à franchir un seuil. Relever en parallèle la consommation CPU et mémoire (docker stats).

  • Le seuil p95 < 500 ms est-il un bon critère pour toutes les routes ? Proposer des seuils réalistes par type de route.

8. Partie 6 : Intégration continue

  • Compléter le workflow dast-perf.yml : démarrage de la cible dans le job, ZAP baseline avec rules.tsv et rapport en artefact, puis k6 smoke et load avec seuils bloquants.
  • Ouvrir une Pull Request qui retire un en-tête de sécurité du proxy : le job doit échouer. Expliquer où placer ces contrôles dans une chaîne DEV → STAGING → PRODUCTION.

Défi

  1. Faire un scan ZAP authentifié sur Juice Shop (contexte ZAP, script de connexion ou en-tête Authorization via -z "-config replacer...") et comparer la couverture.
  2. Envoyer les métriques k6 vers le Prometheus du TP21 (-o experimental-prometheus-rw) et les afficher dans Grafana à côté des métriques serveur.

9. Livrables

Captures à déposer dans captures/ :

Fichier Contenu attendu
zap-baseline.png Rapport baseline (résumé des alertes)
zap-full.png Alertes supplémentaires du scan actif
zap-before-after.png Alertes direct / via proxy durci
zap-rules-exit.png Codes retour 1 puis 0 avec rules.tsv
k6-summary.png Résumé k6 avec les seuils (✓ / ✗)
k6-breaking-point.png Seuil franchi lors de la recherche du point de rupture
ci-dast-perf.png Workflow en échec puis réussi

Ainsi que : default.conf, rules.tsv, load.js, openapi.yaml, dast-perf.yml et les tableaux remplis.