Tutoriels gratuits et ouverts à toutes et à tous · conçus au Lycée Chevrollier — AngersÀ propos

Série « Recettage informatique » · partie 3 / 3

Exemple de recettage : application web PHP / MariaDB

Cas complet rédigé : recette d’une application d’inscription à des ateliers, avec tests fonctionnels et de sécurité (injection SQL, XSS, accès, mots de passe, HTTPS), jusqu’au PV avec réserve.

Méthodologie✎ B. Jousseaume📅 16 octobre 2026⏱ 1 h📶 Intermédiaire

Présentation du cas

Une association souhaite une petite application web permettant à ses adhérents de s’inscrire à des ateliers. Un espace d’administration protégé par identifiant et mot de passe affiche la liste des inscrits. L’application est développée en PHP, avec une base MariaDB.

Environnement de recette

ÉlémentValeurRemarque
Serveurweb-dev01 (Debian 12)Apache 2.4, PHP 8.2, MariaDB 10.11
URL de recettehttp://inscriptions.lab.localEnvironnement interne de recette
Poste de testdev-pc01Firefox, terminal avec curl
Version testéev1.0.0 (commit a3f9c2e)Version identifiée dans le dépôt Git
Jeu de données3 ateliers, 12 inscrits fictifsAucune donnée réelle

Exigences retenues

Réf.Exigence du cahier des charges
E1Un adhérent peut s’inscrire à un atelier ; l’inscription est enregistrée en base.
E2Les saisies sont contrôlées côté serveur (champs obligatoires, format de l’e-mail), et pas seulement dans le navigateur.
E3L’application résiste aux injections SQL.
E4Les données saisies par les utilisateurs sont affichées sans pouvoir exécuter de script (protection XSS).
E5Les pages d’administration ne sont accessibles qu’après authentification.
E6Les mots de passe des administrateurs sont stockés sous forme d’empreinte (hachage), jamais en clair.
E7Les échanges avec l’application sont chiffrés (HTTPS).

Fiches de test et preuves

Test 1 : inscription valide à un atelier

Fiche de test n° 1
Exigence couverteE1
Objectif du testVérifier qu’une inscription correcte est acceptée et enregistrée.
Type de testFonctionnel
PrérequisApplication déployée ; atelier « Initiation Linux » ouvert.
Procédure1. Ouvrir la page d’inscription dans Firefox.
2. Saisir : Martin / Léa / lea.martin@example.org / Initiation Linux, puis valider.
3. Contrôler l’enregistrement dans la table inscrit.
Résultat attenduMessage de confirmation affiché ; une nouvelle ligne en base avec les valeurs saisies et la date du jour.
Résultat obtenuConforme.
Preuve(s)Captures 1a et 1b
Statut☒ Conforme
Anomalie / commentaireAucune.
Re-test après correctionSans objet.

Capture 1a : réponse de l’application (code source de la page, extrait)

<main>
  <p class="succes">Votre inscription à l'atelier « Initiation Linux » est enregistrée.</p>
  <a href="index.php">Retour à la liste des ateliers</a>
</main>

Capture 1b : contrôle en base de données

MariaDB [inscriptions]> SELECT id, nom, prenom, email, id_atelier, date_inscription
    -> FROM inscrit ORDER BY id DESC LIMIT 1;
+----+--------+--------+------------------------+------------+---------------------+
| id | nom    | prenom | email                  | id_atelier | date_inscription    |
+----+--------+--------+------------------------+------------+---------------------+
| 13 | Martin | Léa    | lea.martin@example.org |          2 | 2026-10-15 14:02:11 |
+----+--------+--------+------------------------+------------+---------------------+

Test 2 : contrôle des saisies côté serveur

Fiche de test n° 2
Exigence couverteE2
Objectif du testVérifier que le serveur refuse une saisie invalide, même si les contrôles du navigateur sont contournés.
Type de testFonctionnel / Sécurité
PrérequisTest 1 conforme. 13 inscrits en base.
Procédure1. Envoyer directement au serveur, avec curl, un formulaire sans prénom et avec un e-mail invalide.
2. Compter les inscrits en base.
Résultat attenduDeux messages d’erreur ; aucune nouvelle ligne en base (toujours 13).
Résultat obtenuConforme.
Preuve(s)Capture 2
Statut☒ Conforme
Anomalie / commentaireAucune.
Re-test après correctionSans objet.

Capture 2 : envoi d’une saisie invalide sans passer par le navigateur

dev@dev-pc01:~$ curl -s -X POST http://inscriptions.lab.local/inscription.php \
     -d 'nom=Test&prenom=&email=pas-un-email&id_atelier=2' | grep 'class="erreur"'
<p class="erreur">Le prénom est obligatoire.</p>
<p class="erreur">L'adresse e-mail n'est pas valide.</p>

