Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 1417 minIntermédiaire

Tests de sécurité : revue de code, SAST, DAST et SCA

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • choisir la famille de tests adaptée à une faille ;
  • mener une revue de code orientée sécurité ;
  • lancer Semgrep, npm audit, gitleaks et ZAP ;
  • intégrer les contrôles dans l'intégration continue ;
  • rédiger une fiche de vulnérabilité.

3 exercices corrigés1 schéma

Les tests de sécurité vérifient qu'une application résiste aux attaques connues, avant qu'un attaquant ne s'en charge. On combine plusieurs familles : la revue de code par un humain, l'analyse statique du code (SAST), l'analyse des dépendances (SCA), la recherche de secrets, l'analyse dynamique de l'application en fonctionnement (DAST) et le test d'intrusion. Ce chapitre présente chacune, avec les outils courants (Semgrep, npm audit, gitleaks, ZAP), leur intégration dans l'intégration continue et la rédaction d'un rapport d'audit.

Les familles de tests

FamilleCe qu'elle examineQuandOutils courantsCe qu'elle trouve bienCe qu'elle rate
Revue de codeLe code source, lu par un humainÀ chaque pull requestGitHub, GitLabLogique métier, contrôle d'accès, défauts de conceptionCe que le relecteur ne connaît pas ; elle dépend de son attention
SAST (Static Application Security Testing)Le code source, analysé sans l'exécuterÀ chaque commitSemgrep, SonarQube, CodeQL, eslint-plugin-securityInjections, fonctions dangereuses, secrets, mauvaises pratiques connuesLe contexte d'exécution, la configuration ; faux positifs
SCA (Software Composition Analysis)Les dépendancesÀ chaque commit et en continunpm audit, Dependabot, OWASP Dependency-CheckCVE connues dans les bibliothèquesLes failles de ton propre code
Recherche de secretsLe code et l'historique GitAvant chaque commit et en continugitleaks, GitHub secret scanningClés, mots de passe, jetons commitésLes secrets dans un format inconnu
DAST (Dynamic Application Security Testing)L'application en fonctionnement, vue de l'extérieurSur un environnement de test ou de préproductionZAP, Burp SuiteEn-têtes, cookies, XSS, injections, configuration du serveurLe code jamais atteint par le scan, la logique métier
Tests automatisés de sécuritéDes cas précis, écrits par l'équipeÀ chaque commitVitest, Jest, PHPUnit, JUnit avec SupertestLes régressions sur le contrôle d'accès, la validationCe qu'on n'a pas pensé à tester
Test d'intrusionToute l'application, par un expert qui l'attaqueAvant une mise en production importante, puis régulièrementLes outils précédents et l'expertise humaineLes enchaînements de failles, la logique métierIl est ponctuel : l'application change ensuite

Aucune famille ne suffit seule. Les outils automatiques trouvent vite et souvent les failles « techniques » ; seuls les humains trouvent les failles de logique (une IDOR, une étape de validation contournable) que le code exprime parfaitement.

Postehook depré-commitgitleaksPull requesttests de droitsnpm auditSemgrepgitleaksrevue de codePréproductionZAP : analysepassivepuis activeAvant versiontestd'intrusionProductionDependabotsuivi du SBOMjournauxet alertesplus une faille est trouvée tôt, moins elle coûte à corriger
Chaque étape du cycle a ses tests de sécurité : plus une faille est trouvée tôt, moins elle coûte

La revue de code orientée sécurité

Une revue de code suit une liste de points de contrôle. Celle-ci reprend les chapitres du cours :

PointCe qu'on cherche dans le code
EntréesD'où vient chaque donnée ? Est-elle validée côté serveur (type, longueur, format) ?
RequêtesUne saisie est-elle concaténée dans du SQL, une requête NoSQL, une commande système ?
AutorisationChaque route vérifie-t-elle le rôle et le propriétaire ? Le rôle vient-il du serveur ?
SortiesLes données affichées sont-elles encodées ? Y a-t-il innerHTML, dangerouslySetInnerHTML, |raw, {!! !!} ?
SecretsUn mot de passe, une clé, un secret JWT apparaît-il dans le code ou la configuration versionnée ?
CryptographieAlgorithme des mots de passe ? Aléatoire cryptographique pour les jetons ?
ErreursQue se passe-t-il dans le catch ? Une erreur peut-elle laisser passer une requête ou révéler des détails ?
JournalisationLes événements de sécurité sont-ils tracés ? Sans données sensibles ?
DépendancesUne nouvelle dépendance est-elle justifiée, maintenue, à jour ?

