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

Post-Mortem — Les Plus Gros Hacks DeFi du T1 2026

Des millions perdus au T1 2026. Patterns récurrents, nouveaux vecteurs d'attaque, et ce qu'il faut retenir.

# Post-Mortem — Les Plus Gros Hacks DeFi du T1 2026

Le T1 2026 a vu ~$180M volés dans la DeFi. Nouveaux vecteurs d'attaque, vieux patterns qui reviennent, et quelques leçons douloureuses.

Voici les 5 hacks majeurs, ce qui s'est passé, et ce qu'il faut retenir.

1. Bridge Exploit via Malléabilité de Signature (~$45M)

Protocole : CrossChain Bridge (anonymisé)

Date : 12 janvier 2026

Pertes : $45M

Ce qui s'est passé

Un bridge cross-chain utilisait des signatures ECDSA pour autoriser les retraits. L'attaquant a exploité la malléabilité des signatures ECDSA.

// Contrat vulnérable (simplifié)

contract Bridge {

mapping(bytes32 => bool) public processedWithdrawals;

function withdraw(

address to,

uint256 amount,

bytes memory signature

) external {

bytes32 messageHash = keccak256(abi.encodePacked(to, amount));

address signer = recoverSigner(messageHash, signature);

require(signer == validator, "Invalid signature");

// ❌ Bug : pas de nonce, seulement le hash du message

require(!processedWithdrawals[messageHash], "Already processed");

processedWithdrawals[messageHash] = true;

token.transfer(to, amount);

}

function recoverSigner(bytes32 hash, bytes memory sig) internal pure returns (address) {

(uint8 v, bytes32 r, bytes32 s) = splitSignature(sig);

return ecrecover(hash, v, r, s);

}

}

Exploit : les signatures ECDSA ont deux formes valides (malléabilité) :

signature (r, s, v)  → valide

