Lanjut ke konten utama

Cara menggunakan Slither untuk menemukan bug kontrak pintar

soliditykontrak pintarkeamananpengujiananalisis statis
Tingkat lanjut
Trailofbits
Membuat kontrak yang aman(opens in a new tab)
9 Juni 2020
6 bacaan singkat minute read

Cara menggunakan Slither

Tujuan tutorial ini adalah menunjukkan cara menggunakan Slither untuk menemukan bug dalam kontrak pintar secara otomatis.

  • Instalasi
  • Penggunaan baris perintah
  • Pengantar analisis statis: Pengantar singkat tentang analisis statis
  • API: deskripsi API Python

Instalasi

Slither memerlukan versi Python >= 3.6. Itu bisa diinstal melalui pip atau docker.

Slither melalui pip:

pip3 install --user slither-analyzer

Slither melalui docker:

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

Perintah terakhirnya menjalankan kotak peralatan keamanan eth di dalam docker yang memiliki akses ke direktori Anda saat ini. Anda bisa mengubah file dari host Anda, dan menjalankan peralatannya pada file dari docker

Dalam docker, jalankan:

solc-select 0.5.11
cd /home/trufflecon/

Menjalankan skrip

Untuk menjalankan skrip python dengan python 3:

python3 script.py

Baris perintah

Baris perintah versus skrip yang ditentukan pengguna. Slither dilengkapi dengan serangkaian detektor yang telah ditentukan sebelumnya yang menemukan banyak bug umum. Memanggil Slither dari baris perintah akan menjalankan semua detektor, pengetahuan mendetail tentang analisis statis tidak diperlukan:

slither project_paths

Selain detektor, Slither memiliki kemampuan meninjau kode melalui printer(opens in a new tab) dan peralatan(opens in a new tab).

Gunakan crytic.io(opens in a new tab) untuk mengakses detektor privat dan integrasi GitHub.

Analisis statis

Kemampuan dan desain kerangka kerja analisis statis Slither telah dijelaskan dalam postingan blog (1(opens in a new tab), 2(opens in a new tab)) dan sebuah makalah akademik(opens in a new tab).

Analisis statis hadir dalam rasa berbeda. Kemungkinan besar Anda menyadari bahwa pengompilasi seperti clang(opens in a new tab) dan gcc(opens in a new tab) bergantung pada teknik penelitian ini, tapi teknik ini juga mendasari (Infer(opens in a new tab), CodeClimate(opens in a new tab), FindBugs(opens in a new tab), dan peralatan berbasis metode formal seperti Frama-C(opens in a new tab) dan Polyspace(opens in a new tab).

Kita tidak akan mengulas secara lengkap teknik analisis statis dan penelitinya di sini. Sebaliknya, kita akan fokus pada apa yang diperlukan untuk memahami cara kerja Slither, sehingga Anda bisa menggunakannya dengan lebih efektif untuk menemukan bug dan memahami kode.

  • Representasi kode
  • Analisis kode
  • Representasi tingkat menengah

Representasi kode

Berbeda dengan analisis dinamis, yang menalarkan tentang jalur eksekusi tunggal, analisis statis menalarkan tentang semua jalur sekaligus. Untuk melakukannya, bergantung pada representasi kode yang berbeda. Dua representasi paling umum adalah pohon sintaksis abstrak (AST) dan grafik aliran kontrol (CFG).

Pohon Sintaksis Abstrak (AST)

AST digunakan setiap kali pengompilasi menguraikan kode. Kemungkinan ini adalah struktur paling dasar di mana analisis statis bisa dilakukan.

Singkatnya, AST adalah pohon berstruktur di mana, biasanya, tiap daunnya berisi satu variabel atau konstanta dan node internalnya adalah operand atau operasi alur kontrol. Perhatikan kode berikut:

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

AST yang sesuai ditunjukkan di dalam:

AST

Slither menggunakan AST yang diekspor oleh solc.

Meskipun mudah untuk dibuat, AST adalah struktur bersarang. Terkadang, ini bukan yang paling mudah untuk dianalisis. Contohnya, untuk mengenali operasi yang digunakan oleh ekspresi a + b <= a, Anda harus terlebih dahulu menganalisa <= dan kemudian +. Pendekatan yang umum adalah menggunakan pola pengunjung, yang menelusuri pohonnya secara berulang. Slither berisi pengunjung generik dalam ExpressionVisitor(opens in a new tab).

Kode berikut menggunakan ExpressionVisitor untuk mendeteksi apakah ekspresi berisi tambahan:

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) # ekspresi adalah ekspresi yang harus diuji
14print(f'The expression {expression} has a addition: {visitor.result()}')
Tampilkan semua
Salin

Grafik Aliran Kontrol (CFG)

Representasi kode paling umum kedua adalah grafik aliran kontrol (CFG). Seperti namanya, ini adalah representasi berbasis grafik yang menampilkan semua jalur eksekusi. Setiap node berisi satu atau beberapa instruksi. Tepi dalam grafik merepresentasikan operasi alur kontrol (jika/maka/jika tidak, perulangan, dll). CFG contoh kita sebelumnya adalah:

CFG

CFG adalah representasi yang di atasnya kebanyakan analisis dibangun.

Ada banyak representasi kode lainnya. Setiap representasi memiliki kelebihan dan kekurangan bergantung pada analisis yang ingin Anda lakukan.

Analisis

Jenis analisis paling sederhana yang dapat dilakukan dengan Slither adalah analisis sintaksis.

