sajjit.soti
← Retour aux projets

Étude de cas — Mémoire de fin d’études en cybersécurité

QR-SafeScan : un moteur anti-hameçonnage qui n’a jamais besoin de faire confiance au réseau

Les QR codes relient le monde physique au monde numérique sans aucun filtre de sécurité — un autocollant malveillant posé sur un horodateur contourne tous les pare-feu et passerelles email d’une entreprise, car l’attaque ne transite jamais par le réseau de l’organisation. QR-SafeScan est une application React Native développée avec trois coéquipiers à l’EPITA qui détecte ces attaques de « Quishing » avant même que le navigateur ne charge la page, grâce à 27 heuristiques locales et une couche de renseignement sur les menaces qui ne s’active que pour les URL réellement ambiguës.

100 % / 100 %

Précision / rappel sur le jeu de validation

<1,5 s

Temps de verdict (p95)

27

Vérifications heuristiques locales

~95 %

Des scans qui ne quittent jamais l'appareil

Développé par Sajjit Soti, Yahya Borghol, Yuvaraj Reddy Thimmapuram et Gatcha Mouhamed Moustapha · encadré par le Prof. Mohammad Salman Nadeem · EPITA Graduate School of Computer Science, 2026.

01

Le problème

Un QR code ne fournit aucun signal visuel sur sa destination — la barre d’adresse du navigateur, que l’on consulterait normalement, n’existe pas encore au moment du scan. Cela crée ce que la recherche appelle une asymétrie d’information: l’attaquant connaît la destination, la victime non, jusqu’à ce qu’il soit trop tard. Les solutions existantes imposaient un choix impossible entre deux modes d’échec :

  • Les scanners caméra basiques — instantanés, mais sans aucune vérification de sécurité ; ils ouvrent simplement le lien.
  • Les applications antivirus « lourdes » — une vraie détection, mais qui journalise chaque scan sur un compte cloud, sacrifiant la vie privée au profit de la sécurité.

Personne ne proposait de scanner léger offrant une protection réelle sanssurveiller les habitudes de scan de l’utilisateur — c’est exactement ce vide que le mémoire cherchait à combler.

02

Architecture : la vie privée par construction, pas par politique

SafeScan repose sur un modèle à deux niveaux. Environ 90 % du travail s’effectue directement sur le téléphone, via le moteur Hermes JS de React Native — non pas parce qu’un serveur serait trop coûteux, mais parce qu’un serveur qui ne reçoit jamais vos données ne peut ni les divulguer, ni être perquisitionné, ni être compromis avec elles.

Niveau 1 — Moteur de sécurité local

27 vérifications heuristiques s’exécutent de manière synchrone sur la chaîne d’URL scannée, entièrement hors ligne. Si une vérification produit un verdict fiable — sain ou dangereux — l’application affiche immédiatement le résultat, avec 0 ms de latence réseau, et l’URL ne quitte jamais l’appareil. C’est le chemin « fail fast ».

Niveau 2 — Couche de renseignement externe

Seules les URL que le moteur local ne peut pas trancher avec confiance sont envoyées — en parallèle — à Google Safe Browsing, VirusTotal et AlienVault OTX. Les résultats sont normalisés en un verdict unique Sûr / Suspect / Malveillant, quel que soit le format d’origine.

Les trois sources externes ont chacune été choisies pour combler une lacune précise : Google Safe Browsing pour sa rapidité et la plus grande liste noire de phishing au monde, VirusTotal pour sa profondeur — un consensus entre plus de 70 moteurs antivirus, ce qui évite qu’un seul moteur trop zélé ne génère un faux positif — et AlienVault OTX pour les menaces issues de la communauté, pas encore répertoriées par les fournisseurs commerciaux.

03

Le pivot : quand l’architecture « correcte » était la mauvaise

L’équipe n’a pas démarré avec ce design. Le plan initial reposait sur un serveur middleware Node.js centralisé : le client envoie l’URL, le serveur interroge chaque API de menace séquentiellement, puis répond. Cela ressemblait au choix conventionnel et sûr. Deux semaines plus tard, ce choix échouait sur les deux critères les plus importants du projet :

  • Latence. Les appels API séquentiels (Google → VirusTotal → OTX) faisaient dépasser 5 secondes au temps de scan — bien au-delà du seuil de ~2 secondes à partir duquel les utilisateurs abandonnent un outil de sécurité au profit de la commodité.
  • Confidentialité.Un serveur central par lequel transite chaque scan est, par définition, un honeypot — exactement ce que la promesse « stateless » du mémoire cherchait à éviter.

La correction fut architecturale, pas incrémentale : déplacer l’orchestration côté client et déclencher les trois appels API en parallèle avec Promise.allSettled. Le temps d’attente total passe de la somme des trois appels au temps du plus lent d’entre eux seul — le temps de verdict est tombé de ~1,76 s à ~0,92 s, une amélioration de 48 %, et il n’y avait plus de serveur intermédiaire à compromettre.

// Séquentiel (legacy) : Google (350ms) + VirusTotal (900ms) + OTX (500ms) ≈ 1,76s
// Parallèle (SafeScan) : max(350ms, 900ms, 500ms) ≈ 0,92s — borné par l'appel le plus lent

const results = await Promise.allSettled([
  checkGoogleSafeBrowsing(url),
  checkVirusTotal(url),
  checkAlienVaultOTX(url),
]);
// Une API en échec/limitée (ex. limite VirusTotal de 4 req/min en offre gratuite)
// ne fait pas planter le scan — les deux autres résultats se résolvent quand même.
04

Quatre vérifications du moteur local à 27 points

Contrairement à une API de liste noire, le moteur local raisonne sur les propriétésd’une URL — ce qui lui permet de détecter des menaces jamais vues auparavant.

