Přeskočit na hlavní obsah

Jak používat Slither k hledání chyb ve smart kontraktech

solidity
smart kontrakt účty
bezpečnost
testování
Další
Trailofbits
9. června 2020
6 minuta čtení

Jak používat Slither

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

Instalace

Slither vyžaduje Python >= 3.6. Lze jej nainstalovat pomocí pip nebo s použitím 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 z vašeho hostitele 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í pythonového skriptu v pythonu 3:

python3 script.py

Příkazový řádek

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

slither project_paths

Kromě detektorů má Slither také možnosti revize kódu prostřednictvím svých výpisůopens in a new tab a nástrojůopens in a new tab.

Použijte crytic.ioopens in a new tab pro získání přístupu k soukromým detektorům a integraci s GitHub.

Statická analýza

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

Statická analýza existuje v různých variantách. S největší pravděpodobností si uvědomujete, že kompilátory jako clangopens in a new tab a gccopens in a new tab závisí na těchto výzkumných technikách, ale jsou také základem pro (Inferopens in a new tab, CodeClimateopens in a new tab, FindBugsopens in a new tab a nástroje založené na formálních metodách, jako jsou Frama-Copens in a new tab a Polyspaceopens in a new tab.

Nebudeme zde vyčerpávajícím způsobem procházet techniky statické analýzy a výzkum. Místo toho se zaměříme na to, co je potřeba k pochopení fungování Slitheru, 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 jedné cestě spuště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 řízení toku (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.

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

1function safeAdd(uint a, uint b) pure internal returns(uint){
2 if(a + b <= a){
3 revert();
4 }
5 return a + b;
6}

Odpovídající AST je zobrazen v:

AST

Slither používá AST exportovaný kompilátorem solc.

I když je AST jednoduché sestavit, jedná se o vnořenou strukturu. Někdy to není nejjednodušší analyzovat. Například pro identifikaci operací použitých ve výrazu a + b <= a musíte nejprve analyzovat <= a pak +. Běžným přístupem je použití takzvaného vzoru návštěvník, který rekurzivně prochází stromem. Slither obsahuje obecného návštěvníka v ExpressionVisitoropens in a new tab.

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

1from slither.visitors.expression.expression import ExpressionVisitor
2from slither.core.expressions.binary_operation import BinaryOperationType
3
4class HasAddition(ExpressionVisitor):
5
6 def result(self):
7 return self._result
8
9 def _post_binary_operation(self, expression):
10 if expression.type == BinaryOperationType.ADDITION:
11 self._result = True
12
13visitor = HasAddition(expression) # expression je výraz, který se má testovat
14print(f'Výraz {expression} obsahuje sčítání: {visitor.result()}')
Zobrazit vše

Graf řízení toku (CFG)

Druhou nejběžnější reprezentací kódu je graf řízení toku (CFG). Jak název napovídá, jedná se o grafovou reprezentaci, která odhaluje všechny cesty spuštění. Každý uzel obsahuje jednu nebo více instrukcí. Hrany v grafu představují operace řízení toku (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 v závislosti na analýze, 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 může procházet různými komponenty kódu a jejich reprezentací, aby našel nekonzistence a nedostatky pomocí přístupu podobného porovnávání vzorů.

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

Sémantická analýza

Na rozdíl od syntaktické analýzy jde sémantická analýza hlouběji a analyzuje „význam“ kódu. Tato rodina zahrnuje několik širokých typů analýz. Vedou k výkonnějším a užitečnějším výsledkům, ale jsou také složitější na psaní.

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

Analýza závislosti dat

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

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

1// ...
2variable_a = variable_b + 1;

Slither je dodáván s vestavěnými schopnostmi datové závislostiopens in a new tab, díky své mezilehlé reprezentaci (diskutované v pozdější sekci).

Příklad použití datové závislosti lze nalézt v detektoru nebezpečné striktní rovnostiopens in a new tab. Zde Slither bude hledat porovnání striktní rovnosti s nebezpečnou hodnotou (incorrect_strict_equality.py#L86-L87opens in a new tab), a informuje uživatele, že by měl použít >= nebo <= místo ==, 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-L64opens in a new tab) a použije engine datové závislosti ke sledování jejího použití.

Výpočet pevného bodu

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

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

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

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

Psaní analýz využívajících efektivní výpočet pevného bodu vyžaduje dobré porozumění tomu, jak analýza šíří své informace.

Mezilehlá reprezentace

Mezilehlá 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é vlastní IR: SlithIRopens in a new tab.

Porozumění SlithIR není nutné, pokud chcete psát pouze základní kontroly. Bude se však hodit, pokud plánujete psát pokročilé sémantické analýzy. Výpisyopens in a new tab SlithIR a SSAopens in a new tab vám pomohou pochopit, jak je kód přeložen.

Základy API

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

Pro načtení kódové báze:

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

Prozkoumává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 jména

Objekt Contract má:

  • name (str): Jméno 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 podle jejího podpisu
  • get_modifier_from_signature (str): Vrátí modifikátor podle jeho podpisu
  • get_state_variable_from_name (str): Vrátí stavovou proměnnou podle jejího jména

Objekt Function nebo Modifier má:

  • name (str): Jméno 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 přečtených proměnných
  • variables_written (list(Variable)): Seznam zapsaných proměnných
  • state_variables_read (list(StateVariable)): Seznam přečtených stavových proměnných (podmnožina proměnných read)
  • state_variables_written (list(StateVariable)): Seznam zapsaných stavových proměnných (podmnožina proměnných written)

Stránka naposledy aktualizována: 3. února 2025

Byl tento tutoriál užitečný?