Un exemple de revue sur un extrait de la version PHP :

php
1  $id = $_GET['id'];
2  $res = $pdo->query("SELECT * FROM ligne_frais WHERE id_fiche = $id");
3  foreach ($res as $ligne) {
4      echo "<tr><td>" . $ligne['libelle'] . "</td><td>" . $ligne['montant'] . "</td></tr>";
5  }

Les commentaires de revue :

  • Ligne 2 : injection SQL, $id est concaténé. Requête préparée avec un marqueur.
  • Ligne 2 : aucune vérification que la fiche appartient à l'utilisateur connecté (IDOR). Ajouter AND id_visiteur = ? via une jointure avec fiche_frais.
  • Ligne 2 : SELECT * : ne sélectionner que les colonnes affichées.
  • Ligne 4 : XSS stockée possible, libelle est affiché sans encodage. htmlspecialchars.

SAST : analyser le code source

Un outil SAST lit le code sans l'exécuter et cherche des motifs dangereux : une saisie de l'utilisateur qui atteint une requête SQL sans passer par une requête préparée, l'appel d'une fonction risquée, un secret écrit en dur. Semgrep est gratuit dans sa version communautaire et couvre de nombreux langages :

bash
semgrep scan --config p/owasp-top-ten        # règles publiques alignées sur le Top 10
semgrep scan --config p/javascript --error   # code de sortie en erreur si un problème est trouvé

On peut aussi écrire ses propres règles, pour les erreurs propres au projet :

yaml
rules:
  - id: requete-sql-concatenee
    languages: [javascript]
    severity: ERROR
    message: Requête SQL construite par concaténation, utiliser une requête préparée
    pattern-either:
      - pattern: $POOL.query("..." + $X, ...)
      - pattern: $POOL.execute("..." + $X, ...)

Les autres outils courants : SonarQube, qui combine qualité et sécurité et s'intègre aux pull requests ; CodeQL, le moteur de l'analyse de code de GitHub (code scanning), gratuit pour les dépôts publics ; eslint-plugin-security pour JavaScript ; Psalm et son analyse de propagation des données (taint analysis) pour PHP.

SCA et secrets dans la chaîne d'intégration

L'analyse des dépendances et la recherche de secrets se branchent au même endroit que les tests, pour bloquer une pull request qui ajoute une dépendance vulnérable ou un secret :

bash
npm audit --omit=dev --audit-level=high     # échoue s'il existe une vulnérabilité élevée ou critique
gitleaks git . --redact                      # cherche des secrets dans l'historique, sans les afficher en clair

Le chapitre 5 détaille npm audit et Dependabot ; le chapitre 4, la réaction à un secret commité.

DAST : attaquer l'application qui tourne

Un outil DAST se comporte comme un attaquant : il parcourt l'application en fonctionnement, envoie des requêtes piégées et analyse les réponses. Il ne voit pas le code, mais il voit ce que voit un attaquant : en-têtes, cookies, messages d'erreur, réactions aux charges d'injection.

