Přejít na hlavní obsah

Jak používat Slither k hledání chyb v chytrých kontraktech

Solidity
chytré kontrakty
bezpečnost
testování
Pokročilý
Trailofbits
9. června 2020
7 minut čtení
Upravit stránku (opens in a new tab)

Jak používat Slither

Cílem tohoto tutoriálu je ukázat, jak používat Slither k automatickému hledání chyb v chytrých kontraktech.

Instalace

Slither vyžaduje Python >= 3.6. Lze jej nainstalovat přes pip nebo pomocí Dockeru.

Slither přes pip:

pip3 install --user slither-analyzer

Slither přes Docker:

docker pull trailofbits/eth-security-toolbox
docker run -it -v "$PWD":/home/trufflecon trailofbits/eth-security-toolbox

Poslední příkaz spustí eth-security-toolbox v Dockeru, který má přístup k vašemu aktuálnímu adresáři. Můžete měnit soubory ze svého hostitelského systému a spouštět nástroje na souborech z Dockeru.

Uvnitř Dockeru spusťte:

solc-select 0.5.11
cd /home/trufflecon/

Spuštění skriptu

Pro spuštění Python skriptu pomocí Pythonu 3:

python3 script.py

Příkazový řádek

Příkazový řádek versus uživatelsky definované skripty. Slither přichází se sadou předdefinovaných detektorů, které nacházejí mnoho běžných chyb. Volání Slitheru z příkazového řádku spustí všechny detektory, není potřeba žádná detailní znalost statické analýzy:

slither project_paths

Kromě detektorů má Slither schopnosti revize kódu prostřednictvím svých nástrojů pro výpis (printers) (opens in a new tab) a nástrojů (opens in a new tab).

Použijte crytic.io (opens in a new tab) pro získání přístupu k soukromým detektorům a integraci s GitHubem.

Statická analýza

Schopnosti a návrh frameworku pro statickou analýzu Slither byly popsány v příspěvcích na blogu (1 (opens in a new tab), 2 (opens in a new tab)) a v akademickém článku (opens in a new tab).

Statická analýza existuje v různých podobách. Pravděpodobně si uvědomujete, že kompilátory jako clang (opens in a new tab) a gcc (opens in a new tab) závisí na těchto výzkumných technikách, ale tvoří také základ nástrojů jako Infer (opens in a new tab), CodeClimate (opens in a new tab), FindBugs (opens in a new tab) a nástrojů založených na formálních metodách, jako jsou Frama-C (opens in a new tab) a Polyspace (opens in a new tab).

Nebudeme zde vyčerpávajícím způsobem zkoumat techniky statické analýzy a výzkum. Místo toho se zaměříme na to, co je potřeba k pochopení toho, jak Slither funguje, abyste jej mohli efektivněji používat k hledání chyb a porozumění kódu.

Reprezentace kódu

Na rozdíl od dynamické analýzy, která uvažuje o jediné cestě provádění, statická analýza uvažuje o všech cestách najednou. K tomu se spoléhá na jinou reprezentaci kódu. Dvě nejběžnější jsou abstraktní syntaktický strom (AST) a graf toku řízení (CFG).

Abstraktní syntaktické stromy (AST)

AST se používají pokaždé, když kompilátor parsuje kód. Je to pravděpodobně nejzákladnější struktura, na které lze provádět statickou analýzu.

Stručně řečeno, AST je strukturovaný strom, kde obvykle každý list obsahuje proměnnou nebo konstantu a vnitřní uzly jsou operandy nebo operace toku řízení. Zvažte následující kód:

function safeAdd(uint a, uint b) pure internal returns(uint){
    if(a + b <= a){
        revert();
    }
    return a + b;
}

Odpovídající AST je zobrazen na:

AST

Slither používá AST exportovaný pomocí solc.

Ačkoli je snadné jej sestavit, AST je vnořená struktura. Někdy to není to nejpřímočařejší pro analýzu. Například k identifikaci operací použitých výrazem a + b <= a musíte nejprve analyzovat <= a poté +. Běžným přístupem je použití takzvaného návrhového vzoru návštěvník (visitor pattern), který prochází stromem rekurzivně. Slither obsahuje obecného návštěvníka v ExpressionVisitor (opens in a new tab).

Následující kód používá ExpressionVisitor k detekci, zda výraz obsahuje sčítání:

Graf toku řízení (CFG)

Druhou nejběžnější reprezentací kódu je graf toku řízení (CFG). Jak název napovídá, jedná se o reprezentaci založenou na grafech, která odhaluje všechny cesty provádění. Každý uzel obsahuje jednu nebo více instrukcí. Hrany v grafu představují operace toku řízení (if/then/else, smyčka atd.). CFG našeho předchozího příkladu je:

CFG

CFG je reprezentace, na které je postavena většina analýz.

Existuje mnoho dalších reprezentací kódu. Každá reprezentace má výhody a nevýhody podle analýzy, kterou chcete provést.

Analýza

Nejjednodušším typem analýz, které můžete se Slitherem provádět, jsou syntaktické analýzy.

Syntaktická analýza

Slither dokáže procházet různými komponentami kódu a jejich reprezentací, aby našel nesrovnalosti a nedostatky pomocí přístupu podobného porovnávání vzorů (pattern matching).

