Gli attacchi di broken access control si verificano quando un contratto Solidity permette a un chiamante non affidabile di accedere a capacità che dovrebbero essere riservate, come spostare asset, modificare un’implementazione, assegnare ruoli o configurare un indirizzo di fiducia. L’attaccante non ha bisogno di violare la crittografia: sfrutta un controllo di autorizzazione mancante, errato o troppo ampio per ottenere una escalation di privilegi efficace.
L’impatto può interessare l’intero protocollo perché le funzioni privilegiate spesso controllano fondi collettivi o configurazioni critiche per la governance. Poly Network ha perso 611 milioni di dollari il 10-08-2021 tra Ethereum, BSC e Polygon in un incidente classificato da DefiLlama come Improper Access Control. L’autorizzazione non è quindi una questione UI o un dettaglio amministativo: fa parte del confine di sicurezza centrale del contratto.
Cos’è una vulnerabilità di broken access control in Solidity?
Una vulnerabilità di broken access control si verifica quando un contratto non verifica in modo affidabile che msg.sender sia autorizzato a eseguire un’azione privilegiata, o quando il modello di autorizzazione concede più privilegi di quelli necessari per quella azione. In Solidity, il fallimento si manifesta solitamente in funzioni chiamabili esternamente che modificano dati sensibili, trasferiscono asset, aggiornano il codice oppure cambiano controparti di fiducia.
Un contratto può essere vulnerabile anche quando ha una variabile owner. La domanda critica è se ogni percorso di esecuzione privilegiato verifica correttamente questa autorità, comprese le funzioni helper, gli initializer, le funzioni di gestione ruoli e i hook di upgrade.
Un attacco tipico di broken access control segue questa sequenza:
- L’attaccante identifica una funzione chiamabile esternamente con conseguenze privilegiate.
- La funzione non ha controlli sul chiamante, verifica l’indirizzo sbagliato o si affida a uno stato controllato dall’attaccante.
- L’attaccante chiama direttamente la funzione, bypassando eventuali restrizioni di frontend.
- La chiamata concede un ruolo, cambia un indirizzo di fiducia, o invoca un’operazione di spostamento asset.
- L’attaccante utilizza la capacità appena acquisita per prelevare fondi o modificare il comportamento del protocollo.
- Il contratto accetta la transazione perché l’EVM applica solo controlli on-chain.
I bottoni del frontend, la documentazione e le procedure operative previste non limitano le chiamate dirette al contratto. Qualunque account può inviare calldata a una funzione pubblica o esterna. Pertanto, il codice Solidity deve imporre questa politica nel punto esatto in cui avviene una transizione di stato sensibile.
Quali pattern di Solidity causano attacchi di access control e escalation di privilegi?
Le vulnerabilità di improper access control emergono comunemente quando gli sviluppatori espongono impostatori di configurazioni, helper amministrativi o percorsi di inizializzazione senza considerare che ogni funzione pubblica può rappresentare un’API rivolta all’attaccante. Il pattern pericoloso non si limita a una mancanza totale di onlyOwner: include autorizzazioni che sono mutabili da chiamanti arbitrari, ereditate in modo errato, applicate in modo incoerente o troppo ampie rispetto all’operazione.
I pattern di fallimento più frequenti includono:
- Setter pubblici che permettono a chiunque di sostituire un treasury, un oracle, un’implementazione, un operatore o un beneficiario.
- Funzioni di gestione ruoli prive di controlli da parte di un amministratore.
- Initializer chiamabili post-deploy o chiamati da account non previsti.
- Funzioni che verificano
tx.origininvece dimsg.sender. - Funzioni interne privilegiate raggiunte tramite un wrapper esterno non protetto.
- Un singolo ruolo di amministratore che può eseguire tutte le operazioni operative, aumentando il raggio di compromissione.
- Autorizzazioni basate su un indirizzo fornito dall’utente invece di una mappatura di ruoli immutabili o controllati.
- Funzioni di upgrade che validano il chiamante ma non la nuova implementazione o il percorso di upgrade.
Questo pattern difettoso è particolarmente sottile:
function setOperator(address newOperator) external {
operator = newOperator;
}
La funzione di pagamento può richiedere correttamente msg.sender == operator, ma il limite di accesso è già fallito perché chiunque può nominarsi operatore. I revisori dovrebbero tracciare all’indietro da ogni funzione sensibile per identificare chi può ottenere il ruolo richiesto.
La metodologia di Soken considera l’assegnazione dei ruoli e la configurazione degli indirizzi di fiducia operazioni privilegiate a tutti gli effetti. Un design di controllo accessi sicuro protegge sia il trasferimento finale di asset sia ogni percorso che può sbloccarlo.
Come appare un attacco di broken access control nel codice Solidity?
Questo vault vulnerabile verifica il chiamante prima di un payout, ma la funzione setPayoutOperator è pubblica. Un attaccante può assegnarsi come operatore e successivamente invocare pay, rendendo inutile il check di autorizzazione successivo.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract VulnerablePayoutVault {
address public payoutOperator;
constructor(address initialOperator) {
payoutOperator = initialOperator;
}
receive() external payable {}
function setPayoutOperator(address newOperator) external {
// VULNERABILE: qualsiasi chiamante può concedersi autonomia di payout.
payoutOperator = newOperator;
}
function pay(address payable recipient, uint256 amount) external {
require(msg.sender == payoutOperator, "not operator");
require(address(this).balance >= amount, "insufficient balance");
(bool sent, ) = recipient.call{value: amount}("");
require(sent, "payment failed");
}
}
La soluzione separa amministrazione e operazioni. Solo il proprietario può concedere o revocare il ruolo di payout ristretto, mentre un operatore payout non può amministrare ruoli o cambiare proprietà.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract FixedPayoutVault {
address public owner;
mapping(address => bool) public payoutOperators;
modifier onlyOwner() {
require(msg.sender == owner, "not owner");
_;
}
modifier onlyPayoutOperator() {
require(payoutOperators[msg.sender], "not payout operator");
_;
}
constructor(address initialOwner, address initialOperator) {
require(initialOwner != address(0), "zero owner");
require(initialOperator != address(0), "zero operator");
owner = initialOwner;
payoutOperators[initialOperator] = true;
}
receive() external payable {}
function setPayoutOperator(address operator, bool allowed) external onlyOwner {
require(operator != address(0), "zero operator");
// CORREZIONE: solo il proprietario può modificare l'appartenenza ai ruoli privilegiati.
payoutOperators[operator] = allowed;
}
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "zero owner");
owner = newOwner;
}
function pay(address payable recipient, uint256 amount)
external
onlyPayoutOperator
{
require(address(this).balance >= amount, "insufficient balance");
(bool sent, ) = recipient.call{value: amount}("");
require(sent, "payment failed");
}
}
Se nel sistema sono previsti cambi di ruolo, permessi di upgrade o prelievi dal treasury, una audit di smart-contract focalizzata sul controllo accessi e una valutazione di sicurezza possono verificare che tutti i percorsi verso queste capacità siano correttamente vincolati.
Come possono i sviluppatori Solidity prevenire vulnerabilità di improper access control?
Prevenire attacchi di broken access control richiede un modello di permessi esplicito, non una serie di require sparse aggiunte a posteriori. Definire quale attore può eseguire ogni operazione sensibile, perché tale autorizzazione esiste e quale ruolo più piccolo può svolgere in sicurezza le attività di routine.
| Tecnica | Cosa blocca | Costo di gas o complessità | Limite |
|---|---|---|---|
Modificatore onlyOwner |
Gestione amministrativa non autorizzata di funzioni owner-only | Una lettura di storage per chiamata protetta | Un owner compromesso mantiene tutti i privilegi |
Mappature di ruoli o AccessControl di OpenZeppelin |
Uso non autorizzato di ruoli operativi separati | Letture di storage e logica di gestione ruoli | Un design di ruolo poco preciso può creare privilegi eccessivi |
| Ruoli a minimo privilegio | Un keeper o operatore con poteri amministrativi non correlati | Più funzioni e definizioni di ruolo da mantenere | Richiede limiti chiari di operatività |
| Timelock per cambi critici | Aggiornamenti o modifiche di parametri immediati e malevoli | Esecuzione differita e contratti aggiuntivi | Non impedisce azioni malevole se gli utenti non rispondono al delay |
| Amministrazione multisig | Una singola chiave che modifica unilateralmente lo stato privilegiato | Coordinamento dei firmatari e sovraccarico operativo | Signer condivisi o politiche deboli rimangono rischi |
| Guardie di inizializzazione single-time | Inizializzazione non autorizzata o ripetuta | Flag di storage e logica di initializer | Le procedure di deploy devono ancora impostare l’autorità desiderata |
Utilizzare primitive di ownership e ruoli ben revisionate dove opportuno, ma non considerare una libreria come un modello di sicurezza completo. Ad esempio, Ownable di OpenZeppelin definisce meccaniche di proprietà, ma il team di protocollo deve ancora decidere se la proprietà debba controllare pause, upgrade, raccolta fee, sostituzione oracle o recupero di emergenza.
Evita tx.origin per l’autorizzazione: può essere manipolato tramite contratti intermediari e non identifica immediatamente il chiamante diretto. Usa msg.sender e verifica rispetto a un owner, ruolo, multisig o regola di autorizzazione specifica del contratto.
Quali incidenti reali dimostrano l’impatto di attacchi di access control?
DefiLlama registra 130 incidenti di smart-contract classificati come Improper Access Control tra il 2020 e il 2026, escludendo breach di exchange e wallet, con perdite complessive di 909,7 milioni di dollari. Di seguito alcune di queste verifiche confermate, che mostrano come i fallimenti di access control coinvolgano bridge, protocolli DeFi, bot e applicazioni su più catene.
| Protocollo | Data | Importo | Chain |
|---|---|---|---|
| Poly Network | 2021-08-10 | $611,0M | Ethereum, BSC, Polygon |
| Mirror | 2021-10-08 | $90,0M | Terra |
| Wintermute | 2022-06-05 | $17,6M | Optimism |
| ValueDefi | 2021-05-05 | $10,0M | BSC |
| Cork V1 | 2025-05-28 | $12,0M | Ethereum |
| SUPERFORTUNE AI | 2026-05-27 | $15,2M | Ethereum |
| The Sandbox | 2026-08-22 | $675K | Base, BSC, Ethereum |
| Radix | 2026-08-31 | $1,2M | Radix |
Questi incidenti portano a una lezione fondamentale di sicurezza: il valore economico di un protocollo può essere esposto quando il confine di autorità di un contratto non corrisponde al modello di fiducia previsto. La funzione interessata può variare, ma il fallimento è sempre lo stesso: un attore ottiene o esercita un’autorità che il design avrebbe dovuto trattenere.
Come individuare le vulnerabilità di broken access control prima del deploy?
Rilevare i problemi di access control richiede mappare le modifiche di stato sensibili rispetto alle loro condizioni di autorizzazione e dimostrare che nessun percorso alternativo può bypassarle. Strumenti automatizzati aiutano a identificare pattern sospetti, ma la revisione umana è essenziale per comprendere le assunzioni di business, le fiducie e le catene di escalation privilegi.
Segui questa checklist:
- Elenca tutte le funzioni che trasferiscono asset, modificano ruoli, aggiornano codice, mettono in pausa o cambiano indirizzi di fiducia.
- Identifica il chiamante previsto e la regola di autorizzazione per ogni funzione.
- Traccia ogni funzione che può concedere, revocare, trasferire o inizializzare quella autorità.
- Controlla tutte le funzioni wrapper esterne che raggiungono funzioni interne privilegiate.
- Verifica che l’inizializzazione possa avvenire una sola volta e solo nel processo di deploy previsto.
- Controlla che i ruoli di emergenza non possano eseguire azioni di routine o di governance non correlate.
- Testa il revoking di ruoli, il trasferimento di proprietà e la gestione dello zero address.
- Esamina proxy, contratti ereditati e percorsi di delegatecall come parte dello stesso boundary di autorizzazione.
Slither può segnalare pattern di autorizzazione mancante, uso pericoloso di tx.origin e modificatori incoerenti, ma i risultati richiedono validazione manuale. Foundry fuzzing aiuta a generare chiamate da indirizzi arbitrari e verificare che chiamanti non autorizzati non possano modificare stati privilegiati.
I test invarianti sono particolarmente efficaci. Per esempio, un’invariante di vault può affermare che un indirizzo senza il ruolo payout non deve mai far diminuire il saldo del vault tramite pay. Un’altra invariante può stabilire che solo il proprietario può modificare i membri di ruolo. Questi test verificano direttamente la regola di sicurezza, piuttosto che i semplici flussi d’utente attesi.
Approccio di sicurezza, metodologia Soken: Revisiona l’autorizzazione come un grafo di capacità, non come una lista di modificatori: ogni risultato privilegiato deve avere un percorso deliberate e verificabile verso l’autorità, e ogni percorso non previsto rappresenta una potenziale via di escalation.
Gli attacchi di broken access control si evitano rendendo l’autorità esplicita, riducendo al minimo i poteri di ciascun ruolo e proteggendo le funzioni che assegnano tali poteri con attenzione quanto le funzioni che movono fondi. Una funzione di prelievo correttamente limitata non è sicura se chiunque possa nominare il prelevatore.
Inizia creando una mappa dell’autorità dei tuoi contratti deployati e in progetto, comprese proxy e account operativi, e trasforma ogni regola in un modificatore e in un test invariante. Per approfondimenti sulla sicurezza Solidity e vulnerabilità correlate, esplora il Soken Hub.