Introduction
Le portail des membres de Harbor permet de récupérer un compte après l'oubli d'un mot de passe. Le courriel contient un jeton temporaire : sa possession devrait autoriser une seule modification pour un seul compte. L'assistance a découvert que les liens restent réutilisables et peuvent modifier le mot de passe d'un autre compte.
Vous récupérerez d'abord un compte fictif normalement, observerez le comportement dangereux, puis réparerez un petit gestionnaire Python. Aucune expérience Python n'est nécessaire : fonctions et conditions sont expliquées avant la modification. Vous devez savoir saisir une commande et changer d'onglet.
Utilisez Portal Web pour l'application, Mailbox Web pour les courriels simulés et App Server pour le code préparé. Tout fonctionne sur cette instance de formation indépendante, sans envoi de courriel externe. Jetons, comparaisons d'expiration, transactions, modifications de mots de passe et connexions sont réels ; cette petite application n'est pas un système d'identité de production.
Récupérer un compte normalement
Dans cette étape, vous suivrez un courriel de réinitialisation puis vous connecterez avec le nouveau mot de passe, afin d'établir le fonctionnement à préserver.
Un compte identifie une personne ; un mot de passe prouve qu'elle peut se connecter. En cas d'oubli, un lien autorise temporairement sa modification. Le jeton du lien n'est pas le mot de passe. Cette boîte locale permet d'incarner les propriétaires des comptes fictifs maya et noah, initialement associés à StartPass42!. Utilisez uniquement ces comptes de formation.
Dans Portal Web, trouvez Request a reset link. Saisissez maya dans Recovery account, puis cliquez sur Send reset link. Le résultat indique Link requested. Open Local Mailbox. Actualisez Mailbox Web : le message le plus récent apparaît en haut, avec destinataire, Open reset link et chaîne du jeton.
Cliquez sur Open reset link dans ce message. Le formulaire s'ouvre dans le même onglet avec Token et Reset account préremplis. Ne les modifiez pas ; entrez FirstPass42! dans New password, puis cliquez sur Reset password. Attendez Reset accepted et HTTP 200.
HTTP est le dialogue de requêtes et réponses entre navigateur et serveur. Ici, 200 signifie accepté, 403 signifie refusé et 500 signale une erreur de l'application. Une erreur de connexion diffère d'un refus de sécurité.
Ouvrez Portal Web, actualisez si nécessaire, puis utilisez Sign in : Login account maya, Password FirstPass42!, bouton Sign in. Attendez Login accepted. Une réinitialisation fonctionnelle modifie réellement le mot de passe, et pas seulement une étiquette.
Si vous perdez un lien, demandez-en un autre avec le même formulaire et utilisez le message le plus récent. Il reste valable 30 minutes. Une faute de frappe ou un lien expiré ne nécessite pas de redémarrer l'environnement.
Observer la frontière défaillante
Dans cette étape, vous effectuerez des modifications de démonstration jetables pour distinguer propriétaire, durée de validité et autorisation à usage unique.
Le navigateur envoie des champs nommés contenant une valeur. L'utilisateur peut les modifier : le serveur ne doit donc pas faire confiance à Reset account parce que le courriel l'a prérempli. Il conserve un enregistrement distinct :
| Champ | Signification |
|---|---|
account |
Compte autorisé à utiliser le jeton |
expires_at |
Dernier instant autorisé, en secondes depuis une origine temporelle fixe |
used |
0 avant utilisation ; 1 après |
Un nouveau jeton n'est utilisable que par son propriétaire avant expiration. Une réinitialisation réussie le rend utilisé ; il ne doit jamais redevenir utilisable. Un jeton inutilisé peut aussi expirer avec le temps. Aucun de ces deux états n'autorise une autre réinitialisation.
Demandez un nouveau lien pour maya dans Portal Web. Actualisez Mailbox Web, ouvrez le dernier message et définissez DemoPass42!. Rouvrez le lien du même message et définissez ReplayDemo42!. Les deux requêtes affichent Reset accepted. La seconde aurait dû être refusée : c'est un rejeu, la réutilisation d'une autorisation censée être épuisée.
Rouvrez encore ce lien. Remplacez Reset account par noah et New password par CrossDemo42!, puis cliquez sur Reset password. La requête est acceptée. Dans Portal Web, connectez-vous avec noah et CrossDemo42!. Le lien émis pour maya a modifié noah : le serveur n'a pas lié le jeton au compte.
Enfin, dans le formulaire de Portal Web, entrez Token expired-demo, Reset account maya et New password ExpiredDemo42!, puis cliquez sur Reset password. Ce jeton a expiré une heure avant la préparation ; pourtant la demande est acceptée. Le serveur ignore l'horloge. Vous n'avez pas à attendre un minuteur.
Ces comptes sont jetables ; les tests suivants utilisent des copies propres. Conservez l'état observé sans modifier directement la base de données.
Lier et consommer le jeton
Dans cette étape, vous réparerez la petite fonction serveur et la testerez sans modifier les comptes de démonstration actifs.
Passez dans App Server, initialisé dans /home/labex/project/reset. cat affiche un fichier texte ; son argument est le nom du fichier. Lisez le gestionnaire :
cat handler.py
La première ligne importe deux fonctions auxiliaires préparées. def définit une fonction ; les noms entre parenthèses sont ses paramètres, les entrées d'un appel. eligible(row, account, now) reçoit l'enregistrement stocké, le compte soumis et l'heure serveur. row["account"] lit un champ. row is None signifie qu'aucun jeton n'a été trouvé. Le bloc indenté de if ne s'exécute que si la condition est vraie ; return False refuse, tandis que return True accorde l'autorisation. Quatre espaces initiaux définissent ici un bloc Python.
La fonction actuelle renvoie True pour tout jeton existant, ce qui explique les trois failles. reset appelle aussi unsafe_reset, qui modifie le mot de passe sans consommer le jeton.
La réparation exige toutes les conditions. == compare, > signifie supérieur à, et and exige que les deux côtés soient vrais. or accepterait l'un ou l'autre : une propriété valide masquerait une propriété invalide. Une expiration future est supérieure à now ; l'égalité signifie déjà expiré. used == 0 n'autorise qu'un jeton inutilisé.
L'auxiliaire consume_and_reset ouvre une transaction SQLite, lit le jeton actuel, appelle votre fonction eligible, puis valide ensemble le changement de mot de passe et used = 1. C'est l'atomicité : les modifications réussissent ensemble. Il verrouille avant de lire, empêchant deux requêtes concurrentes de voir toutes deux un jeton inutilisé. Vous n'implémentez ni verrouillage ni hachage.
Ouvrez le petit fichier avec l'éditeur de terminal nano :
nano handler.py
Remplacez son contenu par le code suivant. Utilisez les flèches et Retour arrière/Suppr pour enlever l'ancien texte, puis collez le remplacement complet. Respectez espaces et ponctuation ; les parenthèses autorisent une condition sur plusieurs lignes.
from framework import unsafe_reset, consume_and_reset
def eligible(row, account, now):
if row is None:
return False
return (row["account"] == account
and row["expires_at"] > now
and row["used"] == 0)
def reset(db, token, account, password):
return consume_and_reset(db, token, account, password, eligible)
Enregistrez avec Ctrl+O, confirmez handler.py avec Enter, puis quittez avec Ctrl+X. Chaque requête Web relit le code enregistré : aucun redémarrage n'est nécessaire. En cas de HTTP 500, rouvrez et comparez indentation, crochets et guillemets.
python3 exécute un programme Python. Le vérificateur reçoit le chemin du gestionnaire et démarre une application HTTP indépendante avec des données SQLite temporaires. Il teste le code réel sans le réparer ni modifier les comptes actifs :
python3 /opt/reset-lab/check.py handler.py
Attendez onze lignes PASS, dont valid reset accepted, replay denied et competing use has exactly one winner. FAIL nomme la propriété incorrecte. Tout refuser fait échouer les tests légitimes de récupération et de connexion. Modifier le navigateur ou la base active ne répare pas le code testé sur des données neuves. Rouvrez nano handler.py, corrigez, enregistrez et relancez le même vérificateur si nécessaire.
Prouver que la récupération fonctionne encore
Dans cette étape, vous utiliserez le service réparé et relierez ses résultats aux tests de sécurité isolés.
Dans Portal Web, demandez un nouveau lien pour maya. Actualisez Mailbox Web, ouvrez le dernier message et définissez ResetSafe42!. Attendez Reset accepted et HTTP 200. Rouvrez le lien du même message et essayez ReplayAgain42! pour maya : attendez Reset denied et HTTP 403.
Demandez un autre lien neuf pour maya. Ouvrez-le, remplacez Reset account par noah et essayez BlockedCross42!. La demande doit être refusée sans consommer le lien inutilisé du propriétaire. Rouvrez le même message, conservez maya et définissez ResetSafe42! : cela doit réussir.
Dans le formulaire, essayez Token expired-demo, Reset account maya et New password BlockedExpired42!. Attendez un refus. Dans Portal Web, connectez-vous avec maya et ResetSafe42! : Login accepted prouve que les échecs n'ont pas changé le mot de passe. Vous pouvez aussi connecter noah avec son mot de passe de démonstration CrossDemo42! ; les autres comptes sont préservés.
Dans App Server, relancez les tests de régression jetables :
python3 /opt/reset-lab/check.py handler.py
Les onze tests doivent toujours réussir, dont deux utilisations simultanées avec une seule réinitialisation acceptée. Les répéter préserve les comptes actifs. Il faut autoriser la récupération et refuser l'abus : désactiver la page ou tout refuser n'est pas une réparation réussie.
L'application simule la livraison et simplifie l'identité. En production, il faut aussi une livraison sûre, des limites de fréquence, une gestion des sessions et une politique de mots de passe, hors de ce périmètre. Le guide OWASP apporte ce contexte.
Résumé
Vous avez suivi une réinitialisation réelle, démontré rejeu, changement intercompte et expiration, puis réparé un petit gestionnaire Python. Propriétaire stocké, expiration future, état inutilisé et consommation atomique agissent ensemble. Des tests HTTP jetables prouvent récupération valide, abus refusé, autres comptes inchangés et unique gagnant concurrent. Le défi du personnel applique ces règles à une autre condition incomplète.



