La faille : les réponses lisibles par tous
Jouer l'attaquant contre votre propre base de TP pour comprendre pourquoi le serveur doit rester seul maître des réponses.
Objectifs de la séance
- observer le trafic réseau de l'application avec les outils du navigateur ;
- lire les bonnes réponses du quiz avec une simple commande
curlet la clé publique ; - constater qu'un score calculé par le client peut être falsifié ;
- comprendre le principe : le client n'est jamais digne de confiance ;
- identifier les trois failles à corriger dans les séances suivantes.
Petite vérification
Avant de commencer, vérifiez si vous savez...
- lancer l'application, ouvrir l'écran « Mon pseudo » (bouton « Changer de pseudo » de l'accueil), saisir
abet lire le message affiché sous le champ - dans le tableau de bord Supabase (Table Editor), ouvrir la table
joueuret lire les trois colonnes de la ligne qui contient votre pseudo - citer les caractères autorisés dans un pseudo et sa longueur
📌 Réponse attendue
TP pour réfléchir et résoudre des problèmes
Dans ce TP, vous êtes l'attaquant. Votre cible : le quiz que vous avez vous-même construit. Votre but : obtenir toutes les bonnes réponses sans jamais lire une seule question, puis obtenir un score parfait sans jouer.
Les manipulations de cette séance sont autorisées uniquement parce qu'elles visent votre propre projet Supabase de TP, dont vous êtes le propriétaire. Accéder ou se maintenir sans autorisation dans le système d'information d'un tiers (même sans rien casser, même avec une clé publique) est un délit puni par la loi (articles 323-1 et suivants du Code pénal, sur l'accès frauduleux à un système de traitement automatisé de données). Ne testez jamais l'application d'un camarade, d'une entreprise ou d'un site quelconque sans autorisation écrite. Si vous découvrez une faille chez un tiers, signalez-la de façon responsable à son propriétaire plutôt que de l'exploiter.
Gardez le projet en cours d'exécution : npx expo start, puis la touche w pour la version Web. Vous aurez besoin
de l'adresse de votre projet (https://xxxx.supabase.co) et de la clé publishable, que vous trouvez dans votre fichier
.env.local (variables EXPO_PUBLIC_SUPABASE_URL et EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY).
Sous PowerShell, curl est un alias d'une autre commande : tapez curl.exe à la place, ou utilisez le terminal Git Bash
(installé avec Git) dans VS Code. Les exemples ci-dessous sont écrits pour un terminal de type Bash.
Étape 1 — Espionner le trafic réseau de l'application
Dans le navigateur, ouvrez l'application, puis les outils de développement avec F12. Ouvrez l'onglet Réseau
(Network) et cochez le filtre Fetch/XHR. Créez ensuite un quiz (n'importe quel thème, 3 questions). Dans la liste des
requêtes apparaît une requête vers .../rest/v1/question?select=.... Cliquez dessus, puis ouvrez l'onglet Réponse.
Que lisez-vous ? Pour chaque question, la liste des réponses avec, pour chacune, une valeur est_correcte égale à true ou false.
Le serveur a envoyé les bonnes réponses au navigateur avant même que vous ayez répondu.
Pour évaluer la sécurité d'une application, demandez-vous : « que verrais-je si je regardais ce qui circule sur le réseau ? ». Cette habitude (le modèle de menace) révèle plus de failles qu'une relecture du code.
Étape 2 — Rejouer la requête sans l'application
Voici le plus important : vous n'avez pas besoin de l'application. N'importe qui, avec l'adresse du projet et la clé
publishable (qui figure dans le code de l'application, donc publique par nature), peut interroger directement l'API REST de Supabase.
Dans un terminal, remplacez VOTRE-PROJET et VOTRE_CLE_PUBLISHABLE par vos valeurs :
curl "https://VOTRE-PROJET.supabase.co/rest/v1/reponse?select=question_id,texte,est_correcte&limit=5" \
-H "apikey: VOTRE_CLE_PUBLISHABLE"
Le résultat est un tableau JSON de 5 réponses avec leur est_correcte. La clé est transmise dans un en-tête HTTP.
Elle sert à identifier le projet, pas à le protéger. Ce qui protège réellement les données, ce sont les droits (grant) et la RLS de la base. Une
clé secrète (service_role) ne doit en revanche jamais apparaître dans une application.
Étape 3 — Ne garder que les bonnes réponses
Ajoutez un filtre : est_correcte=eq.true signifie « les lignes dont est_correcte est égal à true ». Complétez la ligne qui
n'envoie plus que les bonnes réponses :
Exécutez la commande. Vous obtenez 27 lignes, une bonne réponse par question, par exemple :
[{"question_id":1,"texte":"Velo-Rouge-Mange-7-Nuages!","est_correcte":true},
{"question_id":2,"texte":"Non, il faut un mot de passe différent par compte","est_correcte":true}, ...]
Avec cette liste, vous répondez juste à tous les quiz sans réfléchir : le jeu n'a plus aucun sens.
Notez trois choses : qui peut l'exploiter (n'importe qui, sans compte), avec quels moyens (une commande de deux lignes), avec quel impact (le jeu perd tout intérêt). Plus l'attaque est simple et l'impact élevé, plus la correction est urgente.
Étape 4 — Falsifier un score
Deuxième faiblesse : à la fin d'une partie, l'application envoie le score à l'écran de résultat dans l'adresse (/resultat?score=...). Le score est
calculé par l'application, sur le téléphone du joueur. Ouvrez dans le navigateur (adaptez le port si besoin) une adresse que vous fabriquez :
L'écran affiche « Quiz terminé ! » avec 5 / 5 en 1 seconde. Vous n'avez répondu à aucune question. Le travail de validation des paramètres d'URL (tutoriel 5) n'y change rien : le score est cohérent (5 sur 5), donc accepté.
Vérifier qu'une valeur est bien formée (un nombre, entre 0 et le total) ne prouve pas qu'elle est vraie. Un score affiché par l'application n'a de valeur que s'il a été calculé par quelqu'un en qui on a confiance : le serveur.
Étape 5 — Dresser le bilan et préparer les corrections
Recopiez et complétez ce petit rapport d'incident (dans un fichier texte ou sur papier) :
| Faille constatée | Qui peut l'exploiter ? | Comment la corriger ? | Séance |
|---|---|---|---|
| Les bonnes réponses sont envoyées au navigateur | n'importe qui | ne plus envoyer est_correcte ; corriger les réponses côté serveur | 40 et 50 |
| Le score est calculé et transmis par le client | n'importe quel joueur | calculer le score dans une fonction SQL du serveur | 60 |
| La durée est mesurée par le client | n'importe quel joueur | mesurer la durée sur le serveur | 60 |
Pour la première ligne, la solution passera par deux idées : retirer le droit de lire la colonne est_correcte, et confier la correction à des fonctions SQL
que seul le serveur exécute.
On corrige d'abord la faille la plus grave et la plus simple à exploiter. Ici, la lecture des réponses (étape 3) passe avant la falsification du score (étape 4).
Étape 6 — Vérifier que rien n'est à enregistrer
Cette séance ne modifie aucun fichier de l'application : vous avez seulement observé et interrogé le serveur. Vérifiez-le
(vous restez sur la branche feature/securite) :
git status
Git répond « nothing to commit, working tree clean » (rien à enregistrer). Il n'y a donc pas de commit pour cette séance : un commit sans modification n'aurait
aucun sens. Si git status affiche des fichiers modifiés, c'est que vous avez changé le code par erreur : annulez ces modifications avec git restore . après les avoir relues.
Chaque commit correspond à une modification précise du projet. Une séance d'observation ne produit pas de commit : c'est normal et c'est sain.
📌 Une solution
Notions théoriques
Le client n'est jamais digne de confiance
Une application mobile ou Web s'exécute chez l'utilisateur, sur un appareil qu'il contrôle entièrement. Tout ce qui s'y trouve ou qui en sort peut être lu, copié et modifié : le code, les données reçues, les requêtes envoyées, les valeurs en mémoire. Cacher un bouton, brouiller le code ou vérifier une valeur dans l'application peut gêner un joueur honnête qui se trompe, mais n'arrête aucun attaquant.
Toute décision de sécurité doit être prise côté serveur. Le client sert à afficher et à demander, jamais à décider. C'est la première cause de vulnérabilité des applications : le contrôle d'accès défaillant occupe la première place du classement OWASP Top 10 (A01).
Ce qui protège réellement des données dans Supabase
| Élément | Visible par l'attaquant ? | Protège les données ? |
|---|---|---|
| Code de l'application | oui (il est téléchargé) | non |
| URL du projet et clé publishable | oui (dans le code) | non : elles ne font que désigner le projet |
Droits grant / revoke | non | oui |
| Règles RLS | non | oui |
Fonctions SQL security definer | non (seul leur résultat) | oui, si elles vérifient tout |
L'autorité du serveur
Nous allons appliquer un principe simple : le serveur est la seule autorité sur tout ce qui compte.
| Question | Qui répond aujourd'hui ? | Qui doit répondre ? |
|---|---|---|
| Quelles sont les bonnes réponses ? | le client les connaît | le serveur seul les connaît |
| Cette réponse est-elle juste ? | le client compare | le serveur corrige |
| Quel est le score ? | le client compte | le serveur calcule |
| Combien de temps a duré la partie ? | le client chronomètre | le serveur mesure |
La RLS filtre des lignes, pas des colonnes
Rappel des séances précédentes : la RLS décide quelles lignes un rôle peut lire ou écrire. Ici, c'est la colonne est_correcte qui est sensible, alors que la
ligne (la réponse « Velo-Rouge-Mange-7-Nuages! ») doit rester lisible. Il faut donc un autre mécanisme : les droits par colonne
(revoke select sur la table, puis grant select (id, question_id, texte)), que vous avez déjà croisés pour la table joueur. Et pour que le joueur obtienne tout de même la correction après
avoir répondu, des fonctions SQL feront le travail à sa place.
La défense en profondeur
Une seule protection ne suffit jamais. Pour le quiz, nous empilerons plusieurs couches :
- les droits par colonne (la colonne
est_correcten'est plus lisible) ; - la RLS (chacun ne voit que ses parties) ;
- des fonctions SQL qui vérifient tout (joueur connecté, partie en cours, réponse appartenant à la question) ;
- des limites contre les abus (nombre de parties par minute).
Cadre légal et déontologie
- Tester la sécurité d'un système impose une autorisation préalable du propriétaire. Sans elle, c'est un délit, même « pour voir » et même avec une clé publique.
- En cas de découverte d'une faille chez un tiers : ne pas l'exploiter, ne pas copier de données, la signaler de façon responsable (divulgation responsable) à l'éditeur.
- Dans ce cours, vous attaquez votre base, créée pour l'occasion, et vous la réparez ensuite : c'est la meilleure façon d'apprendre à défendre.
Test de mémorisation/compréhension
Exercices complémentaires
Exercice A : lister les failles d'une autre application fictive
Une application de vente en ligne envoie dans l'URL /paiement?prix=49.90&produit=12. Citez deux risques et expliquez qui doit connaître le vrai prix.
Exercice B : relire votre application
Cherchez dans le code de votre projet deux autres valeurs qui voyagent dans une adresse (useLocalSearchParams). Pour chacune, demandez-vous : le serveur
pourrait-il la recalculer lui-même ?
Ce qu'il faut retenir
| Notion | Résumé |
|---|---|
| Le client n'est jamais digne de confiance | Tout ce qui est dans l'application ou qui en sort peut être lu et modifié par le joueur. |
| Clé publishable | Publique par nature : elle identifie le projet, elle ne protège rien. |
| Autorité du serveur | Correction, score et durée doivent être décidés par le serveur. |
| RLS et colonnes | La RLS filtre des lignes ; pour cacher une colonne, on utilise les droits par colonne. |
| Défense en profondeur | Plusieurs couches de protection se cumulent. |
| Cadre légal | On ne teste que ses propres systèmes, ou avec autorisation écrite. |
Aperçu de la prochaine séance
Vous avez vu le problème ; il faut maintenant construire la solution. Dans la prochaine séance, vous allez écrire vos premières fonctions SQL (appelées depuis
l'application avec supabase.rpc) : demarrer_partie, qui tire les questions sans envoyer les bonnes réponses, et repondre, qui corrige côté serveur.