Come prevenire attacchi di Input Validation in Solidity

Autore dell’articolo

Gli attacchi di missing input validation si verificano quando un contratto Solidity accetta indirizzi, importi, identificativi, prezzi o valori di configurazione controllati dall’utente senza dimostrare che essi soddisfino le ipotesi economiche e di sicurezza del contratto. Per prevenirli, è fondamentale validare ogni input esterno al limite di fiducia con allowlist, controlli di limite, verifiche dell’indirizzo zero, autorizzazioni e invarianti di stato prima che avvengano movimenti di asset o cambiamenti contabilistici.

L’impatto è concreto: ad esempio, Qubit ha perso 80,0 milioni di dollari sulla BSC il 28-01-2022 in un incidente classificato come Missing Input Validation. Questi fallimenti non sono semplicemente problemi di transazioni malformate. Un valore non verificato può far eseguire a codice Solidity valido un’azione aziendale invalida, come accreditare collateral inesistente, trasferire asset a destinazioni non affidabili o creare debiti oltre il limite reale di un prestatore.

Cos’è una vulnerabilità di validazione input in Solidity e come funziona un attacco?

Una vulnerabilità di validazione input in Solidity si verifica quando un contratto considera un valore fornito dal chiamante come affidabile, anche se quel valore controlla permessi, contabilizzazione degli asset, prezzi o transizioni di stato. Un attaccante fornisce un valore sintatticamente valido per l’ABI, ma economicamente invalido per il protocollo, e utilizza lo stato risultante per prelevare asset, mintare claim o bypassare restrizioni.

Solidity applica i tipi, non l’intento del protocollo. Un uint256 può essere qualsiasi numero che non faccia revert, e un address può rappresentare un EOA, un contratto maligno, l’indirizzo zero o un token non supportato. Il contratto deve applicare le regole semantiche mancanti.

Una sequenza di attacco tipica è:

  1. L’attaccante individua una funzione pubblica che accetta un importo, token, destinatario, prezzo o identificativo non controllato.
  2. L’attaccante sceglie un valore che viola le ipotesi previste dal protocollo.
  3. Il contratto registra un saldo, diritto, limite di debito o stato di autorizzazione non sicuro.
  4. L’attaccante chiama una funzione successiva che si fida dello stato alterato.
  5. Il protocollo trasferisce asset o minta claims trasferibili basandosi su quello stato falso.
  6. Gli altri utenti subiscono la perdita quando emerge il déficit contabile.

La domanda critica è: * quale fatto rappresenta questa affermazione di input, e dove verifica indipendentemente il contratto quel fatto?* Se la risposta è “l’ha fornito il chiamante”, allora il contratto presenta una vulnerabilità di validazione input impropria.

Quali pattern di Solidity causano attacchi di validazione input?

Gli attacchi di validazione input sorgono spesso quando un contratto accetta flessibilità per integrazioni o esperienza utente, ma non limita tale flessibilità al dominio supportato dal protocollo. Il parametro pericolo non è sempre chiaramente chiamato price o amount; può essere un indirizzo token, un percorso, un destinatario, un ID tranches, una prova o un indirizzo di contratto esterno.

Pattern rischiosi comuni includono:

  • Collateral dichiarato dal chiamante: un prestatore fornisce il valore usato per calcolare la sua capacità di prestito.
  • Indirizzi token arbitrari: una funzione di deposito accetta qualsiasi ERC-20, incluse crypto con comportamenti di trasferimento non standard.
  • Indirizzi destinatari non verificati: una funzione accetta address(0), il contratto stesso o indirizzi che violano le regole di custodia.
  • Percentuali e rapporti non limitati: una fee, LTV, sconto, slippage o tasso di premio supera la gamma prevista in basis point.
  • Relazioni tra array non validate: array paralleli con lunghezze diverse, duplicati accettati, o un percorso che ripete asset in modo inatteso.
  • Transizioni di stato senza precondizioni: azioni di claim, cancellazione, redemption o migrazione vengono eseguite in uno stato del ciclo di vita errato.
  • Dati esterni accettati come fatti: si fida di prezzi, saldi, firme o valori restituiti forniti dal chiamante senza verifica.

