Keamanan kontrak pintar
Terakhir diedit: , Invalid DateTime
Kontrak pintar Ethereum sangat fleksibel, mampu menampung sejumlah besar token (sering kali lebih dari $1 Miliar) dan menjalankan logika yang tak bisa diubah berdasarkan kode kontrak pintar yang digunakan sebelumnya. Sekalipun hal ini telah menciptakan ekosistem kontrak pintar yang saling berhubungan dan tidak memerlukan kepercayaan yang penuh energi dan kreatif, juga merupakan ekosistem yang sempurna untuk menarik penyerang yang mencari keuntungan dengan mengeksploitasi kerentanan dalam kontrak pintar dan perilaku tak terduga di Ethereum. Kode kontrak pintar biasanya tidak bisa diubah untuk menambal kelemahan keamanan, aset yang telah dicuri dari kontrak pintar tidak bisa didapatkan kembali, dan aset yang dicuri sangat sulit untuk dilacak. Jumlah total nilai yang dicuri atau hilang karena masalah kontrak pintardengan mudah bernilai $1 Miliar. Beberapa nilai yang lebih besar karena kesalahan pengodean kontrak pintar meliputi:
- Masalah partity multi-sig #1 - kehilangan $30 Juta(opens in a new tab)
- Masalah parity multi-sig #2 - $300 Juta terkunci(opens in a new tab)
- Peretasan TheDAO, 3,6 Juta ETH! Lebih dari $1 Miliar dalam harga ETH saat ini(opens in a new tab)
Prasyarat
Ini akan membahas keamanan kontrak pintar jadi pastikan Anda terbiasa dengan kontrak pintar sebelum menangani tentang keamanan.
Bagaimana menulis kode kontrak pintar yang lebih aman
Sebelum meluncurkan kode apa pun ke Jaringan Utama, penting untuk mengambil langkah pencegahan yang memadai untuk melindungi segala sesuatu yang bernilai yang dipercayakan oleh kontrak pintar Anda. Dalam artikel ini, kita akan membahas tentang beberapa serangan spesifik, menyediakan sumber daya untuk mempelajari selengkapnya tentang jenis serangan, dan memberi Anda beberapa peralatan dasar dan praktik terbaik untuk memastikan kontrak Anda berfungsi dengan benar dan dengan aman.
Audit bukan sebuah solusi ampuh
Beberapa tahun sebelumnya, peralatan untuk menulis, mengompilasi, menguji, dan menggunakan kontrak pintar sangat belum matang, mengakibatkan banyak proyek menulis kode Solidity dengan cara yang berantakan, menghalangi auditor yang akan melakukan investigasi kode guna memastikannya berfungsi dengan aman dan sesuai harapan. Pada tahun 2020, proses pengembangan dan peralatan yang mendukung penulisan Solidity jauh lebih baik; memanfaatkan praktik terbaik ini tidak hanya memastikan proyek Anda lebih mudah dikelola, tapi juga merupakan bagian vital dari keamanan proyek Anda. Sebuat proses audit pada akhir penulisan kontrak pintar Anda tidak lagi cukup sebagai satu-satunya pertimbangan keamanan yang bisa dibuat dalam proyek Anda. Keamanan dimulai sebelum Anda menulis baris pertama dari kode kontrak pintar Anda, keamanan dimulai dengan desain dan proses pengembangan yang baik.
Proses pengembangan kontrak pintar
Paling sedikit:
- Semua kode disimpan dalam sistem kontrol versi, seperti git
- Semua modifikasi kode dibuat lewat Tarik Permintaan
- Semua Tarik Permintaan harus memiliki setidaknya satu pengulas. Jika proyek Anda bersifat tunggal, pertimbangkanlah untuk mencari penulis tunggal lainnya dan saling bertukar ulasan!
- Satu perintah mengompilasikan, menggunakan, dan menjalankan serangkaian pengujian terhadap kode Anda menggunakan lingkungan Ethereum pengembangan (Lihat: Truffle)
- Anda harus menjalankan kode Anda melalui peralatan analisis kode dasar seperti Mythril dan Slither, secara ideal sebelum tiap tarik permintaan digabungkan, yang membandingkan perbedaan output
- Solidity tidak menampilkan peringatan pengompilasi APA PUN
- Kode Anda terdokumentasi dengan baik
Masih ada banyak hal yang harus dibahas terkait proses pengembangan, tapi item-item ini adalah tempat yang bagus untuk memulai. Untuk item dan penjelasan mendetail selengkapnya, lihat daftar periksa kualitas proses yang disediakan oleh DeFiSafety(opens in a new tab). DefiSafety(opens in a new tab) adalah layanan publik tidak resmi yang menerbitkan ulasan atas berbagai dApp Ethereum publik yang besar. Bagian dari sistem rating DeFiSafety meliputi seberapa baik proyek mematuhi daftar periksa kualitas proses ini. Dengan mengikuti proses ini:
- Anda akan menghasilkan kode yang lebih aman, melalui pengujian otomatis yang dapat dibuat kembali
- Auditor akan dapat mengulas proyek Anda dengan lebih efektif
- Orientasi yang lebih mudah bagi pengembang baru
- Memungkinkan pengembang untuk dengan cepat mengulangi, menguji, dan mendapat masukan tentang modifikasi
- Proyek Anda lebih jarang mengalami regresi
Serangan dan kerentanan
Karena sekarang Anda akan menulis kode Solidity menggunakan proses pengembangan yang efisien, mari lihat beberapa kerentanan umum Solidity untuk mengetahui apa yang salah.
Re-entrancy
Re-entrancy adalah salah satu dari masalah keamanan yang paling besar dan paling penting untuk dipertimbangkan saat mengembangkan Kontrak Pintar. Meskipun EVM tidak bisa menjalankan beberapa kontrak pada saat bersamaan, sebuah kontrak yang memanggil kontrak lainnya membuat jeda pada eksekusi pemanggilan kontrak dan state memori sampai pemanggilan kembali, di mana dalam proses ini, eksekusi berjalan secara normal. Menjeda dan memulai kembali ini bisa menciptakan kerentanan yang dikenal sebagai "re-entrancy".
Ini adalah versi sederhana dari sebuah kontrak yang rentan terhadap re-entrancy:
1// KONTRAK INI SENGAJA DIBUAT MEMILIKI KERENTANAN, JANGAN DISALIN2contract Victim {3 mapping (address => uint256) public balances;45 function deposit() external payable {6 balances[msg.sender] += msg.value;7 }89 function withdraw() external {10 uint256 amount = balances[msg.sender];11 (bool success, ) = msg.sender.call.value(amount)("");12 require(success);13 balances[msg.sender] = 0;14 }15}16Tampilkan semuaSalin
Untuk memungkinkan seorang pengguna menarik ETH yang telah disimpan sebelumnya pada kontrak, fungsi ini
- Membaca jumlah saldo yang dimiliki pengguna
- Mengirimkan kepada pengguna jumlah saldo dalam ETH
- Mengatur ulang saldo pengguna ke 0, sehingga mereka tidak bisa menarik saldo mereka lagi.
Jika dipanggil dari akun reguler (seperti akun MetaMask milik Anda), ini berfungsi sebagaimana mestinya: msg.sender.call.value(), mengirimkan ETH ke akun Anda. Akan tetapi, kontrak pintar juga bisa membuat pemanggilan. Jika kontrak kustom yang jahat adalah kontrak yang memanggil withdraw()
, msg.sender.call.value() tidak hanya akan mengirim amount
ETH, tetapi secara implisit juga akan memanggil kontrak untuk memulai eksekusi kode. Bayangkan kontrak jahat ini:
1contract Attacker {2 function beginAttack() external payable {3 Victim(VICTIM_ADDRESS).deposit.value(1 ether)();4 Victim(VICTIM_ADDRESS).withdraw();5 }67 function() external payable {8 if (gasleft() > 40000) {9 Victim(VICTIM_ADDRESS).withdraw();10 }11 }12}13Tampilkan semuaSalin
Memanggil Attacker.beginAttack() akan memulai satu siklus yang terlihat seperti ini:
10.) Attacker's EOA calls Attacker.beginAttack() with 1 ETH20.) Attacker.beginAttack() deposits 1 ETH into Victim34 1.) Attacker -> Victim.withdraw()5 1.) Victim reads balances[msg.sender]6 1.) Victim sends ETH to Attacker (which executes default function)7 2.) Attacker -> Victim.withdraw()8 2.) Victim reads balances[msg.sender]9 2.) Victim sends ETH to Attacker (which executes default function)10 3.) Attacker -> Victim.withdraw()11 3.) Victim reads balances[msg.sender]12 3.) Victim sends ETH to Attacker (which executes default function)13 4.) Attacker no longer has enough gas, returns without calling again14 3.) balances[msg.sender] = 0;15 2.) balances[msg.sender] = 0; (it was already 0)16 1.) balances[msg.sender] = 0; (it was already 0)17Tampilkan semua
Memanggil Attacker.beginAttack dengan 1 ETH akan menyerang Korban secara re-entrancy, yang menarik lebih banyak ETH dari yang disediakan (diambil dari saldo pengguna lain, menyebabkan kontrak Korban menjadi di bawah penjaminan)
Bagaimana mengatasi re-entrancy (cara yang salah)
Seseorang mungkin menganggap mengalahkan re-entrancy cukup dengan mencegah kontrak pintar apa pun berinteraksi dengan kode Anda. Anda mencari tumpukan yang meluap, Anda menemukan potongan kode ini dengan banyak suara positif:
1function isContract(address addr) internal returns (bool) {2 uint size;3 assembly { size := extcodesize(addr) }4 return size > 0;5}6Salin
Tampak masuk akal: kontrak memiliki kode, jika pemanggil memiliki kode apa pun, jangan membiarkannya melakukan deposito. Mari tambahkan ini:
1// KONTRAK INI SENGAJA DIBUAT MEMILIKI KERENTANAN, JANGAN DISALIN2contract ContractCheckVictim {3 mapping (address => uint256) public balances;45 function isContract(address addr) internal returns (bool) {6 uint size;7 assembly { size := extcodesize(addr) }8 return size > 0;9 }1011 function deposit() external payable {12 require(!isContract(msg.sender)); // <- NEW LINE13 balances[msg.sender] += msg.value;14 }1516 function withdraw() external {17 uint256 amount = balances[msg.sender];18 (bool success, ) = msg.sender.call.value(amount)("");19 require(success);20 balances[msg.sender] = 0;21 }22}23Tampilkan semuaSalin
Sekarang, untuk mendepositokan ETH, Anda tidak boleh memiliki kode kontrak pintar di alamat Anda. Akan tetapi, ini bisa dengan mudah dikalahkan dengan kontrak Penyerang berikut ini:
1contract ContractCheckAttacker {2 constructor() public payable {3 ContractCheckVictim(VICTIM_ADDRESS).deposit(1 ether); // <- New line4 }56 function beginAttack() external payable {7 ContractCheckVictim(VICTIM_ADDRESS).withdraw();8 }910 function() external payable {11 if (gasleft() > 40000) {12 Victim(VICTIM_ADDRESS).withdraw();13 }14 }15}16Tampilkan semuaSalin
Sementara serangan pertama adalah serangan pada logika kontrak, ini adalah serangan pada perilaku penggunaan kontrak Ethereum. Dalam pembuatannya, kontrak belum mengembalikan kodenya untuk digunakan pada alamatnya, tapi mempertahankan kontrol penuh EVM SELAMA proses ini.
Secara teknis, hal ini dimungkinkan untuk mencegah kontrak pintar memanggil kode Anda, menggunakan baris ini:
1require(tx.origin == msg.sender)2Salin
Namun, ini tetap bukanlah solusi yang bagus. Satu dari aspek paling menyenangkan dari Ethereum adalah komposabilitasnya, kontrak pintar saling terintegrasi dan membangun satu sama lain. Dengan menggunakan baris di atas, Anda sedang membatasi daya guna proyek Anda.
Bagaimana mengatasi re-entrancy (cara yang benar)
Hanya dengan mengalihkan urutan pembaruan penyimpanan dan pemanggilan eksternal, kita mencegah kondisi re-entrancy yang memungkinkan penyerangan. Memanggil kembali penarikan, sekalipun dimungkinkan, tidak akan menguntungkan penyerang, karena penyimpanan saldo
telah diatur ke 0.
1contract NoLongerAVictim {2 function withdraw() external {3 uint256 amount = balances[msg.sender];4 balances[msg.sender] = 0;5 (bool success, ) = msg.sender.pemanggilan.value(amount)("");6 require(success);7 }8}9Salin
Kode di atas mengikuti pola desain "Pemeriksaan-Efek-Interaksi", yang membantu perlindungan terhadap re-entrancy. Anda bisa membaca selengkapnya tentang Pemeriksaan-Efek-Interaksi di sini(opens in a new tab)
Bagaimana mengatasi re-entrancy (opsi inti)
Setiap kali Anda mengirim ETH ke alamat tak terpercaya atau berinteraksi dengan kontrak yang tidak dikenal (seperti pemanggilan transfer()
dari alamat token yang disediakan pengguna), Anda membuka diri untuk kemungkinan serangan re-entrancy. Dengan mendesain kontrak yang tidak mengirim ETH maupun memanggil kontrak tak terpercaya, Anda mencegah kemungkinan serangan re-entrancy!
Jenis serangan lainnya
Jenis serangan di atas mencakup masalah pengodean kontrak pintar (re-entrancy) dan keanehan pada Ethereum (menjalankan kode di dalam pembangun kontrak, sebelum kode tersedia di alamat kontrak). Masih banyak jenis serangan yang perlu diwaspadai, seperti:
- Front-running
- Penolakan pengiriman ETH
- Overflow/underflow integer
Bacaan lebih lanjut:
- Serangan yang Diketahui pada Kontrak Pintar Consensys(opens in a new tab) - Penjelasan yang sangat mudah dibaca tentang kerentanan paling signifikan, disertai contoh kode untuk sebagian besar kerentanan.
- Daftar SWC(opens in a new tab) - Daftar terkurasi CWE yang berlaku untuk Ethereum dan kontrak pintar
Perangkat keamanan
Sekalipun memahami dasar-dasar keamanan Ethereum dan melibatkan jasa firma pengauditan profesional untuk mengulas kode Anda tak tergantikan, ada banyak peralatan yang tersedia untuk menolong menyoroti potensi masalah dalam kode Anda.
Keamanan kontrak pintar
Slither - Kerangka kerja analisis statis untuk Solidity yang ditulis dalam Python 3.
MythX - API analis keamanan untuk kontrak pintar Ethereum.
Mythril - Peralatan analis keamanan untuk kode bita EVM.
Manticore - Antarmuka baris perintah yang menggunakan sebuah perangkat eksekusi simbolis pada kontrak pintar dan biner.
Securify - Pemindai keamanan untuk kontrak pintar Ethereum.
ERC20 Verifier - Peralatan verifikasi untuk memeriksa apakah sebuah kontrak memenuhi standar ERC20.
Verifikasi Formal
Informasi mengenai Verifikasi Formal
- Bagaimana cara kerja verifikasi formal pada kontrak pintar(opens in a new tab) 20 Juli 2018 - Brian Marick
- Bagaimana Verifikasi Formal Dapat Memastikan Kontrak Pintar Sempurna(opens in a new tab) 29 Januari 2018 - Bernard Mueller
Menggunakan peralatan
Dua dari peralatan paling populer untuk analisis keamanan kontrak pintar adalah:
- Slither(opens in a new tab) oleh Trail of Bits(opens in a new tab) (versi yang telah dihost: Crytic(opens in a new tab))
- Mythril(opens in a new tab) oleh ConsenSys(opens in a new tab) (versi yang telah dihost: MythX(opens in a new tab))
Keduanya adalah peralatan berguna yang menganalisa kode Anda dan melaporkan masalah. Masing-masing mempunyai versi yang telah dihost [commercial], tapi juga tersedia secara gratis untuk dijalankan secara lokal. Berikuti ini adalah contoh singkat bagaimana menjalankan Slither, yang tersedia dalam gambar Docker yang mudah dipahami trailofbits/eth-security-toolbox
. Anda akan perlu menginstal Docker jika Anda belum melakukannya(opens in a new tab).
$ mkdir test-slither$ curl https://gist.githubusercontent.com/epheph/460e6ff4f02c4ac582794a41e1f103bf/raw/9e761af793d4414c39370f063a46a3f71686b579/gistfile1.txt > bad-contract.sol$ docker run -v `pwd`:/share -it --rm trailofbits/eth-security-toolboxdocker$ cd /sharedocker$ solc-select 0.5.11docker$ slither bad-contract.sol
Akan menghasilkan output ini:
ethsec@1435b241ca60:/share$ slither bad-contract.solINFO:Detectors:Reentrancy in Victim.withdraw() (bad-contract.sol#11-16):External calls:- (success) = msg.sender.call.value(amount)() (bad-contract.sol#13)State variables written after the call(s):- balances[msg.sender] = 0 (bad-contract.sol#15)Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilitiesINFO:Detectors:Low level call in Victim.withdraw() (bad-contract.sol#11-16):- (success) = msg.sender.call.value(amount)() (bad-contract.sol#13)Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-callsINFO:Slither:bad-contract.sol analyzed (1 contracts with 46 detectors), 2 result(s) foundINFO:Slither:Use https://crytic.io/ to get access to additional detectors and GitHub integrationTampilkan semua
Slither telah mengidentifikasi potensi re-entrancy di sini, mengidentifikasi baris kunci di mana masalah mungkin muncul, dan memberi kita sebuah tautan untuk informasi lebih detail tentang masalahnya:
memungkinkan Anda dengan cepat belajar tentang potensi masalah dalam kode Anda. Seperti semua peralatan pengujian otomatis, Slither tidak sempurna, dan terlalu sering membuat kesalahan pada aspek pelaporan. Slither bisa memperingatkan potensi re-entrancy, bahkan ketika tidak ada kerentanan yang bisa diekspoloitasi. Sering kali, meninjau ulang PERBEDAAN dalam output Slither di antara perubahan kode sangat memberi kelejasan, membantu menemukan kerentanan yang ditimbulkan sebelumnya daripada menunggu sampai kode proyek Anda selesai.
Bacaan lebih lanjut
Panduan praktik terbaik untuk keamanan kontrak pintar
- consensys.github.io/smart-contract-best-practices/(opens in a new tab)
- GitHub(opens in a new tab)
- Daftar rekomendasi keamanan dan praktik terbaik(opens in a new tab)
Standar verifikasi keamanan kontrak pintar (SCSVS)
Tahu tentang sumber daya komunitas yang membantu Anda? Edit halaman ini dan tambahkan!
Tutorial terkait
- Alur kerja pengembangan yang aman
- Cara menggunakan Slither untuk menemukan bug kontrak pintar
- Cara menggunakan Manticore untuk menemukan bug kontrak pintar
- Pedoman kemananan
- Kemananan token