MariaDB [inscriptions]> SELECT COUNT(*) FROM inscrit;
| COUNT(*) | 13 |

Test 3 : résistance à l’injection SQL

Fiche de test n° 3
Exigence couverteE3
Objectif du testVérifier qu’une saisie piégée dans l’identifiant ne permet pas de contourner l’authentification.
Type de testSécurité
PrérequisCompte administrateur « gestionnaire » existant.
Procédure1. Envoyer au formulaire de connexion l’identifiant admin' OR '1'='1' -- et un mot de passe quelconque.
2. Relire la requête de connexion dans le code source.
Résultat attenduConnexion refusée ; requête préparée avec paramètre lié.
Résultat obtenuConforme.
Preuve(s)Captures 3a et 3b
Statut☒ Conforme
Anomalie / commentaireAucune.
Re-test après correctionSans objet.

Capture 3a : tentative d’injection

dev@dev-pc01:~$ curl -s -X POST http://inscriptions.lab.local/admin/login.php \
     --data-urlencode "login=admin' OR '1'='1' -- " --data-urlencode 'mdp=x' \
     | grep -E 'erreur|Tableau de bord'
<p class="erreur">Identifiant ou mot de passe incorrect.</p>

Capture 3b : requête de connexion (admin/login.php, extrait)

$req = $pdo->prepare('SELECT id, mdp_hash FROM utilisateur WHERE login = :login');
$req->execute(['login' => $_POST['login']]);
$user = $req->fetch();

Test 4 : protection contre l’injection de script (XSS)

Fiche de test n° 4
Exigence couverteE4
Objectif du testVérifier qu’un script saisi dans le nom n’est pas exécuté lors de l’affichage dans l’espace d’administration.
Type de testSécurité
PrérequisTests 1 à 3 conformes.
Procédure1. S’inscrire avec le nom <script>alert('XSS')</script>.
2. Se connecter à l’espace d’administration et ouvrir la liste des inscrits.
3. Afficher le code source de la page.
Résultat attenduAucune fenêtre d’alerte ; le nom apparaît sous forme de texte, avec les caractères < et > échappés.
Résultat obtenu1er passage : fenêtre « XSS » affichée (non conforme). 2e passage : texte échappé.
Preuve(s)Captures 4a et 4b
Statut☒ Conforme après correction
Anomalie / commentaireAnomalie bloquante (sécurité) : faille XSS stockée dans admin/liste.php, les noms étaient affichés sans échappement. Corrigée avec htmlspecialchars().
Re-test après correction15/10/2026 — Conforme (capture 4b), et test 1 rejoué sans régression.

Capture 4a : premier passage, code source de admin/liste.php (extrait)

<tr>
  <td><script>alert('XSS')</script></td>
  <td>example</td>
  <td>test.xss@example.org</td>
</tr>

Correction appliquée dans admin/liste.php (même traitement pour le prénom et l’e-mail)

// Avant
<td><?= $inscrit['nom'] ?></td>

// Après : échappement de toutes les données affichées
<td><?= htmlspecialchars($inscrit['nom'], ENT_QUOTES, 'UTF-8') ?></td>

Capture 4b : re-test, code source après correction (extrait)