Typosquatting — distance de Levenshtein

Les hameçonneurs enregistrent des domaines qui ressemblent presque aux vrais — paypa1.com contre paypal.com. SafeScan calcule la distance d’édition par rapport à une liste blanche de cibles à forte valeur ; une distance de 1 à 2 caractères (proche, mais pas identique) déclenche une alerte de typosquatting de sévérité élevée.

for (const legit of LEGITIMATE_DOMAINS) {
  const distance = levenshtein(mainDomain, legitMain);
  if (distance > 0 && distance <= 2 && mainDomain !== legitMain) {
    return { type: "TYPOSQUATTING", severity: "HIGH",
             message: `Possible typosquatting of ${legit}` };
  }
}

Attaques homographes — détection multi-script

Une usurpation plus sophistiquée remplace des lettres latines par des caractères cyrilliques ou grecs visuellement identiques — à l’œil, аpple.com ressemble au domaine d’Apple, mais son Punycode est totalement différent. Une table de correspondance caractère-par-caractère détecte l’incohérence.

const HOMOGRAPH_CHARS = {
  'a': ['а', 'ɑ', 'α'],  // Correspondances visuelles cyrilliques / grecques
  'c': ['с', 'ϲ'],
  'e': ['е', 'ė'],
  // ...
};

Entropie de Shannon — domaines générés automatiquement

Les domaines générés par des botnets (x8z9q2.cn) ont une aléatoire de caractères bien plus élevé que de vrais mots comme « facebook » ou « nytimes ». Calculer l’entropie de Shannon du nom d’hôte et signaler tout ce qui dépasse un seuil de densité de 3,5 bits permet de détecter le trafic d’algorithmes de génération de domaines (DGA) sans aucune requête externe.

URL en IP brute ou obfusquées en hexadécimal

Les sites légitimes demandent presque jamais à un utilisateur de naviguer vers une adresse IP brute — c’est un moyen courant d’échapper aux listes de blocage basées sur le DNS. Une couche d’expressions régulières détecte les formats IPv4, IPv6 et IP encodées en hexadécimal (http://0x7f000001) avant même que l’application n’envisage un appel réseau.

05

Du JSON brut au feu tricolore

La première version testable affichait les résultats bruts sous forme d’objet JSON. Les testeurs bêta trouvaient des termes comme « Heuristic Match » ou « DNS A-Record » intimidants et appuyaient sur « Ignorer » par confusion — un véritable échec UX pour un outil dont la mission entière est de changer un comportement en une fenêtre de deux secondes. La correction a réduit un verdict à trois variables (score de risque, résultat API, nombre d’heuristiques) en un seul signal familier :

  • Vert — Sûr. Aucune heuristique déclenchée, ou la couche API a confirmé que le lien est sain.
  • Jaune — Suspect. Un signal mineur (ex. un raccourcisseur d’URL) sans résultat API concluant ; l’utilisateur peut continuer avec un avertissement visible.
  • Rouge — Dangereux. Confiance > 90 % (ex. un positif VirusTotal, ou un schéma de phishing connu). Le bouton « Ouvrir le lien » est désactivé par défaut pour éviter un clic accidentel.
06

Trois attaques que l’application a été conçue pour détecter

Menu de restaurant

Un QR code de table mène vers une fausse page de paiement quasi parfaite sur paypa1.com. Détecté par le typosquatting avant tout appel réseau.

Horodateur

Un autocollant recouvre le vrai code et pointe vers une adresse IP brute. Signalé « Suspect » par la détection d’IP, avec un avertissement explicite à ne pas saisir d’informations de paiement.

Site de phishing zero-day

Un domaine enregistré dix minutes plus tôt passe les vérifications locales et n’est pas encore dans la liste noire de Google — mais le consensus des moteurs VirusTotal le signale comme malveillant, et SafeScan affiche l’avertissement.

07

Vérifier la promesse de confidentialité, pas seulement l’affirmer

« Stateless » est un mot facile à écrire dans un README et difficile à prouver. L’équipe a simulé un scénario de saisie de serveur — 100 scans malveillants, suivis d’un audit de la mémoire d’exécution et des journaux serveur. Résultat : aucune URL scannée ni adresse IP n’a persisté nulle part après la fermeture de la transaction HTTP. Combiné au design à deux niveaux, environ 95 % des scans réels (les URL clairement sûres ou clairement dangereuses) ne génèrent aucune requête réseau — seuls les ~5 % ambigus atteignent une API externe.

08

Résultats

MétriqueSéquentiel (legacy)Parallèle (SafeScan)
Temps de verdict total~1,76 s~0,92 s (48 % plus rapide)
Précision (jeu de validation)100 % — aucun faux positif ; les marques en liste blanche restent saines
Rappel (jeu de validation)100 % — 24/24 menaces détectées, dont les variantes de typosquatting
Données conservées par scan0 (vérifié par audit)

Testé sur un appareil Android de milieu de gamme (Pixel 6a) via une connexion 4G LTE standard.

09

Comment l’équipe a travaillé

Quatre sprints Agile Action Learning de deux semaines, après qu’un plan Waterfall initial se soit révélé trop rigide pour un problème aussi exploratoire. La sécurité a été intégrée au processus, pas ajoutée après coup : modélisation des menaces STRIDE avant d’écrire du code, plugins de sécurité ESLint détectant des problèmes comme les ReDoS dans la logique de validation, GitHub Actions exécutant la suite de 112 tests unitaires à chaque pull request, et une matrice de risque probabilité-impact qui, par exemple, avait anticipé la limite de 4 requêtes/minute de VirusTotal en offre gratuite et ajouté préventivement un cache local de 5 minutes.