Analisis sintaksis

Slither bisa menelusuri berbagai komponen kode dan representasinya untuk menemukan inkonsistensi dan kelemahan dengan menggunakan pendekatan pola yang mirip.

Contohnya, detektor berikut mencari masalah yang terkait dengan sintaksis:

Analisis semantik

Berbeda dengan analisis sintaksis, analisis semantik akan bergerak lebih dalam dan menganalisis "arti" kode. Keluarga ini memasukkan beberapa jenis analisis yang luas. Mereka membuat hasil yang lebih efektif dan berguna, tapi juga lebih rumit untuk ditulis.

Analisis semantik digunakan untuk deteksi kerentanan yang paling canggih.

Analisis dependensi data

Variabel variable_a dianggap bergantung pada data variable_b jika ada jalur yang nilai variable_a dipengaruhi oleh variable_b.

Dalam kode berikut, variable_a bergantung pada variable_b:

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

Slither hadir dengan kemampuan dependensi data(opens in a new tab) bawaan, berkat representasi menengahnya (dibahas di bagian akhir).

Satu contoh penggunaan dependensi data bisa ditemukan dalam detektor kesamaan ketat berbahaya(opens in a new tab). Di sini, Slither akan mencari perbandingan kesamaan ketat dari nilai berbahaya (incorrect_strict_equality.py#L86-L87(opens in a new tab)), dan akan memberi tahu pengguna bahwa harus menggunakan >= atau <= daripada ==, untuk mencegah penyerang memerangkap kontrak. Di antara lainnya, detektor akan menganggap nilai pengembalian dari pemanggilan balanceOf(address) berbahaya (incorrect_strict_equality.py#L63-L64(opens in a new tab)), dan akan menggunakan mesin dependensi data untuk melacak penggunaannya.

Komputasi titik tetap

Jika analisis Anda menelusuri CFG dan mengikuti tepinya, Anda mungkin akan melihat node yang sudah dikunjungi. Contohnya, jika satu perulangan ditampilkan seperti di bawah:

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

Analisis Anda perlu tahu kapan harus berhenti. Ada dua strategi utama di sini: (1) mengulangi setiap node beberapa kali, (2) menghitung apa yang disebut titik tetap. Titik tetap pada dasarnya berarti bahwa menganalisis node ini tidak memberi informasi berarti apa pun.

Contoh penggunaan titik tetap bisa ditemukan dalam detektor reentrancy: Slither menjelajah node, dan mencari pemanggilan eksternal, menulis dan membaca ke penyimpanan. Setelah mencapai titik tetap (reentrancy.py#L125-L131(opens in a new tab)), analisis akan menghentikan penjelajahan, dan menganalisis hasilnya untuk melihat apakah ada reentrancy, dengan menggunakan pola reentrancy berbeda (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)).

Menulis analisis dengan komputasi titik tetap yang efisien memerlukan pemahaman yang baik tentang cara analisis menyebarkan informasinya.

Representasi tingkat menengah

Representasi tingkat menengah (IR) adalah sebuah bahasa yang dimaksudkan agar lebih mudah diterima oleh analisis statis daripada bahasa aslinya. Slither menerjemahkan Solidity ke dalam IR-nya sendiri: SlithIR(opens in a new tab).

Memahami SlithIR tidak penting jika Anda hanya mau menulis pemeriksaan biasa. Namun, itu akan berguna jika Anda berencana menulis analisis semantik tingkat lanjut. Printer SlithIR(opens in a new tab) dan SSA(opens in a new tab) akan membantu Anda memahami cara kode diterjemahkan.

Dasar-Dasar API

Slither memiliki API yang mengizinkan Anda menjelajah atribut dasar kontrak dan fungsinya.

Untuk memuat basis kode:

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

Menjelajahi kontrak dan fungsi

Objek Slither memiliki:

  • kontrak (daftar(Kontrak): daftar kontrak
  • contracts_derived (list(Contract): daftar kontrak yang tidak diwariskan oleh kontrak lainnya (subset kontrak)
  • get_contract_from_name (str): Mengembalikan kontrak dari namanya

Objek Contract memiliki:

  • name (str): Nama kontrak
  • functions (list(Function)): Daftar fungsi
  • modifiers (list(Modifier)): Daftar fungsi
  • all_functions_called (list(Function/Modifier)): Daftar semua fungsi internal yang dapat dicapai oleh kontrak
  • inheritance (list(Contract)): Daftar kontrak yang diwariskan
  • get_function_from_signature (str): Mengembalikan Fungsi dari tanda tangannya
  • get_modifier_from_signature (str): Mengembalikan Pengubah dari tanda tangannya
  • get_state_variable_from_name (str): Mengembalikan StateVariable dari namanya

Objek Function atau Modifier memiliki:

  • name (str): Nama fungsi
  • contract (contract): kontrak di mana fungsi dideklarasikan
  • nodes (list(Node)): Daftar node yang membentuk CFG fungsi/pengubah
  • entry_point (Node): Titik masuk CFG
  • variables_read (list(Variable)): Daftar variabel yang dibaca
  • variables_written (list(Variable)): Daftar variabel yang ditulis
  • state_variables_read (list(StateVariable)): Daftar variabel state yang dibaca (subset variabel `yang dibaca)
  • state_variables_written (list(StateVariable)): Daftar variabel state yang ditulis (subset variabel `yang ditulis)

Terakhir diedit: @nhsz(opens in a new tab), 15 Agustus 2023

Apakah tutorial ini membantu?