Salt la conținutul principal

Cum se utilizează Slither pentru a găsi erori în contractele inteligente

soliditycontracte inteligentesecuritatetestareanaliză statică
Avansat
Trailofbits
Construirea de contracte sigure(opens in a new tab)
9 iunie 2020
7 minute de citit minute read

Cum se utilizează Slither

Intenția acestui tutorial este de a arăta cum se utilizează Slither pentru a găsi automat erori în contracte inteligente.

Instalare

Slitter necesită Python >= 3.6. Acesta poate fi instalat prin pip sau folosind docker.

Slither prin pip:

pip3 install --user slither-analyzer

Slither prin docker:

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

Ultima comandă rulează eth-Security-Toolbox într-un docker care are acces la directorul curent. Poți să schimbi fișierele din gazdă și să execuți instrumentele de pe fișiere din docker

În interiorul docker, execută:

solc-select 0.5.11
cd /home/trufflecon/

Rularea unui script

Pentru a rula un script python cu python 3:

python3 script.py

Linie de comandă

Linia de comandă în comparație cu scripturile definite de utilizator. Slither vine cu un set de detectoare predefinite care găsesc multe erori obișnuite. Apelarea Slither din linia de comandă va rula toate detectoarele, fără cunoștințe detaliate de analiză statică necesare:

slither project_paths

Pe lângă detectoare, Slither are capacități de revizuire a codului prin imprimante(opens in a new tab) și instrumente(opens in a new tab).

Use crytic.io(opens in a new tab) to get access to private detectors and GitHub integration.

Analiză statică

Capacitățile și designul cadrului de analiză statică Slither au fost descrise în postările de pe blog (1(opens in a new tab), 2(opens in a new tab)) și academic paper(opens in a new tab).

