Gli attacchi di reentrancy rimangono una delle minacce più famose e persistenti per la sicurezza degli smart contract su Ethereum e sulle catene compatibili con EVM. Nonostante anni di sforzi nel settore, nuovi protocolli continuano a essere vittime di vulnerabilità da reentrancy, causando perdite finanziarie significative — talvolta dell’ordine di milioni di dollari. Questi exploit si manifestano spesso in contratti DeFi di lending, staking e bridge, dove le chiamate esterne e i trasferimenti di token sono comuni.
In questo articolo, analizziamo la meccanica di un attacco di reentrancy, identifichiamo i tipici schemi contrattuali vulnerabili e condividiamo strategie di mitigazione collaudate. Basandoci sull’analisi di oltre 282 audit di smart contract condotti da Soken, evidenziamo come pratiche di codifica in Solidity raffinate e workflow di testing completi possano impedire minacce di reentrancy. Questo articolo collega inoltre rischi correlati come l’uso improprio di delegatecall e tattiche di flash loan per fornire una comprensione a tutto tondo, essenziale per ogni sviluppatore Web3 o auditor della sicurezza.
Cos’è un attacco di Reentrancy e come sfrutta gli Smart Contract?
Un attacco di reentrancy avviene quando un contratto malevolo chiama ripetutamente una funzione di un contratto vulnerabile prima che l’esecuzione iniziale sia conclusa, sfruttando uno stato inconsistente del contratto.
In pratica, una vulnerabilità di reentrancy nasce da un ordinamento scorretto delle modifiche di stato e delle chiamate esterne all’interno di un contratto. Quando il contratto trasferisce ETH o token tramite una call o transfer a un indirizzo non attendibile, il destinatario può rientrare ricorsivamente nella funzione vulnerabile prima che le variabili di stato vengano aggiornate. Questo consente all’attaccante di prosciugare fondi o manipolare la logica del contratto in modo imprevisto.
Ad esempio, il celebre hack del DAO nel 2016 ha portato all’esploit di quasi 60 milioni di dollari tramite vulnerabilità di reentrancy, un monito diventato iconico. Da allora, l’industria ha identificato che la causa principale è l’aggiornamento del bilancio o dello stato del contratto solo dopo le chiamate esterne, creando una finestra di stato incoerente sfruttata dall’attaccante.
Insight esperto dagli audit Soken: “In oltre il 40% dei nostri recenti audit di sicurezza DeFi, schemi di reentrancy emergono in qualche forma, spesso con varianti sottili che coinvolgono contratti proxy o chiamate a strati. Identificare questi richiede un’analisi approfondita delle chiamate inter-contratto, oltre la semplice ispezione di un singolo contratto.”
Schemi comuni in Solidity che causano vulnerabilità di Reentrancy
Le vulnerabilità di reentrancy tipicamente derivano da una specifica sequenza di operazioni: chiamate esterne (come trasferimenti di ETH o chiamate token) che avvengono prima che il contratto aggiorni le sue variabili di stato interne.
Lo schema più vulnerabile è il seguente:
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount);
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] -= amount;
}
Qui la chiamata esterna con call.value() avviene prima della detrazione del saldo dell’utente. Durante quella chiamata esterna, la funzione fallback di un attaccante può invocare ricorsivamente withdraw(), prosciugando fondi prima che i saldi vengano aggiornati.
Costrutti chiave a rischio includono:
- Chiamate esterne prima degli aggiornamenti di stato: invio di ETH o token prima di aggiornare i bilanci.
- Uso di
callosendsenza precauzioni: chiamate esterne tramite funzioni di basso livello aggirano i controlli e possono invocare fallback functions. - Mancanza di guardian contro la reentrancy: assenza di mutex o altri meccanismi di blocco.
- Catene di chiamate complesse con delegatecall: possono indirettamente aprire a reentrancy tramite chiamate esterne in contratti chiamati.
Al contrario, lo schema più sicuro inverte l’ordine:
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // aggiornamento stato prima
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
Come prevenire attacchi di Reentrancy in Solidity: best practice e strumenti
La difesa più efficace contro la reentrancy è aggiornare lo stato prima delle chiamate esterne combinato con pattern espliciti di protezione da reentrancy.
Tecniche standard di mitigazione:
-
Pattern Checks-Effects-Interactions:
Aggiornare sempre lo stato interno prima di effettuare chiamate esterne. Ciò riduce lo stato incoerente del contratto durante le chiamate esterne. -
Reentrancy Guards / Mutex:
IlReentrancyGuarddi OpenZeppelin usa un semplice mutex per bloccare chiamate nidificate. Esempio d’uso:
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecureContract is ReentrancyGuard {
mapping(address => uint256) balances;
function withdraw(uint256 amount) public nonReentrant {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
}
-
Limitare le Chiamate Esterne:
Minimizzare trasferimenti o interazioni all’interno di funzioni critiche — preferire pagamenti pull anziché push, con utenti che ritirano esplicitamente fondi. -
Analisi Statica e Strumenti Automatici:
Strumenti come Slither e i framework di audit Soken segnalano la reentrancy. -
Usare le Versioni Recenti di Solidity e le loro Funzionalità:
Solidity 0.8+ introduce controlli built-in sugli overflow, ma i rischi di reentrancy persistono, quindi è necessario combinare la sicurezza del codice con pattern progettuali.
Metodo Soken: I nostri audit integrano modellazione manuale delle minacce, esecuzione simbolica e fuzz testing, rilevando flussi complessi di reentrancy spesso non individuati da scanner generici.
Esempi reali di exploit di Reentrancy e il loro impatto
Gli attacchi di reentrancy restano frequenti nonostante la maggiore consapevolezza. Di seguito una comparazione di hack rilevanti che mostrano la diversità e il costo di questi exploit:
| Incidente | Data | Ammontare Perdite | Schema Sfruttato | Lezione Chiave |
|---|---|---|---|---|
| The DAO Hack | 2016-06 | ~$60 milioni | Reentrancy nel splitDAO | Aggiornare stato prima chiamata |
| The Lendf.Me Hack | 2020-04 | $25 milioni+ | Reentrancy amplificata da flash loan | Combinare guardia reentrancy + resilienza flash loan |
| Euler Finance Hack | 2023-03 | $197 milioni | Reentrancy nidificata + delegatecall | Catene di chiamate complesse aumentano il rischio |
| Recent DeFi Bridge Hack | 2025-11 | $15 milioni | Exploit di reentrancy su trasferimento token | Audit delle interazioni cross-contract |
Nota: Gli audit Soken enfatizzano la sicurezza a livelli multipli, soprattutto riguardo chiamate cross-contract e standard token che possono attivare fallback dinamicamente.
Delegatecall e vettori flash loan: amplificare i rischi di Reentrancy
Delegatecall, che esegue codice nel contesto del contratto chiamante, può oscurare il rischio di reentrancy e ampliare la superficie di attacco, soprattutto se combinato con flash loan.
Gli attacchi di reentrancy spesso sfruttano flash loan per massimizzare il capitale disponibile per prosciugamenti ricorsivi:
- I flash loan forniscono grossa liquidità immediata in una singola transazione.
- Un attaccante usa fondi presi in prestito per innescare chiamate reentrant per prosciugare la liquidità del protocollo prima di restituire il prestito.
- L’uso di delegatecall può involontariamente attivare codice esterno non attendibile che modifica lo stato del contratto o permette reentrancy.
Insight di sicurezza: “Nell’esperienza Soken, la mitigazione della reentrancy va oltre i singoli contratti a una progettazione a livello di protocollo — specialmente quando delegatecall e flash loan coesistono. I contratti devono combinare guardie di reentrancy con validazione degli ingressi e controlli specifici per flash loan (limiti sull’ammontare, restrizioni sulle chiamate ricorsive).”
Tabella comparativa: tecniche comuni per prevenire la Reentrancy
| Tecnica | Descrizione | Vantaggi | Svantaggi | Uso consigliato |
|---|---|---|---|---|
| Checks-Effects-Interactions | Aggiornare stato prima di chiamate esterne | Semplice, efficace | Richiede disciplina | Pratica di sicurezza di base |
| Reentrancy Guard (mutex) | Usare modifier Solidity per impedire reentrancy | Blocca esplicitamente chiamate ricorsive | Aggiunge un leggero overhead di gas | Raccomandato per tutte le funzioni di prelievo |
| Pull Payment Pattern | Far ritirare i fondi volontariamente agli utenti | Minimizza i rischi di trasferimenti automatici | Richiede interazione utente | Ideale per contratti di escrow e staking |
| Analisi Statica e Dinamica | Audit automatici e manuali del codice | Individua vulnerabilità precocemente | Può non rilevare flussi incrociati complessi | Essenziale per sicurezza pre-distribuzione |
| Controlli Flash Loan & Delegatecall | Validare chiamante e contesto transazioni | Previene reentrancy amplificata dai prestiti | Complesso da implementare | Critico in protocolli DeFi con prestiti |
Esempio di codice Solidity: contratto vulnerabile alla Reentrancy e sua correzione
Ecco un contratto minimo vulnerabile e la sua versione rinforzata con un reentrancy guard:
// Vulnerabile: prelievo prima dell'aggiornamento del saldo
contract Vulnerable {
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient funds");
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
balances[msg.sender] -= amount; // Vulnerabile: stato aggiornato dopo la chiamata esterna
}
}
Corretto con ReentrancyGuard di OpenZeppelin e pattern effetti prima interazione:
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Secure is ReentrancyGuard {
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount, "Insufficient funds");
balances[msg.sender] -= amount; // Stato aggiornato prima della chiamata esterna
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
Consiglio professionale: Combinate sempre il pattern checks-effects-interactions con guardie di reentrancy. Audit approfonditi dei grafi di chiamata e test con strumenti di fuzzing possono svelare vulnerabilità di reentrancy multi-hop prima del deploy.
Le vulnerabilità di reentrancy restano una minaccia critica per gli smart contract, specialmente negli ecosistemi DeFi complessi che utilizzano flash loan e delegatecall. Le difese più efficaci combinano disciplina nel codice (checks-effects-interactions), guardie esplicite contro reentrancy, audit rigorosi e validazioni contestuali delle transazioni.
In Soken, la nostra ricerca sulla sicurezza integra tecniche manuali e automatiche affinate da oltre 282 audit di smart contract, identificando e correggendo costantemente reentrancy e rischi associati. Applicare best practice e metodologie di testing approfondite è imprescindibile per ogni progetto che gestisce fondi degli utenti.
Hai bisogno di consulenza esperta sulla sicurezza? Il team di auditor Soken ha revisionato oltre 255 smart contract assicurando più di 2 miliardi di dollari in valore di protocollo. Che tu necessiti di un audit completo, di una valutazione di sicurezza X-Ray gratuita o di supporto nella navigazione delle regolamentazioni crypto, siamo pronti ad aiutarti.
Parla con un esperto Soken | Visualizza i nostri report di audit