Introduction
La recherche d’une petite boutique de plein air doit renvoyer uniquement les produits publiés dont le nom correspond exactement. Une erreur de construction permet à la recherche de changer cette règle ; même une apostrophe ordinaire la fait échouer. Vous suivrez de vraies requêtes, réparerez une petite fonction Python et testerez sécurité et résultats utiles.
Workstation, App Server et Shop Web sont prêts. Seules la saisie de commandes et la navigation entre onglets sont nécessaires. SQL, la syntaxe Python de cette modification et l’éditeur sont expliqués ici. Les données sont synthétiques ; le service HTTP et l’analyse SQL sont réels.
Effectuer une recherche normale de produit
Dans cette étape, vous utiliserez la boutique et relierez un résultat à sa ligne dans la base. Vous aidez une petite boutique de plein air à réparer sa recherche. Elle doit renvoyer uniquement les produits publiés dont le nom correspond exactement à la saisie. Tous les produits et prix sont des données synthétiques de formation.
Sélectionnez l’onglet Shop Web dans l’interface du laboratoire pour commencer cette étape.
Une table conserve les enregistrements en lignes, décrites par des colonnes nommées. Voici la table products préparée ; listed vaut 1 pour un produit public et 0 pour un prototype interne. Les prix sont des unités monétaires entières de formation.
| id | name | price | listed |
|---|---|---|---|
| 1 | Trail Mug | 18 | 1 |
| 2 | Camper's Cup | 22 | 1 |
| 3 | Prototype Mug | 99 | 0 |
| 4 | ORANGE Bowl | 12 | 1 |
Passez à l’onglet Shop Web. Saisissez Trail Mug dans Product name, puis sélectionnez Search. rows doit contenir [1, "Trail Mug", 18] et error valoir null (aucune erreur). La page envoie une requête à l’application Python, qui demande les enregistrements correspondants à SQLite puis renvoie le résultat. Page, code et base sont des éléments distincts de ce trajet.