Analiza statică există în diferite arome. Cel mai probabil, vei realiza că unele compilatoare precum clang(opens in a new tab) și gcc(opens in a new tab) depind de aceste tehnici de cercetare, dar și sprijină (Infer(opens in a new tab), CodeClimate(opens in a new tab), FindBugs(opens in a new tab) și instrumente bazate pe metode formale precum Frama-C(opens in a new tab) și Polyspace(opens in a new tab).

Nu vom analiza exhaustiv tehnicile de analiză statică și cercetătorii aici. În schimb, ne vom concentra pe ce este nevoie pentru a înțelege modul de funcționare a Slither, încât să îl poți folosi mai eficient pentru a găsi erori și înțelege codul.

Reprezentarea codului

Spre deosebire de o analiză dinamică, care analizează o singură cale de execuție, analiza statică raționează toate căile simultan. Pentru a face acest lucru, se bazează pe o reprezentare de cod diferită. Două din cele mai frecvente sunt arborele de sintaxă abstractă (AST) și graficul fluxului de control (CFG).

Arborele de sintaxă abstractă (AST)

AST este utilizat de fiecare dată când compilatorul analizează codul. Este probabil cea mai de bază structură pe care poate fi efectuată analiza statică.

Pe scurt, un AST este un arbore structurat în care, de obicei, fiecare frunză conține o variabilă sau o constantă și nodurile interne sunt operatori sau operațiuni de control al fluxului. Să considerăm următorul cod:

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

AST-ul corespunzător este indicat în:

AST

Slither utilizează AST-ul exportat de solc.

Deși este simplu de construit, AST este o structură imbricată. Uneori, acest lucru nu este cel mai simplu de analizat. De exemplu, pentru a identifica operațiile utilizate de expresia a + b <= a, trebuie să analizezi mai întâi <= și apoi +. O abordare comună este utilizarea așa-numitului model de vizitator, care navighează prin arbore recursiv. Slither conține un vizitator generic în ExpressionVisitor(opens in a new tab).

Următorul cod utilizează ExpresionVisitor pentru a detecta dacă expresia conține o adăugare:

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 is the expression to be tested
14print(f'The expression {expression} has a addition: {visitor.result()}')
Afișează tot
Copiați

Graficul fluxului de control (CFG)

Cea de-a doua reprezentare a codurilor este graficul fluxului de control (CFG). După cum sugerează și numele, este o reprezentare pe bază de grafic care expune toate căile de execuție. Fiecare nod conține una sau mai multe instrucțiuni. Marginile din grafic reprezintă operațiunile fluxului de control (dacă/atunci/altfel, buclă etc.). CFG-ul exemplului nostru anterior este:

CFG

CFG este reprezentarea pe baza căreia sunt construite cele mai multe analize.

Există multe alte reprezentări de cod. Fiecare reprezentare are avantaje și dezavantaje în funcție de analiza pe care dorești să o efectuezi.

Analiză

Cel mai simplu tip de analize pe care le poți efectua cu Slither sunt analizele sintactice.

Analiza sintaxei

Slither poate naviga prin diferite componente ale codului și reprezentarea lor pentru a găsi incoerențe și defecte folosind o abordare asemănătoare modelului.

De exemplu, următoarele detectoare caută probleme legate de sintaxă:

Analiza semantică

Spre deosebire de analiza sintaxei, o analiză semantică va merge mai adânc și va analiza „sensul" codului. Această familie include câteva tipuri largi de analize. Ele duc la rezultate mai puternice și utile, dar sunt, de asemenea, mai complex de scris.

Analizele semantice sunt utilizate pentru cele mai avansate detectări de vulnerabilitate.

Analiza dependenței de date

Se spune despre o variabilă cum ar fi variable_a că este data dependentă de variable_b dacă există o modalitate prin care valoarea variable_a să fie influențată de variable_b.

În codul următor, variable_a depinde de variable_b:

1// ...
2variable_a = variable_b + 1;
Copiați

Slither vine cu capabilități dependențe de date(opens in a new tab) încorporate, datorită reprezentării sale intermediare (discutate într-o secțiune ulterioară).

Un exemplu de utilizare a dependenței de date poate fi găsit în detectorul de egalitate strictă periculoasă(opens in a new tab). Aici Slither va căuta o comparație strictă a egalității cu o valoare periculoasă (incorrect_strict_equality.py#L86-L87(opens in a new tab)) și va informa utilizatorul că ar trebui să utilizeze >= sau <= în loc de ==, pentru a împiedica un atacator să blocheze contractul. Printre altele, detectorul va considera ca fiind periculoasă valoarea returnată a unui apel către balanceOf(address) (incorrect_strict_equality.py#L63-L64(opens in a new tab)) și va utiliza motorul dependent de date pentru a urmări utilizarea acestuia.

Calcul de punct fix

Dacă analiza ta navighează prin CFG și urmează marginile, este posibil să vezi noduri deja vizitate. De exemplu, dacă o buclă este prezentată precum cea de mai jos:

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

Analiza ta va trebui să știe când să se oprească. Există două strategii principale aici: (1) iterare pe fiecare nod un număr finit de ori, (2) calcularea unui așa-numit fixpoint. Un fixpoint înseamnă că analiza acestui nod nu oferă nici o informație semnificativa.

Un exemplu de „fixpoint” utilizat poate fi găsit în detectoare de reintrare: Slither explorează nodurile și caută apeluri externe, scrie și citește în stocare. După ce a ajuns la un „fixpoint” (reentrancy.py#L125-L131(opens in a new tab)), acesta oprește explorarea și analizează rezultatele pentru a vedea dacă există o reintrare, prin diferite modele de reintrare (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)).

Scrierea analizelor folosind calculul eficient de punct fix necesită o bună înțelegere a modului în care analiza își propagă informațiile.

Reprezentarea intermediară

O reprezentare intermediară (IR) este un limbaj care se dorește a fi mai adecvat la analiza statică decât cea inițială. Slither traduce Solidity în propria sa IR: SlithIR(opens in a new tab).

Înțelegerea SlithIR nu este necesară dacă dorești numai să scrii verificări de bază. Cu toate acestea, va fi utilă dacă intenționezi să scrii analize semantice avansate. Imprimantele SlithIR(opens in a new tab) și SSA(opens in a new tab) te vor ajuta să înțelegi modul în care este tradus codul.

Principii API

Slither are un API care îți permite să explorezi atributele de bază ale contractului și funcțiile sale.

Pentru a încărca un cod de bază:

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

Explorarea contractelor și a funcțiilor

Un obiect Slitter are:

  • contracts (list(Contract): listă de contracte
  • contracts_derived (list(Contract): lista contractelor care nu sunt moștenite de un alt contract (subset de contracte)
  • get_contract_from_name (str): Returnează un contract din numele său

Un obiect contract are:

  • name (str): Numele contractului
  • functions (list(Function)): Listă de funcții
  • modifiers (list(Modifier)): Listă de funcții
  • all_functions_called (list(Function/Modifier)): Lista tuturor funcțiilor interne accesibile prin contract
  • inheritance (list(Contract)): Lista contractelor moștenite
  • get_function_from_signature (str): Returnează o funcție de la semnătură
  • get_modifier_from_signature (str): Returnează un modificator de la semnătură
  • get_state_variable_from_name (str): Returnează o variabilă de stare din numele său

Un obiect Function sau un obiect Modifier are:

  • name (str): Numele funcției
  • contract (contract): contractul în care este declarată funcția
  • nodes (list(Node)): Lista nodurilor care compun CFG-ul funcției/modificatorului
  • entry_point (Node): Punctul de intrare al CFG
  • variables_read (list(Variable)): Lista variabilelor citite
  • variables_written (list(Variable)): Lista variabilelor scrise
  • state_variables_read (list(StateVariable)): Lista variabilelor de stare citite (subset de variabile`read)
  • state_variables_written (list(StateVariable)): Lista variabilelor de stare scrise (subset de variabile`scris)

Ultima modificare: @nicklcanada(opens in a new tab), 15 august 2023

A fost util acest tutorial?