Aller au contenu
Réussir mon BTS SIO

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

Par exemple

Chapitre 416 minIntermédiaire

Mauvaise configuration (A02), environnements et secrets

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

À la fin de ce chapitre, tu sauras

  • repérer les réglages dangereux laissés en production ;
  • séparer et isoler les environnements de développement, de test et de production ;
  • ranger un secret hors du code avec un fichier .env et un coffre-fort ;
  • réagir dans le bon ordre quand un secret a fuité dans Git.

3 exercices corrigés

Une mauvaise configuration de sécurité (Security Misconfiguration, A02:2025) est un réglage dangereux laissé dans un composant de l'application : compte par défaut, mode débogage, message d'erreur trop détaillé, fichier sensible accessible, service inutile. Elle monte au deuxième rang du Top 10 en 2025. Ce chapitre montre les erreurs les plus fréquentes, comment séparer et isoler les environnements, et comment gérer les secrets (mots de passe de base, clés d'API) sans jamais les écrire dans le code.

Ce qu'est une mauvaise configuration

Le code peut être irréprochable et l'application vulnérable, parce que le serveur, le framework, la base ou le conteneur qui l'exécutent sont mal réglés. L'OWASP range dans A02 notamment :

  • des fonctionnalités inutiles activées : ports, services, pages d'exemple, comptes, privilèges ;
  • des comptes et mots de passe par défaut jamais changés ;
  • des erreurs non gérées de façon centralisée, qui affichent des traces d'exécution ou des messages trop bavards ;
  • des réglages par défaut non sûrs dans les serveurs, frameworks, bibliothèques ou bases ;
  • des en-têtes de sécurité absents ou mal réglés (chapitre 9) ;
  • des cookies sans les attributs Secure ou HttpOnly ;
  • un analyseur XML qui accepte les entités externes (XXE).

Comptes par défaut, pages et fichiers oubliés

Les robots qui parcourent Internet testent en permanence les mêmes adresses. Voici quelques lignes du journal d'accès d'AppliFrais, dix minutes après sa mise en ligne :

text
198.51.100.23 - - [12/Mar/2026:09:12:40 +0100] "GET /.env HTTP/1.1" 404 153 "-" "Mozilla/5.0 zgrab/0.x"
198.51.100.23 - - [12/Mar/2026:09:12:41 +0100] "GET /.git/config HTTP/1.1" 404 153 "-" "Mozilla/5.0 zgrab/0.x"
198.51.100.23 - - [12/Mar/2026:09:12:41 +0100] "GET /phpmyadmin/ HTTP/1.1" 404 153 "-" "Mozilla/5.0 zgrab/0.x"
198.51.100.23 - - [12/Mar/2026:09:22:30 +0100] "GET /wp-login.php HTTP/1.1" 404 153 "-" "Mozilla/5.0 zgrab/0.x"

Ici tout renvoie 404 : la configuration est saine. Sur un serveur mal réglé, chacune de ces adresses est une catastrophe.

Élément exposéCe que l'attaquant obtient
/.envLes mots de passe de la base, les clés d'API, le secret de signature des jetons
/.git/Tout le code source et son historique, y compris les secrets commités puis « supprimés »
/phpmyadmin/, /adminer.phpUne interface d'administration de la base, à attaquer par force brute
phpinfo.phpLa configuration complète de PHP, les chemins, les modules, les variables d'environnement
backup.sql, site.zip, config.php.bakUne copie de la base ou du code, lisible en clair
Listage de répertoire activéLa liste de tous les fichiers d'un dossier, justificatifs compris
Compte admin / admin sur un outilUn accès complet, sans même chercher de faille

Les parades : la racine du serveur web ne contient que les fichiers publics (le dossier public/ d'un projet Laravel ou Symfony, le dist/ d'un front compilé) ; les fichiers cachés sont refusés ; le listage est désactivé ; les outils d'administration ne sont pas exposés sur Internet ; tous les mots de passe par défaut sont changés à l'installation.

Messages d'erreur trop bavards

Une erreur en production ne doit révéler à l'utilisateur ni la pile d'appels, ni la requête SQL, ni les chemins des fichiers, ni les versions des logiciels. Ces informations aident l'attaquant à préparer une injection ou à chercher une CVE.

text
Error: ER_BAD_FIELD_ERROR: Unknown column 'mdp' in 'where clause'
    at PromiseConnection.execute (/srv/appli-frais/node_modules/mysql2/promise.js:112:22)
    at connexion (/srv/appli-frais/src/routes/auth.js:18:31)

En quelques lignes, l'attaquant apprend le SGBD, le nom d'une colonne, l'arborescence du serveur et la bibliothèque utilisée. Les réglages de production :

PlateformeRéglage
Node.js / ExpressNODE_ENV=production ; un gestionnaire d'erreurs global qui renvoie un message générique (chapitre 12)
PHP (php.ini)display_errors = Off, log_errors = On, expose_php = Off
LaravelAPP_DEBUG=false dans le .env de production
SymfonyAPP_ENV=prod ; jamais le profiler en production
ASP.NET CorePas de UseDeveloperExceptionPage() hors de l'environnement Development

Le détail de l'erreur ne disparaît pas : il part dans les journaux du serveur, où le développeur le retrouve.

Séparer les environnements

Une application passe par plusieurs environnements, chacun avec son rôle. Le référentiel du BTS insiste sur ce point : les environnements de développement, de test et de production doivent être isolés.

EnvironnementRôleDonnéesRègles
DéveloppementÉcrire et essayer le code, sur le poste du développeurJeu d'essai fictifDébogage autorisé ; aucun secret de production
Test / intégrationExécuter les tests automatisés et les tests de recetteJeu d'essai ou données anonymiséesRéinitialisable à volonté
Préproduction (staging)Valider une version dans des conditions proches du réelDonnées anonymiséesMême configuration que la production, secrets différents
ProductionServir les vrais utilisateursDonnées réellesDébogage interdit ; accès restreint et journalisé

Trois règles en découlent :

  1. Jamais de données réelles en développement ou en test. Une copie de la base de production sur le portable d'un stagiaire est une fuite de données personnelles qui attend son heure. On anonymise (noms remplacés, RIB supprimés, montants brouillés) ou on génère des données fictives.
  2. Des secrets différents dans chaque environnement. Le mot de passe de la base de test ne doit ouvrir que la base de test.
  3. Une configuration identique et automatisée. Les écarts entre préproduction et production sont des failles en puissance. Les fichiers de configuration, les Dockerfile et les scripts de déploiement sont versionnés et rejoués, pas recopiés à la main.

Isoler l'application

L'isolation limite ce qu'un attaquant peut faire s'il prend pied dans un composant.

  • Un compte système dédié. L'application tourne sous un utilisateur sans privilèges (appfrais), jamais sous root. Si une faille permet d'exécuter des commandes, elles s'exécutent avec ces droits réduits.
  • Un compte de base de données dédié, avec les seuls droits nécessaires (droits SQL et moindre privilège).
  • Des conteneurs. Chaque service dans son conteneur, avec un utilisateur non root, un système de fichiers en lecture seule quand c'est possible, et seulement les ports nécessaires publiés. La base n'a aucun port ouvert vers l'extérieur : seule l'API la joint, sur un réseau Docker interne.
  • La segmentation réseau. L'API dans une DMZ, la base dans un réseau interne, un pare-feu entre les deux qui n'ouvre que le port 3306 depuis l'API (cours Cybersécurité des infrastructures).

Un extrait de compose.yaml pour AppliFrais :

yaml
services:
  api:
    build: .
    user: "1000:1000"            # pas root dans le conteneur
    read_only: true
    environment:
      NODE_ENV: production
    env_file: .env.production    # secrets hors de l'image
    ports:
      - "127.0.0.1:3000:3000"    # derrière le reverse proxy uniquement
    networks: [interne]
  db:
    image: mysql:8.4
    volumes: [donnees:/var/lib/mysql]
    networks: [interne]          # aucun port publié : invisible depuis l'extérieur
networks:
  interne:
volumes:
  donnees:

Le cours Docker détaille la construction d'images et les réseaux.

Durcir le serveur web

Le serveur web ou le reverse proxy placé devant l'API (nginx, Apache, Caddy) se règle aussi. Un extrait de configuration nginx :

nginx
server {
    listen 443 ssl;
    server_name frais.gsb.fr;
    root /srv/appli-frais/public;

    server_tokens off;              # ne pas afficher la version de nginx
    autoindex off;                  # pas de listage des répertoires
    client_max_body_size 10m;       # taille maximale d'un envoi (justificatifs)

    location ~ /\. { deny all; }    # refuse .env, .git, .htaccess…

    location /api/ {
        proxy_pass http://127.0.0.1:3000;
    }
}

Les en-têtes de sécurité (HSTS, CSP, X-Content-Type-Options…) se posent au même endroit ou dans l'application : le chapitre 9 les présente un par un.

XXE : les entités externes XML

Un fichier XML peut déclarer des entités externes qui pointent vers un fichier ou une URL. Un analyseur mal configuré les résout :

xml
<?xml version="1.0"?>
<!DOCTYPE note [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<note><libelle>&xxe;</libelle></note>

Si AppliFrais importe des notes de frais au format XML et affiche le libellé, il affiche le contenu de /etc/passwd. C'est l'injection d'entité externe XML (XXE, CWE-611), classée en A02 car il s'agit d'un réglage par défaut dangereux. La parade : désactiver la résolution des entités externes et des DTD dans l'analyseur (c'est le comportement par défaut de PHP depuis la version 8.0 avec libxml 2.9 et plus, mais pas de tous les analyseurs Java ou .NET anciens), ou préférer JSON.

La gestion des secrets

Un secret est une information qui donne un accès : mot de passe de la base, clé d'API d'un service de paiement ou d'envoi de mails, secret de signature des jetons JWT, clé de chiffrement, clé privée d'un certificat. La règle d'or : un secret n'est jamais écrit dans le code source.

js
// VULNÉRABLE : le mot de passe part dans Git, dans chaque copie du projet, dans chaque image Docker
const pool = mysql.createPool({ host: '10.0.1.10', user: 'app_frais', password: 'Gsb2026!', database: 'gsb_frais' });

Un dépôt est copié sur chaque poste de l'équipe, sur la plateforme d'hébergement, dans les sauvegardes, parfois rendu public par erreur. Des robots parcourent GitHub en continu à la recherche de clés : une clé de cloud publiée par erreur peut être exploitée en quelques minutes.

La pratique courante est la variable d'environnement, lue au démarrage :

js
const pool = mysql.createPool({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
});

En développement, les variables viennent d'un fichier .env à la racine du projet. Node.js le lit nativement depuis la version 20.6 (node --env-file=.env src/server.js) ; en PHP, la bibliothèque vlucas/phpdotenv joue ce rôle, et Laravel et Symfony l'intègrent.

FichierContenuVersionné ?
.envLes vraies valeurs de ton posteNon : listé dans .gitignore
.env.exampleLes noms des variables, avec des valeurs vides ou facticesOui : il documente la configuration attendue
.gitignoreContient la ligne .env (et .env.* sauf .env.example)Oui
text
# .gitignore
.env
.env.*
!.env.example

En production, les secrets ne sont pas dans un fichier du dépôt non plus. Selon l'hébergement : variables d'environnement définies sur la plateforme, secrets de l'orchestrateur (Docker, Kubernetes), ou coffre-fort de secrets (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, Google Secret Manager) qui stocke les secrets chiffrés, contrôle qui les lit, journalise chaque accès et facilite leur rotation. Dans GitHub Actions, les secrets du dépôt (${{ secrets.DB_PASSWORD }}) sont masqués dans les journaux d'exécution.

Un secret a fuité dans Git : que faire ?

Tu as commité le .env avec le mot de passe de la base, puis tu l'as supprimé dans le commit suivant. Le mot de passe est toujours dans l'historique : git log -p le montre à qui clone le dépôt.

L'ordre des actions compte :

  1. Révoquer et remplacer le secret, tout de suite. Change le mot de passe de la base, régénère la clé d'API. C'est la seule action qui supprime le risque : le secret a pu être copié dès qu'il a été poussé.
  2. Vérifier dans les journaux qu'il n'a pas été utilisé entre-temps.
  3. Seulement ensuite, nettoyer l'historique si nécessaire (git filter-repo, BFG Repo-Cleaner) et prévenir l'équipe, qui devra recloner.
  4. Ajouter le fichier au .gitignore et installer un garde-fou pour la prochaine fois.

Les garde-fous automatiques :

  • gitleaks recherche des secrets dans l'historique d'un dépôt (gitleaks git .) ou dans des fichiers (gitleaks dir .), et peut tourner en hook de pré-commit pour bloquer le commit fautif ;
  • GitHub secret scanning détecte les formats de clés connus dans les dépôts, et sa protection à l'envoi (push protection) refuse un git push qui contient une clé reconnue.

Le chapitre 14 les branche dans l'intégration continue.

La checklist avant la mise en production

PointVérification
Mode débogageDésactivé (NODE_ENV=production, APP_DEBUG=false, display_errors=Off)
ErreursMessage générique pour l'utilisateur, détail dans les journaux
ComptesAucun compte ni mot de passe par défaut ; comptes de test supprimés
FichiersRacine web limitée aux fichiers publics ; .env, .git, sauvegardes inaccessibles
OutilsphpMyAdmin, consoles, routes de débogage absents ou non exposés
SecretsHors du code, différents de ceux de test, accès restreint
Comptes techniquesApplication et base sous des comptes sans privilèges inutiles
RéseauSeuls les ports nécessaires ouverts ; base non exposée
HTTPSCertificat valide, redirection de HTTP vers HTTPS, HSTS
En-têtes et cookiesEn-têtes de sécurité posés ; cookies Secure, HttpOnly, SameSite
VersionsServeur, framework et dépendances à jour

Exercice 1 sur 3 Que révèle cette erreur ? Réussi À revoir

En saisissant une apostrophe dans le champ de recherche, un testeur obtient cette page :

text
Fatal error: Uncaught PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064
You have an error in your SQL syntax near ''' at line 1 in /var/www/html/frais/recherche.php:14
  1. Liste quatre informations que l'attaquant en tire.
  2. Quelles catégories du Top 10 2025 sont concernées ?
  3. Quels réglages et quelles corrections proposes-tu ?
Voir la réponseMasquer la réponse de l'exercice 1
  1. Le SGBD (MySQL ou MariaDB, erreur 1064), le langage et la bibliothèque (PHP, PDO), le chemin du fichier sur le serveur (/var/www/html/frais/recherche.php) et surtout le fait que l'apostrophe casse la requête : elle est construite par concaténation, donc vulnérable à l'injection SQL.
  2. A02:2025 (erreur affichée à l'utilisateur, configuration de production absente), A05:2025 (injection SQL) et, plus largement, A10:2025 (exception non attrapée).
  3. En configuration : display_errors = Off et log_errors = On dans php.ini, un gestionnaire d'erreurs qui affiche un message générique. Dans le code : une requête préparée (chapitre 7). Les deux sont nécessaires : masquer l'erreur ne supprime pas l'injection.

Tu avais trouvé ?

Exercice 2 sur 3 Ranger les secrets Réussi À revoir

Un étudiant pousse son projet sur GitHub avec un fichier config.js qui contient export const JWT_SECRET = 'secret'; et le mot de passe de sa base.

  1. Cite deux problèmes distincts dans cette ligne.
  2. Donne, dans l'ordre, les actions à mener maintenant que le dépôt est public.
  3. Réécris la configuration correctement, avec les fichiers à créer.
Voir la réponseMasquer la réponse de l'exercice 2
  1. Le secret est dans le code, donc dans Git et dans toutes les copies du dépôt ; et il est faible : secret se devine, ce qui permet de forger des jetons JWT valides (n'importe qui peut devenir administrateur).
  2. D'abord changer le secret JWT (tous les jetons existants deviennent invalides, c'est voulu) et le mot de passe de la base ; vérifier les journaux ; ensuite seulement nettoyer l'historique Git et ajouter les garde-fous (.gitignore, gitleaks, protection à l'envoi).
  3. Le code lit process.env.JWT_SECRET et process.env.DB_PASSWORD. Un fichier .env, non versionné, contient les vraies valeurs, avec un secret JWT long et aléatoire (par exemple 64 caractères hexadécimaux générés par node -e "console.log(crypto.randomBytes(32).toString('hex'))"). Un fichier .env.example, versionné, liste les noms des variables sans valeur. Le .gitignore contient .env.

Tu avais trouvé ?

Exercice 3 sur 3 Isoler les environnements Réussi À revoir

Une équipe teste AppliFrais sur une copie complète de la base de production, avec le même compte MySQL root que la production. Énumère les risques et propose une organisation correcte.

Voir la réponseMasquer la réponse de l'exercice 3

Risques : des données personnelles réelles (noms, RIB, déplacements) se retrouvent sur des postes et des serveurs moins protégés, ce qui constitue une violation potentielle du RGPD ; un test qui supprime ou modifie des lignes peut viser la production par erreur, puisque le même compte y donne accès ; le compte root a tous les droits, donc une erreur ou une faille en test peut tout détruire ; une fuite du mot de passe de test compromet la production.

Organisation : une base de test distincte, remplie de données fictives ou anonymisées ; un compte dédié par environnement avec les seuls droits nécessaires (jamais root pour l'application) ; des secrets différents ; la production accessible seulement aux personnes et aux serveurs qui en ont besoin, avec des accès journalisés.

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 : Chaîne d'approvisionnement (A03) et intégrité (A08) : dépendances et SBOMRevoir le sommaire du cours