ZAP (maintenu aujourd'hui sous le nom ZAP by Checkmarx) est le scanner libre le plus utilisé. Il fonctionne en trois temps :

  1. Proxy : ZAP se place entre le navigateur et l'application et enregistre toutes les requêtes pendant que tu utilises l'application normalement.
  2. Exploration (spider) : il suit les liens et les formulaires pour découvrir les pages et les routes.
  3. Analyse : l'analyse passive examine les réponses sans rien envoyer de plus (en-têtes manquants, cookies sans HttpOnly, informations divulguées) ; l'analyse active envoie des charges d'attaque (injections, XSS, parcours de répertoire) et observe les réponses.

Pour une analyse rapide et automatisable, ZAP fournit des scripts prêts à l'emploi dans son image Docker :

bash
# Analyse de base : exploration et analyse passive, sans attaque, rapport HTML dans le dossier courant
docker run --rm -v "$(pwd):/zap/wrk" -t ghcr.io/zaproxy/zaproxy:stable \
  zap-baseline.py -t https://preprod.frais.gsb.fr -r rapport-zap.html

Le script zap-full-scan.py ajoute l'analyse active, et zap-api-scan.py analyse une API à partir de sa description OpenAPI.

Burp Suite, dont l'édition Community est gratuite, est l'outil de référence des testeurs pour intercepter, modifier et rejouer les requêtes à la main : changer un identifiant pour tester une IDOR, retirer un jeton, modifier un champ caché.

Tester l'autorisation avec des tests automatisés

Aucun scanner ne sait que le visiteur A ne doit pas lire la fiche du visiteur B : c'est une règle métier. Elle se protège par des tests écrits par l'équipe, exécutés à chaque commit. Une approche efficace : une matrice de tests qui joue chaque rôle contre chaque route sensible.

js
import { describe, it, expect } from 'vitest';
import request from 'supertest';
import { app } from '../src/app.js';
import { jetonPour } from './outils.js';

const cas = [
  // [méthode, route, utilisateur, statut attendu]
  ['get',   '/api/fiches/1187',                'visiteurA',  200],
  ['get',   '/api/fiches/1188',                'visiteurA',  404],
  ['get',   '/api/fiches/1188',                'comptable1', 200],
  ['patch', '/api/compta/fiches/1187/valider', 'visiteurA',  403],
  ['patch', '/api/compta/fiches/1187/valider', null,         401],
  ['get',   '/api/moi',                        'visiteurA',  200],
];

describe('matrice de contrôle d’accès', () => {
  it.each(cas)('%s %s par %s → %i', async (methode, route, utilisateur, attendu) => {
    let requete = request(app)[methode](route);
    if (utilisateur) requete = requete.set('Authorization', `Bearer ${jetonPour(utilisateur)}`);
    const reponse = await requete;
    expect(reponse.status).toBe(attendu);
  });
});

Un test qui vérifie aussi qu'une réponse ne contient pas de champ sensible (expect(reponse.body).not.toHaveProperty('mdp_hash')) protège contre le BOPLA. Le cours Tests présente les tests unitaires et d'intégration.

Intégrer les tests dans la chaîne d'intégration continue

Avec GitHub Actions, un fichier .github/workflows/securite.yml exécute les contrôles à chaque pull request :

yaml
name: securite
on: [pull_request]

jobs:
  controles:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0                       # tout l'historique, pour la recherche de secrets
      - uses: actions/setup-node@v4
        with:
          node-version: 24
      - run: npm ci
      - run: npm test                          # dont la matrice de contrôle d'accès
      - run: npm audit --omit=dev --audit-level=high
      - uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - run: pipx run semgrep scan --config p/owasp-top-ten --error

Une règle de protection de la branche principale exige que ce workflow réussisse et qu'un relecteur approuve avant toute fusion. Le scan ZAP se lance après le déploiement en préproduction, dans un autre workflow. Le chapitre Sécurité dans la CI : SAST, dépendances et secrets du cours CI/CD détaille cette démarche DevSecOps, avec ses équivalents dans GitLab CI.

Le test d'intrusion et le rapport d'audit

Un test d'intrusion (pentest) est une attaque simulée menée par un expert, avec l'accord écrit de l'organisation. La convention fixe le périmètre (quelles applications, quelles adresses), les dates, les techniques autorisées ou interdites (pas de déni de service, pas d'ingénierie sociale…) et les contacts en cas de problème. Sans elle, le test est une intrusion au sens du Code pénal (chapitre 1). On distingue le test en boîte noire (le testeur ne sait rien), en boîte grise (il a un compte utilisateur) et en boîte blanche (il a le code). En France, l'ANSSI qualifie des prestataires d'audit (label PASSI) ; des plateformes de bug bounty rémunèrent les chercheurs qui signalent des failles dans un cadre défini.

Un audit, qu'il soit mené par toi sur ton projet ou par un prestataire, se termine par un rapport. Sa structure :

  1. Synthèse pour la direction : périmètre, dates, niveau de sécurité global, les failles les plus graves en langage non technique, les priorités.
  2. Méthodologie : outils, référentiel suivi (OWASP Top 10, ASVS, WSTG), limites du test.
  3. Une fiche par vulnérabilité :
RubriqueExemple
TitreAccès aux fiches de frais des autres visiteurs (IDOR)
CatégorieA01:2025 Contrôle d'accès défaillant, CWE-639
GravitéMoyenne selon CVSS 3.1 (6,5, AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N), relevée à élevée dans le contexte : les RIB et les déplacements de tous les visiteurs sont exposés, et c'est une violation de données à notifier
DescriptionLa route GET /api/fiches/:id renvoie toute fiche existante à un utilisateur authentifié, sans vérifier qu'il en est le propriétaire
PreuveRequête avec le jeton du visiteur A sur la fiche 1188 du visiteur B, réponse 200 avec les données de B
ImpactLecture des montants, déplacements et RIB de tous les visiteurs : violation de données personnelles
RecommandationFiltrer par id_visiteur dans la requête ; ajouter un test automatisé ; journaliser les refus
StatutCorrigée le …, vérifiée le …
  1. Plan d'action : les corrections classées par priorité (gravité, facilité d'exploitation, coût de correction), avec un responsable et une date.

