Aller au contenu

DAST authentifié avec le ZAP Automation Framework et IAST

Objectifs pédagogiques

À la fin de ce TP, vous devez être capables de :

  • décrire l'API par une définition OpenAPI exploitable par un scanner
  • scanner l'environnement de staging, authentifié, avec un plan ZAP versionné
  • faire échouer la pipeline sur un seuil de risque et alimenter DefectDojo
  • mettre en œuvre l'IAST, ou à défaut corréler les alertes DAST avec les traces
  • organiser les scans pour un retour rapide sur les MR et une couverture complète la nuit
  • écrire les tests de non-régression des failles corrigées

Prérequis

  • L'API SecurePay déployée en staging et exposée par l'ingress (api-staging.securepay.local dans ce TP)
  • Un runner GitLab self-hosted capable de joindre staging
  • DefectDojo (voir le TP vulnérabilités) et, pour l'option de corrélation, une stack de traces (Tempo ou Jaeger)

Testé

Le plan de l'atelier 3 a été joué avec l'image ghcr.io/zaproxy/zaproxy:stable contre une API de test : import OpenAPI, toutes les requêtes portant le jeton injecté par variable d'environnement, rapports HTML et XML, code de sortie 1 en présence d'une alerte haute. Le marquage des requêtes par le job replacer et la substitution ${VAR} dans le plan ont aussi été vérifiés.

Contexte

Le DAST de SecurePay tourne aujourd'hui sur un conteneur lancé en service CI, sans authentification : il ne voit que la page de connexion et ignore l'ingress, TLS et les en-têtes ajoutés par le proxy. Vous passez à un scan authentifié de l'environnement déployé, et vous cherchez à localiser les failles jusqu'à la ligne de code.

Atelier 1 : Une définition OpenAPI

Travail demandé

  1. Rédigez openapi.yaml (OpenAPI 3) couvrant les routes de l'API : /health, /api/info, /api/auth/login, /api/payments/process, /api/payments/calculate-tax, /api/payments/recent. Déclarez le schéma de sécurité bearer et les corps de requête.

  2. Servez la définition par l'API en staging (par exemple sur /openapi.json), ou rangez-la dans le dépôt et passez-la au scanner.

  3. Validez-la avec un linter OpenAPI dans la CI.

Critère de réussite

Chaque route exposée figure dans la définition, avec ses paramètres : ce qui n'y est pas ne sera pas scanné.

Atelier 2 : Un utilisateur dédié au scan

Travail demandé

  1. Créez un utilisateur de test non administrateur pour le scan. Dans SecurePay, le nom admin reçoit le rôle ADMIN : ne l'utilisez pas.

  2. Rangez ses identifiants dans OpenBao (ou des variables Masked et Protected).

  3. Vérifiez la durée de vie du jeton renvoyé par /api/auth/login (1 h dans SecurePay) : elle doit couvrir la durée maximale du scan.

Critère de réussite

Le scan s'authentifie avec une identité aux droits minimaux, sans secret en clair dans le dépôt.

Atelier 3 : Le plan ZAP

Travail demandé

  1. Rédigez security/zap/zap-plan.yaml. Les variables ${TARGET} et ${REPORT_DIR} sont substituées par ZAP à l'exécution : un même plan sert pour tous les environnements.
env:
  contexts:
    - name: securepay
      urls: ["${TARGET}"]
      includePaths: ["${TARGET}.*"]
  parameters:
    failOnError: true
    failOnWarning: false
    progressToStdout: true
jobs:
  - type: openapi
    parameters:
      apiUrl: ${TARGET}/openapi.json
      context: securepay
  - type: passiveScan-wait
    parameters:
      maxDuration: 5
  - type: activeScan
    parameters:
      context: securepay
      maxRuleDurationInMins: 2
      maxScanDurationInMins: 20
  - type: report
    parameters:
      template: traditional-html
      reportDir: ${REPORT_DIR}
      reportFile: zap-staging
  - type: report
    parameters:
      template: traditional-xml
      reportDir: ${REPORT_DIR}
      reportFile: zap-staging
  - type: exitStatus
    parameters:
      errorLevel: High
      warnLevel: Medium
  1. Ajoutez le job DAST, déclenché après la synchronisation de staging :
dast:staging:
  stage: dast
  tags: [self-hosted]
  image:
    name: ghcr.io/zaproxy/zaproxy:stable
    entrypoint: [""]
  variables:
    TARGET: https://api-staging.securepay.local
    ZAP_AUTH_HEADER_SITE: api-staging.securepay.local
  script:
    - export REPORT_DIR="$CI_PROJECT_DIR"
    - |
      TOKEN=$(curl -sf -X POST "$TARGET/api/auth/login" -H 'Content-Type: application/json' \
        -d "{\"username\":\"$SCAN_USER\",\"password\":\"$SCAN_PASSWORD\"}" | jq -r .token)
    - export ZAP_AUTH_HEADER_VALUE="Bearer $TOKEN"
    - zap.sh -cmd -autorun "$CI_PROJECT_DIR/security/zap/zap-plan.yaml"
  artifacts:
    when: always
    paths: [zap-staging.html, zap-staging.xml]

Pas de /zap/wrk en CI

Le dossier /zap/wrk n'existe dans l'image ZAP que si vous y montez un volume, et l'utilisateur zap ne peut pas le créer. En CI, écrivez les rapports dans le dossier du job, d'où la variable REPORT_DIR.

  1. Importez zap-staging.xml dans DefectDojo (scan_type="ZAP Scan") avec le job de l'atelier 3 du TP vulnérabilités.

