Aller au contenu principal

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 curl et 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...

  1. lancer l'application, ouvrir l'écran « Mon pseudo » (bouton « Changer de pseudo » de l'accueil), saisir ab et lire le message affiché sous le champ
  2. dans le tableau de bord Supabase (Table Editor), ouvrir la table joueur et lire les trois colonnes de la ligne qui contient votre pseudo
  3. 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.

Cadre légal : n'attaquez que votre propre base

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).

Windows : quel terminal pour curl ?

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.


Bonne pratique - Se mettre à la place de l'attaquant

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.


Bonne pratique - La clé publishable n'est pas un secret

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.


Bonne pratique - Mesurer la gravité d'une faille

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é.


Bonne pratique - Valider n'est pas authentifier

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éeQui peut l'exploiter ?Comment la corriger ?Séance
Les bonnes réponses sont envoyées au navigateurn'importe quine plus envoyer est_correcte ; corriger les réponses côté serveur40 et 50
Le score est calculé et transmis par le clientn'importe quel joueurcalculer le score dans une fonction SQL du serveur60
La durée est mesurée par le clientn'importe quel joueurmesurer la durée sur le serveur60

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.


Bonne pratique - Corriger dans le bon ordre

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.


Bonne pratique - Un commit raconte un changement

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.

Une règle à retenir pour toute votre carrière

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émentVisible par l'attaquant ?Protège les données ?
Code de l'applicationoui (il est téléchargé)non
URL du projet et clé publishableoui (dans le code)non : elles ne font que désigner le projet
Droits grant / revokenonoui
Règles RLSnonoui
Fonctions SQL security definernon (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.

QuestionQui répond aujourd'hui ?Qui doit répondre ?
Quelles sont les bonnes réponses ?le client les connaîtle serveur seul les connaît
Cette réponse est-elle juste ?le client comparele serveur corrige
Quel est le score ?le client comptele serveur calcule
Combien de temps a duré la partie ?le client chronomètrele 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 :

  1. les droits par colonne (la colonne est_correcte n'est plus lisible) ;
  2. la RLS (chacun ne voit que ses parties) ;
  3. des fonctions SQL qui vérifient tout (joueur connecté, partie en cours, réponse appartenant à la question) ;
  4. 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​


Pourquoi la clé publishable ne protège-t-elle pas les bonnes réponses ?


Que montre l'onglet Réseau des outils de développement du navigateur ?


Que prouve la page /resultat?score=5&total=5&secondes=1 ouverte à la main ?


Pourquoi cacher la colonne est_correcte dans le code de l'application ne suffit-il pas ?


Que décide le serveur dans le principe d'autorité du serveur ?


Dans quel cadre est-il permis de rejouer l'attaque de cette séance ?



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​

NotionRésumé
Le client n'est jamais digne de confianceTout ce qui est dans l'application ou qui en sort peut être lu et modifié par le joueur.
Clé publishablePublique par nature : elle identifie le projet, elle ne protège rien.
Autorité du serveurCorrection, score et durée doivent être décidés par le serveur.
RLS et colonnesLa RLS filtre des lignes ; pour cacher une colonne, on utilise les droits par colonne.
Défense en profondeurPlusieurs couches de protection se cumulent.
Cadre légalOn 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.