Après correction, on vérifie : on rejoue la preuve, qui doit échouer, et on garde le test automatisé qui empêche le retour de la faille.

Exercice 1 sur 3 Quelle famille de test ? Réussi À revoir

Pour chaque faille, indique la famille de test la plus susceptible de la trouver en premier.

  1. Le projet utilise une version de jsonwebtoken touchée par une CVE.
  2. La réponse du serveur ne contient pas d'en-tête Strict-Transport-Security.
  3. Un fichier config.js contient une clé d'API d'un service de paiement.
  4. Un comptable peut valider une fiche dont il est lui-même le visiteur.
  5. Une requête SQL est construite par concaténation dans un contrôleur.
Voir la réponseMasquer la réponse de l'exercice 1
  1. SCA (npm audit, Dependabot).
  2. DAST (ZAP le signale dès l'analyse passive) ; la revue de la configuration peut aussi le voir.
  3. Recherche de secrets (gitleaks, GitHub secret scanning), et SAST.
  4. Revue de code ou test automatisé écrit à partir de la règle métier : aucun outil générique ne connaît cette règle. Un test d'intrusion en boîte grise peut aussi la trouver.
  5. SAST (Semgrep, SonarQube) et revue de code.

Tu avais trouvé ?

Exercice 2 sur 3 Faire une revue de code Réussi À revoir

Fais la revue de cette route Express : relève chaque problème, sa catégorie OWASP 2025 et la correction.

js
router.get('/api/recherche', async (req, res) => {
  try {
    const sql = "SELECT * FROM ligne_frais WHERE libelle LIKE '%" + req.query.q + "%'";
    const [lignes] = await pool.query(sql);
    res.send(`<h2>Résultats pour ${req.query.q}</h2>` + lignes.map(l => `<p>${l.libelle}</p>`).join(''));
  } catch (e) {
    res.status(500).send(e.stack);
  }
});
Voir la réponseMasquer la réponse de l'exercice 2
  • Pas d'authentification et pas de filtre sur le propriétaire : n'importe qui cherche dans les lignes de tous les visiteurs (A01). Ajouter authentifier et une jointure sur fiche_frais avec id_visiteur = ?.
  • Injection SQL : q est concaténé (A05). Requête préparée : WHERE libelle LIKE ? avec la valeur `%${q}%`.
  • XSS réfléchie (req.query.q dans le titre) et XSS stockée (l.libelle) : aucune donnée n'est encodée (A05). Encoder, ou mieux, renvoyer du JSON et laisser le front (React) afficher le texte.
  • SELECT * : ne sélectionner que les colonnes utiles.
  • Trace d'erreur envoyée au client (e.stack) : divulgation d'informations (A02, A10). Message générique, détail dans le journal.
  • Pas de pagination : une recherche vide renvoie tout (consommation de ressources).

Tu avais trouvé ?

Exercice 3 sur 3 Rédiger une fiche de vulnérabilité Réussi À revoir

Pendant un scan, ZAP signale que le cookie de session PHPSESSID n'a pas l'attribut HttpOnly. Rédige la fiche de vulnérabilité (titre, catégorie, gravité qualitative, description, impact, recommandation).

Voir la réponseMasquer la réponse de l'exercice 3
  • Titre : cookie de session lisible par JavaScript.
  • Catégorie : A02:2025 Mauvaise configuration de sécurité (CWE-1004).
  • Gravité : moyenne. Seule, elle n'est pas exploitable ; combinée à une XSS, elle permet le vol de session.
  • Description : le serveur envoie Set-Cookie: PHPSESSID=…; path=/ sans l'attribut HttpOnly (ni SameSite).
  • Impact : un script injecté par une XSS peut lire le cookie avec document.cookie, l'envoyer à l'attaquant, qui usurpe alors la session de la victime.
  • Recommandation : session_set_cookie_params(['secure' => true, 'httponly' => true, 'samesite' => 'Lax']) avant session_start() (ou les directives session.cookie_httponly, session.cookie_secure et session.cookie_samesite de php.ini) ; vérifier ensuite l'en-tête avec curl -I.

Tu avais trouvé ?

Chapitre terminé !

Fais les 3 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : E7 SLAM : méthode et sujet corrigéRevoir le sommaire du cours