Une requête pose une question à la base. La trace pédagogique locale montre le SQL exécuté par SQLite :
SELECT id, name, price FROM products WHERE listed = 1 AND name = 'Trail Mug' ORDER BY id
SELECT nomme les colonnes renvoyées ; FROM la table. WHERE restreint les lignes. AND exige les deux conditions : publication et nom exact. Les apostrophes délimitent une valeur texte SQL. ORDER BY id donne un ordre stable. La page ne renvoie pas la colonne listed, mais la requête l’utilise pour filtrer.
Le terminal Workstation est le client qui émet les requêtes. App Server ouvre le répertoire de l’application pour la réparation ultérieure. Ce sont deux sessions sur le même hôte de formation, pas deux machines. La page décrit cette relation et provient de l’application réellement en cours d’exécution.
Passez à l’onglet Workstation pour émettre la même requête :
Passez à l’onglet Workstation avant d’exécuter la commande suivante.
python3 probe.py "Trail Mug"
python3 lance le client préparé probe.py. Son seul argument est le texte recherché. Les guillemets doubles conservent un nom avec des espaces dans un argument ; le shell retire ces guillemets extérieurs. Le client encode correctement la valeur dans une requête HTTP, format utilisé par l’application web. 127.0.0.1 désigne ici cet hôte de formation.
Lisez rows, trace et error dans la sortie JSON, la réponse structurée de l’application. Les tableaux utilisent des crochets et null signifie aucune erreur. Si la page ne se connecte pas, rechargez Shop Web ; une erreur de connexion diffère d’une recherche réussie sans résultat. Le contrôle lit le résultat réel de la requête : aucun rapport n’est nécessaire.
Observer l’entrée modifier la requête
Dans cette étape, vous observerez un test limité d’injection SQL sur cette boutique de formation. L’injection SQL signifie qu’une entrée modifie la structure de la requête au lieu de rester une valeur texte. Réservez ces tests à ce labo préparé.
Sélectionnez l’onglet Workstation dans l’interface du laboratoire pour commencer cette étape.
Passez à l’onglet App Server. Lisez la petite fonction avec cat, qui affiche le fichier sans le modifier :
Passez à l’onglet App Server avant d’exécuter la commande suivante.
cat query.py
def search(db, text): définit la fonction. db est la connexion ouverte ; text la valeur reçue. Les lignes indentées forment son corps. sql = affecte une chaîne Python à une variable. Les guillemets doubles délimitent cette chaîne ; les apostrophes internes délimitent le texte SQL. + text + concatène, donc joint directement, la valeur utilisateur au SQL. db.execute(sql) demande son exécution ; fetchall() collecte les lignes ; return les renvoie.
Passez à l’onglet Workstation et envoyez cette valeur exacte, espace après les deux tirets inclus :
Passez à l’onglet Workstation avant d’exécuter la commande suivante.
python3 probe.py "' OR 1=1 -- "
Les guillemets doubles extérieurs appartiennent au shell. L’apostrophe initiale entre dans SQLite et ferme la valeur texte vide de la requête concaténée. OR autorise l’une ou l’autre condition ; 1=1 est toujours vrai. -- commence un commentaire : l’apostrophe finale et le tri d’origine sont ignorés. La trace contient :
SELECT id, name, price FROM products WHERE listed = 1 AND name = '' OR 1=1 -- ' ORDER BY id
Les quatre lignes apparaissent, dont [3, "Prototype Mug", 99]. Ce prototype n’est pas publié. La requête a franchi la limite métier bien qu’elle ait seulement lu des données. Une réponse HTTP réussie ne prouve donc pas la sécurité d’une recherche.
Essayez un nom légitime avec une apostrophe :
python3 probe.py "Camper's Cup"
L’application vulnérable renvoie rows: [], une trace vide et error contenant near "s": syntax error. SQLite n’a pas pu exécuter ce SQL mal formé ; aucune requête terminée n’est à tracer. Cet échec est attendu, et le client se termine normalement pour vous laisser continuer.
Une apostrophe est une donnée produit valide. La supprimer changerait le nom demandé ; refuser toutes les entrées qui en contiennent bloquerait un client. Bloquer des mots comme OR casserait aussi ORANGE Bowl. La réparation doit conserver la valeur en la séparant de la structure SQL.
Lier la valeur recherchée
Dans cette étape, vous réparerez la fonction en conservant colonnes, correspondance exacte, publication et tri. Passez à l’onglet App Server.
Sélectionnez l’onglet App Server dans l’interface du laboratoire pour commencer cette étape.
Une requête paramétrée sépare structure SQL et valeurs. Dans l’API SQLite de Python, ? marque une position de valeur. Fournissez celle-ci séparément à execute ; ne mettez pas le marqueur entre apostrophes et ne concaténez pas la valeur au SQL. Par exemple, db.execute("SELECT ?", (text,)) renvoie le texte comme donnée sans l’interpréter comme SQL.
(text,) est un tuple à un élément, une collection ordonnée de paramètres. La virgule finale crée le tuple ; (text) seul n’est que la valeur entre parenthèses. Les parenthèses extérieures de execute(sql, (text,)) appellent une fonction avec deux arguments : le modèle SQL et le tuple de valeurs. Le pilote lie la valeur au marqueur.
Ouvrez le fichier dans l’éditeur du terminal :
Passez à l’onglet App Server avant d’exécuter la commande suivante.
nano query.py
Déplacez-vous avec les flèches, effacez avec Backspace/Delete et tapez pour modifier. Remplacez les deux lignes indentées commençant par sql = et return pour obtenir exactement :
def search(db, text):
sql = "SELECT id, name, price FROM products WHERE listed = 1 AND name = ? ORDER BY id"
return db.execute(sql, (text,)).fetchall()
Gardez quatre espaces devant chaque ligne du corps, aucun devant def. Python regroupe le corps grâce à l’indentation. Enregistrez avec Ctrl+O, confirmez query.py avec Enter, puis quittez avec Ctrl+X. Le commentaire initial peut rester.
L’application lit et compile query.py à chaque requête. Enregistrer suffit, sans redémarrage. Un tampon non enregistré n’a aucun effet. Une faute de frappe ou d’indentation apparaît dans error à la réponse suivante. Rouvrez le même fichier, corrigez, enregistrez et renvoyez la requête.
Passez à l’onglet Workstation et réessayez les deux valeurs :
Passez à l’onglet Workstation avant d’exécuter la commande suivante.
python3 probe.py "Camper's Cup"
python3 probe.py "' OR 1=1 -- "
Le vrai produit renvoie désormais [2, "Camper's Cup", 22] ; le test spécial renvoie rows: []. Tous deux ont error: null. Aucun produit de cette base ne porte exactement ce nom spécial.
La trace peut afficher Camper''s Cup ou tout le texte spécial entre apostrophes. SQLite affiche le SQL développé après liaison, valeurs échappées comprises, pas le modèle d’origine. Voir des valeurs dans la trace ne signifie pas que Python les a concaténées : examinez query.py pour voir modèle et tuple séparés. Ne remplacez pas les paramètres liés par un échappement manuel.
Préserver la recherche métier
Dans cette étape, vous testerez ensemble sécurité et fonctionnement normal. Désactiver la recherche ou renvoyer un résultat vide fixe pourrait cacher le prototype, mais empêcherait les clients de trouver les produits.
Sélectionnez l’onglet Workstation dans l’interface du laboratoire pour commencer cette étape.
Passez à l’onglet Workstation et renvoyez le nom normal :
Passez à l’onglet Workstation avant d’exécuter la commande suivante.
python3 probe.py "Trail Mug"
Vous devez toujours obtenir [1, "Trail Mug", 18]. Vérifiez qu’un mot légitime contenant OR reste accepté :
python3 probe.py "ORANGE Bowl"
Résultat attendu : [4, "ORANGE Bowl", 12]. Recherchez le prototype non publié par son nom normal :
python3 probe.py "Prototype Mug"
Attendez rows: [] et error: null. La liaison empêche l’injection ; la restriction intacte listed = 1 contrôle toujours les produits publics. La supprimer pendant la réparation serait dangereux.
Revenez à l’onglet Shop Web, saisissez Camper's Cup, sélectionnez Search et confirmez sa ligne. Répétez avec ' OR 1=1 -- et confirmez un résultat vide sans erreur. Navigateur et client utilisent la même fonction.

Lancez le contrôle de l’étape. Il émet des requêtes en lecture seule et exécute votre fonction inchangée sur des lignes synthétiques supplémentaires dans une base mémoire jetable. Il teste correspondance exacte, ponctuation, publication et noms inconnus sans modifier la base active. Même un nom ressemblant à SQL doit rester recherchable comme donnée s’il existe exactement et est publié.
En cas d’échec, revenez à l’onglet App Server, rouvrez query.py et comparez marqueur, virgule du tuple, indentation et restriction métier à la réparation. Enregistrez puis retestez. Gardez base, client et serveur inchangés : modifier des enregistrements ou fixer des réponses ne répare pas la requête.
Résumé
Vous avez suivi une recherche jusqu’à une vraie requête SQLite, observé l’entrée changer sa structure et réparé l’appel Python avec un marqueur et un tuple à une valeur. Vous avez testé noms ordinaires, apostrophes, test limité et règle de publication intacte. Le défi des commandes applique la même séparation à un autre objet métier.