Ce que vous devez observer

  • Les requêtes du scan portent toutes l'en-tête Authorization : vérifiez-le dans les journaux de l'ingress ou de l'API
  • Une alerte haute fait échouer le job ; une alerte moyenne ne produit qu'un avertissement
  • Les rapports sont disponibles en artefacts et dans DefectDojo

Critère de réussite

Un scan authentifié de staging tourne à chaque synchronisation, avec un résultat bloquant au-delà du seuil défini.

Atelier 4 : Exploiter les résultats

Travail demandé

  1. Triez les alertes dans DefectDojo : vraies failles à corriger, faux positifs justifiés et validés par un coéquipier.

  2. Comparez avec les résultats du scan non authentifié : quelles routes et quelles failles n'étaient pas vues ?

  3. Corrigez au moins une faille révélée (par exemple les en-têtes de sécurité) et vérifiez sa disparition au scan suivant.

Critère de réussite

Vous savez chiffrer ce que l'authentification et le scan du déployé apportent par rapport au DAST précédent.

Atelier 5 : IAST, ou corrélation avec les traces

Choisissez l'option A si une offre IAST est disponible (essai), sinon l'option B.

Option A : agent IAST en staging

  1. Avec une offre compatible Node.js (par exemple Datadog Code Security), activez l'agent sur le seul environnement de staging : traceur dd-trace chargé au démarrage de l'API, variable DD_IAST_ENABLED=true, Agent Datadog déployé dans le cluster, clé d'API rangée dans OpenBao.
  2. Lancez le DAST et les tests fonctionnels pendant que l'agent observe.
  3. Comparez les constats IAST et DAST : lesquels l'IAST localise-t-il jusqu'à la ligne de code ?
  4. Documentez dans un ADR où partent les données d'exécution, et l'avis de la PSSI.

Option B : corréler DAST et traces

  1. Ajoutez au plan ZAP un marqueur sur chaque requête :
  - type: replacer
    rules:
      - description: Marqueur de scan
        matchType: req_header
        matchString: X-Scan-Run
        replacementString: ${CI_PIPELINE_ID}
  1. Dans l'API, copiez cet en-tête dans les spans OpenTelemetry :
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');

new HttpInstrumentation({
  headersToSpanAttributes: { server: { requestHeaders: ['x-scan-run'] } },
});
  1. Pour trois alertes ZAP, retrouvez la trace correspondante (attribut de l'en-tête, URL, horodatage), l'éventuelle exception et la pile d'appels, puis le fichier et la ligne à corriger.

Critère de réussite

Au moins trois alertes DAST sont reliées à leur emplacement dans le code, par l'IAST ou par les traces.

Atelier 6 : Optimiser l'exécution des scans

Travail demandé

  1. Relevez la durée actuelle de la pipeline d'une MR.

  2. Passez les scans de secrets et de SAST en mode différentiel sur les MR :

sast:semgrep:
  variables:
    GIT_DEPTH: "0"
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  script:
    - semgrep scan --config auto --baseline-commit "$CI_MERGE_REQUEST_DIFF_BASE_SHA"
        --json --output semgrep.json

secrets:gitleaks:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  script:
    - gitleaks git --log-opts="$CI_MERGE_REQUEST_DIFF_BASE_SHA..HEAD"
        --report-format json --report-path gitleaks.json .
  1. Créez une pipeline planifiée nocturne qui joue les scans complets : SAST et secrets sur tout l'historique, DAST actif complet, scan des images en production.

  2. Parallélisez les jobs indépendants et mettez en cache la base de vulnérabilités de Trivy.

  3. Relevez la nouvelle durée de pipeline sur une MR, et vérifiez qu'une faille volontairement introduite dans la MR est toujours bloquée.

Critère de réussite

Le temps de retour sur une MR diminue, sans perte de couverture : ce qui n'est plus scanné sur la MR l'est la nuit.

Atelier 7 : Tests de non-régression

Travail demandé

  1. Écrivez tests/security/regression.test.js pour les failles corrigées de SecurePay :
const request = require('supertest');
const app = require('../../src/app');

describe('Non-régression sécurité', () => {
  test("la formule de taxe n'exécute pas de code", async () => {
    const res = await request(app).post('/api/payments/calculate-tax')
      .send({ amount: 100, formula: 'process.exit(1)' });
    expect(res.status).toBe(400);
  });

  test('les en-têtes de sécurité sont présents', async () => {
    const res = await request(app).get('/health');
    expect(res.headers['x-powered-by']).toBeUndefined();
    expect(res.headers['content-security-policy']).toBeDefined();
  });
});
  1. Vérifiez que ces tests échouent sur une version qui réintroduit la faille. Avec eval(), la formule process.exit(1) interrompt même le processus de test : la régression ne peut pas passer inaperçue.

  2. Ajoutez un test à chaque faille corrigée par la suite.

Critère de réussite

Chaque faille corrigée est protégée par un test exécuté à chaque pipeline.

Questions de synthèse

  • Qu'est-ce qu'un scan de l'environnement déployé révèle qu'un scan en service CI ne voit pas ?
  • Pourquoi l'utilisateur de scan ne doit-il pas être administrateur ?
  • Quelles sont les forces et les limites de l'IAST, et pourquoi sa couverture dépend-elle des tests ?
  • En quoi la corrélation DAST et traces diffère-t-elle d'un véritable IAST ?
  • Comment raccourcir le retour sur les MR sans réduire la couverture de sécurité ?