La validazione deve essere contestuale. require(amount > 0) è utile, ma non dimostra che amount sia sostenibile, garantito da collateral, entro un limite o coerente con lo stato attuale. Analogamente, verificare che un indirizzo token non sia zero non garantisce che il token sia approvato, che si comporti come previsto o che appartenga al mercato corretto.

La metodologia di revisione di Soken considera ogni parametro esterno come una transizione di stato tentata: individua l’invariante che può influenzare, quindi verifica che la funzione enforcesce tale invarianti prima di modificare lo storage.

Come appare un esempio di attacco di validazione input in Solidity?

Questo esempio di attacco di validazione input permette a un prestatore di inviare il proprio collateralValue. Il contratto verifica che il prestito richiesto non superi quel valore, ma non stabilisce che il prestatore abbia depositato collateral di quel valore. Un chiamante può quindi impostare entrambi i valori a livelli arbitrari e ottenere i beni finanziati dal prestatore.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IERC20 {
    function transfer(address to, uint256 value) external returns (bool);
}

contract UnsafeLender {
    IERC20 public immutable asset;
    mapping(address => uint256) public debt;

    constructor(IERC20 asset_) {
        require(address(asset_) != address(0), "asset zero");
        asset = asset_;
    }

    function borrow(
        uint256 collateralValue,
        uint256 amount
    ) external {
        require(collateralValue > 0, "no collateral");
        require(amount <= collateralValue, "collateral insufficient");

        // VULNERABILE: il prestatore fornisce il valore di copertura del prestito.
        debt[msg.sender] += amount;

        require(asset.transfer(msg.sender, amount), "transfer fallita");
    }
}