<tr>
  <td>&lt;script&gt;alert(&#039;XSS&#039;)&lt;/script&gt;</td>
  <td>example</td>
  <td>test.xss@example.org</td>
</tr>

Test 5 : accès à l’espace d’administration

Fiche de test n° 5
Exigence couverteE5
Objectif du testVérifier qu’une page d’administration n’est pas accessible sans être connecté.
Type de testDroits et accès
PrérequisAucune session ouverte sur le poste de test.
Procédure1. Demander directement la page admin/liste.php avec curl, sans cookie de session.
Résultat attenduRedirection (code 302) vers login.php ; aucune donnée d’inscrit renvoyée.
Résultat obtenuConforme.
Preuve(s)Capture 5
Statut☒ Conforme
Anomalie / commentaireAucune.
Re-test après correctionSans objet.

Capture 5 : accès direct sans authentification

dev@dev-pc01:~$ curl -s -i http://inscriptions.lab.local/admin/liste.php | head -5
HTTP/1.1 302 Found
Date: Thu, 15 Oct 2026 14:31:07 GMT
Server: Apache
Location: login.php
Content-Length: 0

Test 6 : stockage des mots de passe

Fiche de test n° 6
Exigence couverteE6
Objectif du testVérifier que les mots de passe des administrateurs sont hachés.
Type de testSécurité
PrérequisCompte « gestionnaire » créé via l’application.
Procédure1. Lire la colonne mdp_hash de la table utilisateur.
2. Relire le code de création et de vérification du mot de passe.
Résultat attenduEmpreinte bcrypt (préfixe $2y$) ; utilisation de password_hash() et password_verify().
Résultat obtenuConforme.
Preuve(s)Capture 6
Statut☒ Conforme
Anomalie / commentaireAucune.
Re-test après correctionSans objet.

Capture 6 : contenu de la table et code associé

MariaDB [inscriptions]> SELECT login, mdp_hash FROM utilisateur;
+--------------+------------------------------------------------------+
| login        | mdp_hash                                             |
+--------------+------------------------------------------------------+
| gestionnaire | $2y$10$███████████████████████████████████████████   |
+--------------+------------------------------------------------------+
// admin/creer_utilisateur.php
$hash = password_hash($_POST['mdp'], PASSWORD_DEFAULT);
// admin/login.php
if ($user && password_verify($_POST['mdp'], $user['mdp_hash'])) {

Test 7 : chiffrement des échanges (HTTPS)

Fiche de test n° 7
Exigence couverteE7
Objectif du testVérifier que l’application est servie en HTTPS.
Type de testSécurité
PrérequisAucun.
Procédure1. Demander l’application en HTTPS avec curl.
Résultat attenduRéponse HTTPS valide avec un certificat reconnu ; redirection automatique de HTTP vers HTTPS.
Résultat obtenuConnexion HTTPS refusée : le serveur de recette n’écoute qu’en HTTP.
Preuve(s)Capture 7
Statut☒ Conforme avec réserve
Anomalie / commentaireAnomalie majeure : HTTPS non configuré. Acceptée en réserve, car l’environnement de recette est interne et isolé ; mise en place d’un certificat obligatoire avant la mise en production.
Re-test après correctionÀ réaliser lors de la levée de la réserve.

Capture 7 : tentative de connexion en HTTPS

dev@dev-pc01:~$ curl -sS -I https://inscriptions.lab.local/
curl: (7) Failed to connect to inscriptions.lab.local port 443: Connection refused

Synthèse des tests

N°TestStatutAnomalie / réserveRe-test
1Inscription valideConforme——
2Contrôle des saisies côté serveurConforme——
3Injection SQLConforme——
4Injection de script (XSS)Conforme après correctionBloquante : XSS stockéeConforme
5Accès à l’administrationConforme——
6Stockage des mots de passeConforme——
7Chiffrement HTTPSConforme avec réserveMajeure : HTTPS absentÀ faire

Procès-verbal de recette

Procès-verbal de recette
Projet / installationApplication web d’inscription aux ateliers — version v1.0.1
Bilan des tests7 tests réalisés : 6 conformes (dont 1 après correction), 0 non conforme, 1 avec réserve
Décision☐ Recette prononcée sans réserve
☒ Recette prononcée avec réserves
☐ Recette refusée
Réserves éventuellesR1 — Mettre en place HTTPS (certificat valide et redirection HTTP vers HTTPS) avant toute mise en production. Échéance : avant le déploiement. La mise en production est conditionnée à la levée de cette réserve.
ObservationsPistes d’amélioration hors exigences : jeton anti-CSRF sur les formulaires, réduction de l’en-tête Server, limitation des tentatives de connexion.
TesteurNom Prénom — 15/10/2026 — signature
ValideurNom Prénom — 15/10/2026 — signature

🧠 Testez vos connaissances

5 questions. Répondez puis cliquez sur « Corriger ». Rien n’est enregistré : c’est de l’auto-évaluation.

1. Pourquoi le test 2 envoie-t-il le formulaire avec curl plutôt que par le navigateur ?

💬 Les contrôles du navigateur se contournent facilement : la validation doit être faite côté serveur.

2. Qu’est-ce qui explique que l’injection SQL du test 3 échoue ?

💬 La saisie n’est jamais concaténée dans la requête SQL : elle est traitée comme une simple valeur.

3. Comment la faille XSS stockée a-t-elle été corrigée ?

💬 Les caractères < > et ' deviennent des entités HTML : le script s’affiche comme du texte sans être exécuté.

4. Le préfixe $2y$10$ dans la colonne mdp_hash indique…

💬 password_hash() produit une empreinte bcrypt ; password_verify() la contrôle à la connexion.

5. HTTPS est absent sur le serveur de recette interne. Quelle décision est prise ?

💬 L’anomalie majeure est acceptée provisoirement, avec une échéance claire : avant tout déploiement.

© 2026 Bruno Jousseaume. Ce cours est publié sous licence CC BY-NC-SA 4.0 : vous pouvez le partager et l’adapter en citant l’auteur et un lien vers cette page, sans usage commercial, et en partageant vos adaptations sous la même licence.

Pour aller plus loin

Installer et prendre en main DBeaver

Comprendre pourquoi DBeaver expose moins le serveur que phpMyAdmin, autoriser les connexions distantes, puis installer DBeaver, se connecter, explorer la base et exécuter ses premières requêtes.

Débutant⏱ 2 h · 16 octobre 2026