Například následující detektory hledají problémy související se syntaxí:

Sémantická analýza

Na rozdíl od syntaktické analýzy půjde sémantická analýza hlouběji a bude analyzovat „význam“ kódu. Tato rodina zahrnuje některé široké typy analýz. Vedou k silnějším a užitečnějším výsledkům, ale je také složitější je napsat.

Sémantické analýzy se používají pro nejpokročilejší detekce zranitelností.

Analýza datových závislostí

O proměnné variable_a se říká, že je datově závislá na variable_b, pokud existuje cesta, pro kterou je hodnota variable_a ovlivněna variable_b.

V následujícím kódu je variable_a závislá na variable_b:

// ...
variable_a = variable_b + 1;

Slither přichází s vestavěnými schopnostmi pro datové závislosti (opens in a new tab) díky své průběžné reprezentaci (diskutováno v pozdější části).

Příklad použití datové závislosti lze nalézt v detektoru nebezpečné striktní rovnosti (opens in a new tab). Zde bude Slither hledat porovnání striktní rovnosti s nebezpečnou hodnotou (incorrect_strict_equality.py#L86-L87 (opens in a new tab)) a bude informovat uživatele, že by měl použít >= nebo <= spíše než ==, aby zabránil útočníkovi uvěznit kontrakt. Mimo jiné bude detektor považovat za nebezpečnou návratovou hodnotu volání balanceOf(address) (incorrect_strict_equality.py#L63-L64 (opens in a new tab)) a použije engine datových závislostí ke sledování jejího použití.

Výpočet pevného bodu (Fixed-point computation)

Pokud vaše analýza prochází CFG a sleduje hrany, pravděpodobně uvidíte již navštívené uzly. Například pokud je smyčka prezentována tak, jak je znázorněno níže:

for(uint i; i < range; ++){
    variable_a += 1
}

Vaše analýza bude muset vědět, kdy se zastavit. Zde existují dvě hlavní strategie: (1) iterovat na každém uzlu konečný početkrát, (2) vypočítat takzvaný pevný bod (fixpoint). Pevný bod v podstatě znamená, že analýza tohoto uzlu neposkytuje žádné smysluplné informace.

Příklad použití pevného bodu lze nalézt v detektorech reentrance: Slither prozkoumává uzly a hledá externí volání, zápis a čtení do úložiště. Jakmile dosáhne pevného bodu (reentrancy.py#L125-L131 (opens in a new tab)), zastaví průzkum a analyzuje výsledky, aby zjistil, zda je přítomna reentrance, prostřednictvím různých vzorů reentrance (reentrancy_benign.py (opens in a new tab), reentrancy_read_before_write.py (opens in a new tab), reentrancy_eth.py (opens in a new tab)).

Psaní analýz pomocí efektivního výpočtu pevného bodu vyžaduje dobré pochopení toho, jak analýza šíří své informace.

Průběžná reprezentace

Průběžná reprezentace (IR) je jazyk, který má být pro statickou analýzu vhodnější než ten původní. Slither překládá Solidity do svého vlastního IR: SlithIR (opens in a new tab).

Porozumění SlithIR není nutné, pokud chcete psát pouze základní kontroly. Bude se vám však hodit, pokud plánujete psát pokročilé sémantické analýzy. Nástroje pro výpis (printers) SlithIR (opens in a new tab) a SSA (opens in a new tab) vám pomohou pochopit, jak je kód překládán.

Základy API

Slither má API, které vám umožní prozkoumat základní atributy kontraktu a jeho funkcí.

Pro načtení kódové základny:

from slither import Slither
slither = Slither('/path/to/project')

Zkoumání kontraktů a funkcí

Objekt Slither má:

  • contracts (list(Contract): seznam kontraktů
  • contracts_derived (list(Contract): seznam kontraktů, které nejsou zděděny jiným kontraktem (podmnožina kontraktů)
  • get_contract_from_name (str): Vrátí kontrakt podle jeho názvu

Objekt Contract má:

  • name (str): Název kontraktu
  • functions (list(Function)): Seznam funkcí
  • modifiers (list(Modifier)): Seznam funkcí
  • all_functions_called (list(Function/Modifier)): Seznam všech interních funkcí dosažitelných kontraktem
  • inheritance (list(Contract)): Seznam zděděných kontraktů
  • get_function_from_signature (str): Vrátí funkci (Function) podle jejího podpisu
  • get_modifier_from_signature (str): Vrátí modifikátor (Modifier) podle jeho podpisu
  • get_state_variable_from_name (str): Vrátí stavovou proměnnou (StateVariable) podle jejího názvu

Objekt Function nebo Modifier má:

  • name (str): Název funkce
  • contract (contract): kontrakt, kde je funkce deklarována
  • nodes (list(Node)): Seznam uzlů tvořících CFG funkce/modifikátoru
  • entry_point (Node): Vstupní bod CFG
  • variables_read (list(Variable)): Seznam čtených proměnných
  • variables_written (list(Variable)): Seznam zapisovaných proměnných
  • state_variables_read (list(StateVariable)): Seznam čtených stavových proměnných (podmnožina variables`read)
  • state_variables_written (list(StateVariable)): Seznam zapisovaných stavových proměnných (podmnožina variables`written)

Poslední aktualizace stránky: 15. dubna 2026