signature (r, -s mod n, v') → aussi valide (même signer, même message)

L'attaquant a :

  • Obtenu une signature valide pour un retrait
  • Calculé la signature malléable ``(r, -s mod n, v')
    3. Soumis les deux signatures → deux retraits pour le même message
  • Cause racine

    • Pas de nonce dans le message signé
    • Vérification basée surmessageHash` seul (pas sur la signature)
    • Pas de protection contre la malléabilité ECDSA

    Leçon

    Toujours inclure un nonce dans les messages signés :

    // Fix
    

    mapping(uint256 => bool) public usedNonces;

    function withdraw(

    address to,

    uint256 amount,

    uint256 nonce, // ← Nonce unique

    bytes memory signature

    ) external {

    bytes32 messageHash = keccak256(abi.encodePacked(to, amount, nonce));

    address signer = recoverSigner(messageHash, signature);

    require(signer == validator, "Invalid signature");

    require(!usedNonces[nonce], "Nonce used");

    usedNonces[nonce] = true;

    token.transfer(to, amount);

    }

    Ou utiliser OpenZeppelin ECDSA qui normalise `s` pour empêcher la malléabilité.

    2. Manipulation d'Oracle via Prêt Flash (~$32M)

    Protocole : YieldFarm Protocol (anonymisé)

    Date : 28 janvier 2026

    Pertes : $32M

    Ce qui s'est passé

    Un protocole de lending utilisait un spot price oracle basé sur une pool Uniswap V3.

    // Oracle vulnérable
    

    contract PriceOracle {

    IUniswapV3Pool public pool;

    function getPrice() external view returns (uint256) {

    (uint160 sqrtPriceX96,,,,,,) = pool.slot0();

    // ❌ Bug : utilise le prix instantané (manipulable)

    return uint256(sqrtPriceX96) ** 2 >> 192;

    }

    }

    // Protocole de lending

    contract LendingPool {

    function borrow(uint256 collateralAmount) external {

    uint256 collateralValue = oracle.getPrice() * collateralAmount;

    uint256 maxBorrow = collateralValue * 80 / 100; // 80% LTV

    // ...

    }

    }

    Exploit :

  • Prêt flash de 50,000 ETH
  • Swap massif ETH → collateral token (pump le prix spot)
  • Dépose le collateral, emprunte au prix gonflé
  • Swap inverse (dump le prix)
  • Rembourse le flash loan
  • Profit ~$32M
  • Cause racine

    • Oracle basé sur `slot0()` (prix spot instantané)
    • Pas de TWAP (time-weighted average price)
    • Pool de liquidité insuffisante (manipulation facile)

    Leçon

    Ne JAMAIS utiliser le prix spot pour des décisions financières critiques.

    // Fix : TWAP sur 30 minutes
    

    contract TWAPOracle {

    IUniswapV3Pool public pool;

    uint32 public constant TWAP_PERIOD = 1800; // 30 min

    function getPrice() external view returns (uint256) {

    uint32[] memory secondsAgos = new uint32[](2);

    secondsAgos[0] = TWAP_PERIOD;

    secondsAgos[1] = 0;

    (int56[] memory tickCumulatives,) = pool.observe(secondsAgos);

    int56 tickCumulativeDelta = tickCumulatives[1] - tickCumulatives[0];

    int24 arithmeticMeanTick = int24(tickCumulativeDelta / int56(uint56(TWAP_PERIOD)));

    return getQuoteAtTick(arithmeticMeanTick);

    }

    }

    Mieux : utiliser Chainlink ou Pyth pour les prix critiques.

    3. Griefing de Restaking Slashing (~$28M)

    Protocole : EigenLayer-like Restaking Protocol

    Date : 15 février 2026

    Pertes : $28M (slashing)

    Ce qui s'est passé

    Un protocole de restaking permettait de slasher des validators malveillants. L'attaquant a exploité une race condition dans le processus de slashing.

    // Contrat de restaking (simplifié)
    

    contract RestakingPool {

    mapping(address => uint256) public stakes;

    mapping(bytes32 => bool) public slashingProofs;

    function slash(

    address validator,

    bytes memory proof

    ) external {

    bytes32 proofHash = keccak256(proof);

    require(!slashingProofs[proofHash], "Already slashed");

    require(verifyMisbehavior(proof), "Invalid proof");

    // ❌ Bug : TOCTOU (time-of-check/time-of-use)

    slashingProofs[proofHash] = true;

    uint256 slashAmount = stakes[validator] / 2;

    stakes[validator] -= slashAmount;

    // ...

    }

    }

    Exploit : l'attaquant a soumis la même preuve de slashing dans deux transactions dans le même block.

  • Tx1 : `slash(validator, proof)` → slash 50%
  • Tx2 : `slash(validator, proof)` → re-slash 50% du reste
  • Répété via MEV bundling → validator slashé à 100%
  • Cause racine

    • Pas de reentrancy guard
    • Vérification `slashingProofs` après validation (TOCTOU)
    • Pas de limite sur le slashing total

    Leçon

    Protéger contre les race conditions et double-exécution :

    // Fix
    

    function slash(

    address validator,

    bytes memory proof

    ) external nonReentrant { // ← Reentrancy guard

    bytes32 proofHash = keccak256(proof);

    require(!slashingProofs[proofHash], "Already slashed");

    slashingProofs[proofHash] = true; // ← Marquer AVANT logique

    require(verifyMisbehavior(proof), "Invalid proof");

    uint256 slashAmount = stakes[validator] / 2;

    stakes[validator] -= slashAmount;

    // ...

    }

    Ou utiliser commit-reveal pour éviter la manipulation MEV.

    4. Cascade de Depeg LST (~$40M)

    Protocole : Multi-protocol Depeg

    Date : 8 mars 2026

    Pertes : $40M (liquidations en cascade)

    Ce qui s'est passé

    Pas vraiment un "hack", mais une cascade de liquidations suite au depeg d'un Liquid Staking Token (LST).

  • LST A depeg : un exploit sur le protocole de staking fait chuter le prix de 1.0 → 0.85 ETH
  • Lending protocols : les utilisateurs avec LST A comme collateral sont liquidés
  • Selling pressure : les liquidateurs vendent LST A → prix chute à 0.70
  • Contagion : d'autres LSTs (B, C) perdent confiance → depeg aussi
  • Cascade : ~$40M de liquidations en 2 heures
  • // Protocole de lending (simplifié)
    

    contract LendingPool {

    uint256 public constant LIQUIDATION_THRESHOLD = 120; // 120%

    function liquidate(address borrower) external {

    uint256 collateralValue = oracle.getPrice(lstToken) * collateral[borrower];

    uint256 debtValue = debt[borrower];

    // Si collateral < 120% de la dette → liquidation

    require(collateralValue * 100 < debtValue * LIQUIDATION_THRESHOLD, "Healthy");

    // ❌ Problème : vente massive → prix chute → plus de liquidations

    uint256 seizeAmount = debt[borrower] * 108 / 100; // 8% bonus

    lstToken.transferFrom(borrower, msg.sender, seizeAmount);

    }

    }

    Cause racine

    • Collateral non-diversifié (concentration sur 1-2 LSTs)
    • Oracles lents (pas de circuit breaker sur depeg rapide)
    • Liquidation bonus trop élevé (incentive à liquider massivement)

    Leçon

    Diversifier le collateral et ajouter des circuit breakers :

    // Fix : circuit breaker
    

    contract SafeLendingPool {

    mapping(address => uint256) public lastPrice;

    uint256 public constant MAX_PRICE_CHANGE = 10; // 10% max par heure

    function liquidate(address borrower) external {

    uint256 currentPrice = oracle.getPrice(lstToken);

    uint256 priceChange = abs(currentPrice - lastPrice[lstToken]) * 100 / lastPrice[lstToken];

    // Circuit breaker : si prix change > 10% → pause liquidations

    require(priceChange <= MAX_PRICE_CHANGE, "Circuit breaker triggered");

    // ...

    }

    }

    Ou réduire le liquidation bonus pour éviter les incentives pervers.

    5. Frontrun d'Intent Cross-Chain (~$15M)

    Protocole : Intent-based DEX Aggregator

    Date : 22 mars 2026

    Pertes : $15M

    Ce qui s'est passé

    Un nouveau protocole d'intent-based trading cross-chain permettait aux utilisateurs de signer des "intents" (ordres off-chain). Les fillers exécutaient ces intents on-chain.

    // Protocole d'intent (simplifié)
    

    struct Intent {

    address user;

    address tokenIn;

    address tokenOut;

    uint256 amountIn;

    uint256 minAmountOut;

    uint256 deadline;

    bytes signature;

    }

    function fillIntent(Intent memory intent) external {

    require(block.timestamp <= intent.deadline, "Expired");

    require(verifySignature(intent), "Invalid sig");

    // ❌ Bug : pas de slippage protection cross-chain

    uint256 amountOut = swap(intent.tokenIn, intent.tokenOut, intent.amountIn);

    require(amountOut >= intent.minAmountOut, "Slippage");

    tokenOut.transfer(intent.user, amountOut);

    }

    Exploit : l'attaquant a frontrun les intents en :

  • Monitorer les intents soumis dans le mempool
  • Swap massif pour manipuler le prix cross-chain
  • Remplir l'intent au prix manipulé (juste au-dessus de `minAmountOut``)
  • Swap inverse → profit
  • Cause racine

    • Intents publics dans le mempool (pas de privacy)
    • Deadline trop longue (plusieurs minutes → temps de manipuler)
    • minAmountOut trop laxiste (pas de protection réelle)

    Leçon

    Utiliser des enchères privées (private mempools, MEV-Boost+, etc.) ou commit-reveal :

    // Fix : commit-reveal pour intents
    

    mapping(bytes32 => bool) public commits;

    function commitIntent(bytes32 intentHash) external {

    commits[intentHash] = true;

    }

    function revealIntent(Intent memory intent) external {

    bytes32 intentHash = keccak256(abi.encode(intent));

    require(commits[intentHash], "Not committed");

    require(block.timestamp <= intent.deadline, "Expired");

    // ...

    }

    Ou router via private RPC (Flashbots Protect, MEV Blocker).

    Patterns Récurrents

    Les 5 hacks partagent des patterns communs :

  • Pas de nonce/replay protection (Bridge)
  • Oracle manipulation (YieldFarm)
  • Race conditions (Restaking)
  • Manque de circuit breakers (LST depeg)
  • MEV/frontrunning (Intent protocol)
  • Ce qu'il faut retenir

    Pour les devs :

    • Nonces dans toutes les signatures
    • TWAP, jamais spot price
    • Reentrancy guards partout
    • Circuit breakers sur prix/liquidations
    • Private mempools pour les ordres sensibles

    Pour les utilisateurs :

    • Diversifiez votre collateral (pas tout sur 1 LST)
    • Surveillez les health factors de près (surtout en période volatile)
    • Utilisez des protocols audités (multi-audits > single audit)

    Pour l'écosystème :

    • Bug bounties > post-mortem
    • Audits continus, pas one-off
    • Formal verification pour les protocoles critiques

    Le T1 2026 a été coûteux. Espérons que le T2 sera plus safe. 🛡️

    Prêt à mettre en pratique ?

    Applique ces concepts avec des exercices interactifs sur Solingo.

    Commencer gratuitement