'URGENT !!! VOTRE COMPTE SERA SUPPRIMÉ AUJOURD’HUI', 'reason' => 'Urgence exagérée et abus des majuscules'], ['text' => 'FÉLICITATIONS !!! VOUS AVEZ GAGNÉ 5000 € MAINTENANT', 'reason' => 'Gain surprise, trop beau pour être vrai'], ['text' => 'CLIQUEZ ICI IMMÉDIATEMENT POUR ÉVITER LE BLOCAGE', 'reason' => 'Appel à l’action pressant et menaçant'], ['text' => 'VOTRE COLIS EST BLOQUÉ, PAYEZ TOUT DE SUITE', 'reason' => 'Demande de paiement immédiat inhabituelle'], ['text' => 'DERNIÈRE ALERTE : CONFIRMEZ VOTRE MOT DE PASSE', 'reason' => 'Un service légitime ne redemande jamais un mot de passe par email'], ['text' => 'ACTION OBLIGATOIRE DANS LES 5 MINUTES', 'reason' => 'Délai artificiellement très court'], ['text' => 'VOTRE BANQUE EXIGE UNE VÉRIFICATION IMMÉDIATE', 'reason' => 'Usurpation d’un organisme bancaire'], ['text' => 'RÉCUPÉREZ VOTRE CADEAU GRATUIT MAINTENANT', 'reason' => 'Cadeau non sollicité'], ['text' => 'ALERTE SÉCURITÉ : VOTRE IDENTITÉ EST EN DANGER', 'reason' => 'Peur infondée utilisée pour pousser à agir vite'], ['text' => 'VOUS ÊTES LE GRAND GAGNANT DU JOUR', 'reason' => 'Gain surprise non sollicité'], ['text' => 'PAIEMENT REFUSÉ !!! METTEZ À JOUR VOTRE CARTE', 'reason' => 'Fausse alerte de paiement pour voler des données bancaires'], ['text' => 'CONFIRMEZ VOS INFORMATIONS SINON FERMETURE', 'reason' => 'Menace de fermeture de compte'], ['text' => 'OFFRE SECRÈTE EXCEPTIONNELLE À RÉCUPÉRER', 'reason' => 'Offre “secrète” artificiellement exclusive'], ['text' => 'VOTRE SESSION EXPIRE, CLIQUEZ MAINTENANT', 'reason' => 'Fausse urgence de session'], ['text' => 'REMBOURSEMENT IMMÉDIAT DISPONIBLE ICI', 'reason' => 'Remboursement non sollicité'], ['text' => 'ATTENTION !!! COMPTE BLOQUÉ DANS QUELQUES MINUTES', 'reason' => 'Compte à rebours anxiogène'], ['text' => 'DERNIÈRE CHANCE AVANT SUPPRESSION DÉFINITIVE', 'reason' => 'Menace de suppression définitive'], ['text' => 'VOTRE MOT DE PASSE A ÉTÉ PIRATÉ, AGISSEZ', 'reason' => 'Fausse alerte de piratage'], ['text' => 'VÉRIFICATION URGENTE DE VOTRE CARTE BANCAIRE', 'reason' => 'Demande d’informations bancaires par email'], ['text' => 'CONFIRMEZ VOTRE IDENTITÉ POUR RECEVOIR VOTRE ARGENT', 'reason' => 'Promesse d’argent contre des données personnelles'], ]; // Liste des expéditeurs spam : mêmes formats d'affichage (nom, pas d'adresse brute) que les // expéditeurs normaux ci-dessus, pour qu'il ne soit pas possible de repérer les mails // frauduleux d'un simple coup d'œil sur l'expéditeur. La seule façon de les distinguer doit // être de lire le contenu (sujet), comme un vrai email de phishing usurpant une identité connue. $spamSenders = [ 'Sécurité du compte', 'Service client', 'Banque familiale', 'Livraison Colis', 'Support technique', 'Service facturation', 'Assurance Habitation', 'Mutuelle Santé' ]; // Liste des aperçus normaux $normalPreviews = [ 'Bonjour, nous vous confirmons votre rendez-vous prévu cette semaine.', 'Je vous envoie les informations demandées, bonne journée.', 'Votre document est prêt, vous pouvez le consulter.', 'Merci pour votre message, voici les détails utiles.', 'Votre commande a bien été prise en compte.', 'Petit rappel pour ne rien oublier demain.', 'Nous restons à votre disposition pour toute question.', 'Voici un message d’information concernant votre dossier.', 'Le service vous remercie et vous souhaite une bonne journée.', 'Vous trouverez ci-joint les éléments utiles.' ]; // Liste des aperçus spam $spamPreviews = [ 'Cliquez tout de suite sinon votre compte sera fermé dans quelques minutes.', 'Paiement obligatoire immédiat pour débloquer la situation.', 'Confirmez vos données personnelles maintenant pour éviter la suppression.', 'Votre cadeau gratuit vous attend, cliquez ici sans attendre.', 'Action urgente requise, réponse dans les 5 minutes demandée.', 'Dernier avertissement officiel avant blocage définitif.' ]; function firstLetterSafe(string $text): string { if (function_exists('mb_substr') && function_exists('mb_strtoupper')) { return mb_strtoupper(mb_substr($text, 0, 1, 'UTF-8'), 'UTF-8'); } return strtoupper(substr($text, 0, 1)); } // Construit une nouvelle série de mails pour une manche de captcha. // Le nombre de mails frauduleux varie entre 1 et 3 : un robot ne peut donc pas se // contenter de "toujours cocher une seule case au hasard", ce qui réduit fortement // les chances de réussir par pure chance (voir le calcul dans la doc du projet). function buildInboxRound( array $normalSubjects, array $normalSenders, array $normalPreviews, array $spamSubjects, array $spamSenders, array $spamPreviews, int $maxAttempts ): array { $normalPool = []; foreach ($normalSubjects as $index => $subject) { $normalPool[] = [ 'id' => 'normal_' . $index, 'sender' => $normalSenders[array_rand($normalSenders)], 'subject' => $subject, 'preview' => $normalPreviews[array_rand($normalPreviews)], 'is_spam' => false, 'reason' => null, 'time' => sprintf('%02d:%02d', random_int(8, 19), random_int(0, 59)) ]; } $spamPool = []; foreach ($spamSubjects as $index => $spam) { $spamPool[] = [ 'id' => 'spam_' . $index, 'sender' => $spamSenders[array_rand($spamSenders)], 'subject' => $spam['text'], 'preview' => $spamPreviews[array_rand($spamPreviews)], 'is_spam' => true, 'reason' => $spam['reason'], 'time' => sprintf('%02d:%02d', random_int(0, 23), random_int(0, 59)) ]; } shuffle($normalPool); shuffle($spamPool); // Entre 1 et 3 mails frauduleux dans les 10 affichés (voir commentaire de la fonction) $spamCount = random_int(1, 3); $selectedSpam = array_slice($spamPool, 0, $spamCount); $selectedNormal = array_slice($normalPool, 0, 10 - $spamCount); $selected = array_merge($selectedSpam, $selectedNormal); shuffle($selected); $correctIds = array_map(function ($mail) { return $mail['id']; }, $selectedSpam); sort($correctIds); return [ 'token' => bin2hex(random_bytes(16)), 'correct_ids' => $correctIds, 'emails' => $selected, 'attempts_left' => $maxAttempts, 'started_at' => microtime(true) ]; } if (!isset($_SESSION['captcha_round']) || !is_array($_SESSION['captcha_round'])) { $_SESSION['captcha_round'] = buildInboxRound( $normalSubjects, $normalSenders, $normalPreviews, $spamSubjects, $spamSenders, $spamPreviews, $maxAttempts ); } $round = $_SESSION['captcha_round']; $error = ''; $success = false; $locked = false; $foundReasons = []; // Regénère une série si la structure en session est invalide ou date d'avant cette mise à // jour (ancienne session sans correct_ids/started_at) : évite tout crash après déploiement if ( !isset($round['emails']) || !is_array($round['emails']) || !isset($round['correct_ids']) || !is_array($round['correct_ids']) || !isset($round['started_at']) ) { $_SESSION['captcha_round'] = buildInboxRound( $normalSubjects, $normalSenders, $normalPreviews, $spamSubjects, $spamSenders, $spamPreviews, $maxAttempts ); $round = $_SESSION['captcha_round']; } // Gestion de la soumission du formulaire if ($_SERVER['REQUEST_METHOD'] === 'POST') { $postedToken = $_POST['captcha_token'] ?? ''; $selectedMails = (isset($_POST['selected_mails']) && is_array($_POST['selected_mails'])) ? array_map('strval', $_POST['selected_mails']) : []; sort($selectedMails); // Honeypot : champ invisible pour un humain (masqué en CSS), que les robots qui // remplissent tous les champs d'un formulaire ont tendance à compléter malgré tout $honeypot = trim($_POST['website'] ?? ''); if (!hash_equals($round['token'], $postedToken)) { $error = 'Session invalide, recharge la page.'; } elseif (($round['attempts_left'] ?? 0) <= 0) { $locked = true; $error = 'Tu n’as plus d’essais. Recharge une nouvelle série.'; } elseif ($honeypot !== '') { // On fait échouer la tentative sans révéler la vraie raison, pour ne pas // donner d'indice à un robot sur la façon dont il a été détecté $_SESSION['captcha_round']['attempts_left']--; $round = $_SESSION['captcha_round']; $locked = ($round['attempts_left'] ?? 0) <= 0; $error = $locked ? 'Perdu. Tu n’as plus d’essais. Recharge une nouvelle série.' : 'Ce n’est pas la bonne sélection. Regarde mieux les messages les plus agressifs ou trop urgents.'; sendLog('Captcha : champ piège rempli (robot probable)'); } elseif ((microtime(true) - (float) $round['started_at']) < $minSolveTime) { // Une réponse aussi rapide n'est pas plausible pour un humain qui doit lire 10 mails $_SESSION['captcha_round']['attempts_left']--; $round = $_SESSION['captcha_round']; $locked = ($round['attempts_left'] ?? 0) <= 0; $error = $locked ? 'Perdu. Tu n’as plus d’essais. Recharge une nouvelle série.' : 'Ce n’est pas la bonne sélection. Prends le temps de lire les messages.'; sendLog('Captcha : soumission trop rapide (robot probable)'); } elseif ($selectedMails !== $round['correct_ids']) { $_SESSION['captcha_round']['attempts_left']--; $round = $_SESSION['captcha_round']; if (($round['attempts_left'] ?? 0) <= 0) { $locked = true; $error = 'Perdu. Tu n’as plus d’essais. Recharge une nouvelle série.'; sendLog('Captcha raté : plus aucun essai restant (possible robot)'); } else { $error = 'Ce n’est pas la bonne sélection. Regarde mieux les messages les plus agressifs ou trop urgents.'; sendLog('Captcha raté : mauvaise sélection (' . $round['attempts_left'] . ' essai(s) restant(s))'); } } else { $_SESSION['captcha_valid'] = true; // On récupère les explications des mails trouvés pour les afficher sur // l'écran de succès (petit bonus pédagogique) avant de vider la manche foreach ($round['emails'] as $mail) { if ($mail['is_spam'] && !empty($mail['reason'])) { $foundReasons[] = $mail['reason']; } } unset($_SESSION['captcha_round']); $success = true; sendLog('Captcha réussi : nouveau visiteur sur le site'); } } $attemptsLeft = $round['attempts_left'] ?? 0; ?> Test anti-robot

Vérification réussie

Bravo, tu as repéré le(s) mail(s) frauduleux. Redirection en cours...

Ce que tu as repéré :

🛡️ Test anti-robot

Vérification humaine

Sélectionne tous les emails qui te semblent frauduleux parmi les messages ci-dessous. Certains sont plus dangereux que d'autres.

Vérification demandée Repérer le phishing
Essais restants /
Recharger une nouvelle série
Test anti-robot : cochez les mails qui ressemblent à une arnaque
$mail): ?>