Il design corretto deriva la capacità di prestito dal collateral effettivamente ricevuto dal contratto. Gli indirizzi asset immutabili funzionano come una allowlist ristretta, i controlli nel constructor rifiutano indirizzi zero, l’LTV è limitato, e il controllo sulla delta del saldo valida l’invariante di transfer piuttosto che fidarsi dell’importo depositato richiesto.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IERC20 {
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

contract ValidatedLender {
    IERC20 public immutable loanAsset;
    IERC20 public immutable collateralAsset;
    uint256 public immutable maxLtvBps;

    mapping(address => uint256) public collateral;
    mapping(address => uint256) public debt;

    constructor(IERC20 loanAsset_, IERC20 collateralAsset_, uint256 maxLtvBps_) {
        require(address(loanAsset_) != address(0), "asset zero");
        require(address(collateralAsset_) != address(0), "collateral zero");
        require(maxLtvBps_ > 0 && maxLtvBps_ <= 10_000, "LTV non valida");
        loanAsset = loanAsset_;
        collateralAsset = collateralAsset_;
        maxLtvBps = maxLtvBps_;
    }

    function depositCollateral(uint256 amount) external {
        require(amount > 0, "importo zero");
        uint256 beforeBalance = collateralAsset.balanceOf(address(this));
        require(collateralAsset.transferFrom(msg.sender, address(this), amount), "trasferimento fallito");
        uint256 received = collateralAsset.balanceOf(address(this)) - beforeBalance;

        // CORREZIONE: si considera solo il collateral effettivamente ricevuto.
        require(received == amount, "comportamento token non supportato");
        collateral[msg.sender] += received;
    }

    function borrow(uint256 amount) external {
        require(amount > 0, "importo zero");
        uint256 limit = (collateral[msg.sender] * maxLtvBps) / 10_000;
        require(debt[msg.sender] + amount <= limit, "limite di prestito superato");

        debt[msg.sender] += amount;
        require(loanAsset.transfer(msg.sender, amount), "trasferimento fallito");
    }
}

Se il vostro protocollo accetta asset, percorsi o input di contabilizzazione flessibili, un audit di smart contract avversari e penetration test può tracciare se ogni valore dichiarato è stabilito indipendentemente prima di influenzare i fondi.

Come prevenire attacchi di validazione input in Solidity?

Prevenire attacchi di validazione input richiede di convalidare gli input all’ingresso, quindi di preservare le ipotesi risultanti con invarianti espliciti durante tutto il ciclo di vita del contratto. La miglior difesa non è un singolo modifier, ma un design in cui ogni cambiamento di stato significativo ha un insieme chiaramente definito di input consentiti e una condizione testabile che rimane vera successivamente.

Tecnica Cosa previene Costo di gas o complessità Limitazione
Allowlist di token e implementazioni Token ERC-20 non supportati, adapter maligni, chiamate esterne arbitrarie Ricerca in storage o confronto con indirizzo immutabile I token approvati possono comunque introdurre rischi economici
Controlli di limiti Importi zero, tariffe eccessive, basis point non validi, scadenze insicure Basso, di solito poche comparazioni I limiti da soli non garantiscono correttezza economica
Verifica indirizzo zero e destinatario Burn di asset, proprietà inusabile, destinazioni di custodia non valide Basso, confronto di un indirizzo Un indirizzo diverso da zero può comunque essere non affidabile
Verifiche di autorizzazione e ciclo di vita Chiamate in stato errato o da attori non autorizzati Letture di storage e design dei ruoli I ruoli richiedono amministrazione sicura
Validazione delta di saldo Crediti di deposito falsi e comportamento di transfer non supportato Letture aggiuntive di saldo token Necessario gestione accurata di asset rebasati
Validazione invarianti Problemi di solvibilità, relazione tra supply, debito e riserva rotte Letture aggiuntive e aritmetica Le invarianti devono coprire l’intero modello economico

Per protocolli configurabili, si consiglia di validare la configurazione sia in fase di creazione che durante il consumo. Un parametro impostato dalla governance può essere sicuro durante la creazione, ma rischioso se cambia una dipendenza. Per sistemi aggiornabili, si devono validare i parametri di initializzatore con la stessa rigore dedicato agli input pubblici.

Insight sulla sicurezza: La metodologia di Soken parte dall’invariante, non dall’istruzione require. Se i revisori non possono enunciare cosa deve rimanere vero dopo che un input controllato dall’utente viene processato, non possono determinare se la validazione è sufficiente.

Quali incidenti reali sono stati classificati come Missing Input Validation?

Il Missing Input Validation è stato associato a perdite materiali di smart contract in contesti di lending, swapping e asset management. La categoria di incidenti coinvolge diverse chain e design di protocollo, dimostrando che una validazione input inadeguata è più un fallimento logico del protocollo che un errore specifico di Solidity sulla rete.

Protocollo Data Importo perso Chain
Qubit 28-01-2022 $80.0M BSC
Hedgey 19-04-2024 $44.7M Arbitrum, Ethereum
Transit Swap 02-10-2022 $28.9M Ethereum, BSC
Pickle 22-11-2020 $19.7M Ethereum

Questi incidenti condividono una lezione centrale: i contratti non possono dedurre in modo sicuro la verità del protocollo da un valore solo perché una transazione ha codificato e passato con successo quel valore. I sistemi di asset necessitano di validazione semantica intorno alla proprietà specifica che protegge il valore, siano essi collateral ricevuto, asset supportato, percorso autorizzato o transizione di ciclo di vita valida.

DefiLlama classifica 52 incidenti di smart contract dal 2020 al 2026 con perdite complessive di 330,5 milioni di dollari. Questa ampiezza motiva Soken a trattare la revisione di validazione come un esercizio di sicurezza economica, non come una lista superficiale di require.

Come rilevare vulnerabilità di validazione input non corrette?

Puoi individuare vulnerabilità di validazione input non corrette tracciando ogni parametro accessibile esternamente alle scritture di storage, alle chiamate esterne e ai trasferimenti di asset che influenzano, e testando se valori ABI-valid ma non validi economicamente possano violare un’invariante. L’analisi statica aiuta a trovare alcuni controlli mancanti, ma test scenario-driven sono essenziali per assunzioni specifiche del protocollo.

Utilizza questa checklist di revisione:

  1. Elenca ogni parametro external e public che influenza valore, autorità o instradamento.
  2. Verifica che ogni indirizzo sia diverso da zero, approvato, e appropriato per il ruolo.
  3. Controlla ogni importo rispetto a limite di protocollo, saldo, limite di debito, limiti di fornitura e ipotesi sui decimali.
  4. Accerta che prezzi, valori di collateral e saldi vengano da fonti affidabili o verifiche indipendenti.
  5. Validare lunghezze, unicità, ordinamento e relazioni tra array.
  6. Assicurati che ogni funzione permetta solo stati di ciclo di vita validi e chiamanti autorizzati.
  7. Testa la contabilità delta di saldo per fee-on-transfer, rebase e comportamenti ERC-20 non convenzionali.
  8. Definisci e valida la condizione post-azione di ogni operazione di movimento asset.

Slither può identificare pattern come chiamate esterne pericolose, valori di ritorno non verificati e controlli di accesso fragili che spesso delineano fallimenti di validazione. Non può dedurre ogni invarianti del protocollo, quindi usa Foundry fuzzing per generare valori zero, massimi, array duplicati, indirizzi inattesi e token non supportati.

I test di invarianti sono particolarmente efficaci. Per esempio, verifica che il debito totale pendente non superi mai la capacità di prestito configurata, che il collateral accreditato non superi gli asset ricevuti, e che solo asset approvati possano modificare la contabilità di un mercato. Esplora il Soken Hub quando sviluppi un processo di revisione di sicurezza più ampio attorno a queste proprietà.

La validazione input rappresenta il confine tra intenzione utente e verità del protocollo. Un contratto non dovrebbe mai accreditare, autorizzare, valutare o trasferire esclusivamente sulla base di quanto afferma un chiamante. Deriva valori sensibili da stato verificato, limita gli ambiti di accettazione e applica l’invariante che rende lo stato risultante economicamente sicuro.

Il passo successivo pratico è creare una mappa parametri-invariante per ogni funzione di movimento di asset nel tuo codice. Qualsiasi parametro senza una fonte di fiducia documentata, intervallo consentito e post-condizione meritano una revisione immediata.

Autore dell’articolo

Domande frequenti

Cos'è una vulnerabilità di input validation in Solidity?

Una vulnerabilità di input validation in Solidity si verifica quando un contract accetta indirizzi, importi, ID, prezzi o valori di configurazione controllati dall'utente senza verificarne conformità a regole di sicurezza ed economia. Può causare azioni business non valide.

Come posso prevenire attacchi di input validation in Solidity?

Per prevenire attacchi di input validation in Solidity, verifica tutti gli input esterni prima che i beni si muovano, combinando allowlist, controlli di limiti, zero-address, autorizzazioni e invarianti di stato per rispettare le regole di sicurezza ed economia.

Cosa è successo nell'attacco di input validation di Qubit?

Qubit ha perso 80 milioni di dollari su BSC il 28-01-2022 in un incidente di Missing Input Validation. Questo mostra che input non verificati possono portare a riconoscere collateral inesistenti o compiere azioni economiche invalidi.

Perché i contratti Solidity devono controllare il zero address?

I contratti Solidity devono rifiutare un zero address quando l'indirizzo fornito dall'utente deve rappresentare una destinazione, asset o autorizzazione reale, per evitare valori placeholder che violano trasferimenti, autorizzazioni o assunti di contabilità.

I controlli di limiti sono sufficienti a prevenire vulnerabilità di Input Validation?

I controlli di limiti da soli non sono sufficienti in Solidity, poiché un valore può rimanere entro un intervallo numerico ma comunque violare autorizzazioni, allowlist o requisiti di stato. È necessario un layering di validazioni prima di muovere asset o aggiornare contabilità.

Chat