Pendahuluan
Perusahaan kami terlibat dalam audit keamanan kontrak pintar, dan masalah menggunakan alat otomatis sangat akut. Seberapa banyak mereka dapat membantu dalam mengidentifikasi tempat-tempat yang mencurigakan, mana yang harus digunakan, apa yang dapat mereka lakukan dan apa spesifik pekerjaan di bidang ini? Ini dan masalah-masalah terkait adalah subjek dari artikel ini. Dan materi akan menjadi upaya untuk bekerja dengan kontrak nyata dengan bantuan perwakilan dan resep paling menarik untuk meluncurkan perangkat lunak yang sangat beraneka ragam dan sangat menarik ini. Pada awalnya saya ingin membuat satu artikel, tetapi setelah beberapa waktu jumlah informasi menjadi terlalu besar, sehingga diputuskan untuk membuat serangkaian artikel, satu untuk setiap autoanalyzer. Daftar dari mana kita akan mengambil alat disajikan, misalnya, di sini , tetapi jika alat menarik lainnya ditemukan selama penulisan, saya akan menggambarkannya dengan senang hati dan mengujinya.
Saya harus mengatakan bahwa tugas audit sangat menarik, karena sejauh ini, pengembang belum banyak memperhatikan aspek ekonomi dari algoritma, dan optimasi internal. Dan audit kontrak pintar telah menambahkan beberapa vektor serangan menarik yang harus dipertimbangkan ketika mencari kesalahan. Juga, ternyata, banyak alat untuk pengujian otomatis muncul: analisis statis, analisis bytecode, fuzzers, parser dan banyak perangkat lunak bagus lainnya.
Tujuan artikel: untuk mempromosikan distribusi kode kontrak aman dan memungkinkan pengembang dengan cepat dan mudah menyingkirkan bug bodoh, yang seringkali paling menjengkelkan. Ketika protokol itu sendiri benar-benar dapat diandalkan dan memecahkan masalah serius, keberadaan kesalahan bodoh yang dilupakan pada tahap pengujian dapat secara serius merusak kehidupan proyek. Karena itu, mari kita belajar untuk menggunakan, paling tidak, alat yang memungkinkan "sedikit darah" untuk menyingkirkan masalah-masalah terkenal.
Ke depan, saya harus mengatakan bahwa bug kritis paling umum yang kami temui dalam audit masih merupakan masalah implementasi logis, dan bukan kerentanan khas seperti hak akses, integer overflow, reentrancy. Audit solusi yang besar dan lengkap tidak mungkin tanpa pengembang berpengalaman yang mampu mengaudit logika kontrak tingkat tinggi, siklus hidupnya, aspek operasi nyata dan kepatuhan terhadap tugas, dan bukan hanya pola serangan biasa. Ini adalah logika tingkat tinggi yang sering menjadi sumber bug kritis.
Tetapi peringatan, lubang tipuan dan kesalahan yang ditinggalkan karena kecerobohan yang tidak boleh dilewatkan adalah takdir penganalisa otomatis, mereka harus mengatasi tugas-tugas ini lebih baik daripada orang. Ini adalah tesis ini yang akan diuji.
Fitur audit kode kontrak pintar
Audit kode kontrak pintar adalah bidang yang agak spesifik. Terlepas dari ukurannya yang kecil, kontrak pintar Ethereum adalah program lengkap yang dapat mengatur cabang, loop, pohon keputusan yang rumit, dan bahkan untuk mengotomatisasi transaksi yang tampaknya sederhana memerlukan pemikiran melalui semua cabang yang mungkin pada setiap langkah. Dari sudut pandang ini, pengembangan blockchain adalah level yang sangat rendah, sangat menuntut sumber daya dan sangat mengingatkan pada pengembangan sistem dan perangkat lunak yang disematkan dalam bahasa C / C ++ dan assembler. Itulah sebabnya kami senang melihat pada wawancara para pengembang algoritma tingkat rendah, tumpukan jaringan, layanan yang sangat banyak, semua orang yang berurusan dengan optimasi tingkat rendah dan audit kode.
Dari sudut pandang pengembang, Solidity juga cukup spesifik, meskipun mudah dibaca oleh hampir semua programmer dan pada langkah pertama dan tampaknya sangat sederhana. Kode soliditas cukup mudah dibaca, itu biasa bagi setiap pengembang yang tahu sintaks C / C ++ dan OOP, seperti JavaScript.
Di sini kesederhanaan kode adalah kunci untuk bertahan hidup, tidak ada pekerjaan berat, sehingga seluruh persenjataan pengembangan tingkat rendah digunakan dalam pekerjaan - algoritma yang memungkinkan penggunaan sumber daya yang efisien, menghemat memori: Merkle trees, Bloom filter, pemuatan sumber daya "malas", loop terbuka, pengumpulan sampah manual dan masih banyak lagi.
Sejumlah kecil kode sumber dan bytecode yang dihasilkan.
Kontrak pintar terpisah dibatasi dalam volume bytecode, setiap byte menghabiskan sejumlah gas, dan maksimum dibatasi dari atas, sehingga Anda dapat mendorong sekitar 10Kb ke dalam blockchain (saat ini), itu tidak akan berhasil lagi. Berikut adalah artikel yang bagus tentang berapa banyak biaya kontrak menyebarkan dan berapa banyak biaya gas . Karena itu, banyak yang tidak bisa didorong. Jika Anda melebih-lebihkan, maka beberapa ribu baris kode "rata-rata" adalah maksimum. Beberapa lusin metode, kurangnya agregasi dan logika yang kompleks umumnya sangat karakteristik kontrak. Segala sesuatu yang tidak sesuai mengharuskan Anda untuk memilih kode di perpustakaan yang terpisah, mengubah dan mempersulit prosedur untuk meletakkannya di jaringan. Pengembang soliditas mungkin senang untuk mendorong sekelompok kode ke dalam satu kontrak, tetapi mereka hanya perlu mengatur sistem kontrak mereka dengan benar dengan membuat perpustakaan kelas terpisah dengan penyimpanan mereka sendiri. Dan itu nyaman untuk menguraikan "kelas" yang terpisah seperti itu ke dalam file yang terpisah, dan karena itu, membaca kode kontrak itu cukup bagus, semuanya terstruktur dengan baik sejak awal - itu tidak akan berhasil jika tidak. Sebagai contoh, saya merekomendasikan untuk melihat bagaimana ERC721 dibuat dalam openzeppelin-solidity .
Gas, gas, gas
Gas memperkenalkan lapisan logika tambahan dalam pelaksanaan kode kontrak, yang membutuhkan audit. Selain itu, tidak seperti kode tradisional, bagian kode yang sama dapat menghabiskan jumlah gas yang berbeda. Sebuah tabel opmode EVM dan biayanya berguna untuk memahami pembatasan gas.
Untuk menunjukkan mengapa Anda harus mencurahkan banyak waktu untuk mengevaluasi gas, pertimbangkan pseudo-code ini (tentu saja, tidak realistis, menembak di loop dengan eter adalah ide yang buruk):
// function fixSomeAccountAction(uint _actionId) public onlyValidator { // … events[msg.sender].push(_actionId); } // , function receivePaymentForSavedActions() { // ... for (uint256 i = 0; i < events[msg.sender].length; i++) { // actionId uint actionId = events[msg.sender][i]; // action uint payment = getPriceByEventId(actionId); if (payment > 0) { paymentAccumulators[msg.sender] += payment; } emit LogEventPaymentForAction(msg.sender, actionId, payment); // … // delete “events[msg.sender][i]” from array } }
faktanya adalah bahwa siklus dalam kontrak dijalankan peristiwa [msg.sender] .length kali, dan setiap iterasi adalah entri di blockchain (transfer () dan emit ()). Jika panjang array kecil, maka siklus memenuhi sepuluh kali, mendistribusikan pembayaran untuk setiap tindakan. Tetapi, jika array [msg.sender] event besar, maka akan ada banyak iterasi dan gas yang dihabiskan akan mencapai batas gas maksimum yang dikodekan keras (~ 8.000.000). Transaksi akan jatuh, dan sekarang tidak akan pernah berfungsi, karena tidak ada cara untuk mengurangi lamanya array [msg.sender] dalam kontrak. Jika siklus tidak hanya menghitung nilai unit, tetapi menulis ke blockchain (misalnya, beberapa biaya dibayar, pembayaran untuk tindakan), maka jumlah iterasi yang diperbolehkan sangat terbatas. Nilai sendiri - batas: 8.000.000, merekam nilai 256-bit baru: 20.000. Anda dapat menyimpan atau memperbarui metadata hanya untuk beberapa ratusan alamat 256-bit dengan beberapa metadata. Bagian lain yang menyenangkan adalah menulis nilai baru: 20.000, dan pembaruan yang sudah ada: 5.000, bahkan dengan lingkungan yang sama persis dengan kontrak Anda saat Anda melakukan transfer token ke alamat yang sudah memiliki token, Anda menghabiskan 4 kali lebih sedikit gas (5.000 vs 20.000) pada catatan.
Oleh karena itu, jangan heran bahwa masalah gas dalam kontrak pintar sangat erat kaitannya dengan keamanan kontrak, karena situasi ketika dana secara permanen terjebak dalam kontrak dari sudut pandang praktis sedikit berbeda dari situasi ketika mereka dicuri. Fakta bahwa instruksi ADD menghabiskan 3 gas, dan SSTORE (penghematan ke penyimpanan): 20.000 berarti bahwa sumber daya paling mahal dalam blockchain adalah penyimpanan, dan tugas-tugas mengoptimalkan kode kontrak memiliki banyak kesamaan dengan tugas-tugas pengembangan tingkat rendah dalam C dan ASM untuk disematkan. sistem, di mana penyimpanan juga sumber daya yang sangat terbatas.
Blockchain yang indah
Ini adalah paragraf yang sangat positif tentang mengapa blockchain begitu baik dari sudut pandang keamanan hanya untuk auditor. Determinasi dari pelaksanaan kode kontrak adalah kunci keberhasilan debugging dan pemutaran bug dan kerentanan. Secara teknis, setiap panggilan ke kode kontrak dapat direproduksi pada platform apa pun dengan sedikit akurasi, ini memungkinkan tes untuk bekerja di mana-mana dan sangat mudah untuk didukung, dan investigasi insiden dapat diandalkan dan tidak dapat disangkal. Sekarang kita selalu tahu siapa ketika fungsi apa dipanggil, dengan parameter apa, kode apa yang memprosesnya dan apa hasilnya. Semua ini sepenuhnya ditentukan, mis. bermain di mana saja, bahkan di JS di halaman web. Jika kita berbicara tentang Ethereum, maka setiap test case sangat mudah ditulis dalam JavaScript yang nyaman, termasuk parameter fuzzing, dan bekerja dengan baik di mana pun ada Node.js.
Semua kata-kata indah ini, bagaimanapun, tidak boleh membuat para pengembang rileks, karena, sebagaimana disebutkan di atas, kesalahan paling serius adalah logis, dan bagi mereka determinisme eksekusi adalah properti ortogonal.
Lingkungan untuk perakitan kontrak
Untuk menulis artikel, saya mengambil kontrak eksperimental lama untuk memesan rumah dari perancang Smartz: https://github.com/smartzplatform/constructor-eth-booking . Kontrak memungkinkan Anda untuk membuat catatan objek (apartemen atau kamar hotel), menetapkan harga dan tanggal pengiriman, setelah kontrak menunggu pembayaran dan, jika diterima, memperbaiki tindakan pemesanan, menjaga dana pada saldo sampai tamu memasuki ruangan dan tidak akan mengkonfirmasi entri. Pada titik ini, pemilik ruangan menerima pembayaran. Kontrak pada dasarnya adalah mesin negara, negara bagian dan transisi yang dapat dilihat di Booking.sol. Kami melakukannya dengan sangat cepat, mengubahnya selama proses pengembangan dan tidak berhasil melakukan sejumlah besar tes, itu jauh dari versi baru dari kompiler dan lebih atau kurang logika internal yang kaya. Jadi mari kita lihat bagaimana analis mengatasinya, kesalahan apa yang akan mereka temukan, dan, jika perlu, kita tambahkan sendiri.
Bekerja dengan berbagai versi solc
Analisis yang berbeda harus digunakan dengan cara yang berbeda - ada yang diluncurkan dari buruh pelabuhan, yang lain menggunakan bytecode yang sudah jadi, dan auditor sendiri juga harus berurusan bukan dengan pasangan, tetapi dengan lusinan kontrak awal dengan versi berbeda dari kompiler. Oleh karena itu, versi solc
berbeda harus dapat "memulai" dengan cara yang berbeda, baik di sistem host, dan di dalam gambar buruh pelabuhan, dan di dalam truffle, jadi saya akan memberikan Anda beberapa opsi untuk peretasan kotor:
1 cara: di dalam truffle
Untuk ini, tidak ada trik yang diperlukan, karena dimulai dengan truffle versi 5.0.0, Anda dapat menentukan versi kompiler langsung di truffle.js, seperti pada diff ini .
Sekarang truffle akan mengunduh kompiler yang diperlukan dan menjalankannya. Terima kasih banyak kepada tim untuk ini, Solidity adalah bahasa yang muda, perubahan dalam bahasa itu serius, dan beralih dari satu versi ke versi lainnya tidak dapat diterima oleh auditor - dengan cara ini Anda dapat memperkenalkan kesalahan baru dan menutupi yang lama.
Metode 2: mengganti / usr / bin / solc dalam wadah buruh pelabuhan analyzer
Jika penganalisis didistribusikan dalam bentuk Dockerfile, Anda dapat menggantinya saat merakit gambar buruh pelabuhan dengan menambahkan garis ke Dockerfile yang mendapatkan versi yang diinginkan solc
langsung dari gambar, yang menariknya dari jaringan dan menggantikan / usr / bin / solc:
COPY --from=ethereum/solc:0.4.19 /usr/bin/solc /usr/bin
3 cara: mengganti / usr / bin / solc
Cara paling kotor di dahi, jika tidak ada jalan keluar sama sekali, Anda bisa mengganti biner / usr / bin / solc dengan script seperti ini (jangan lupa untuk menyimpan file asli):
#!/bin/bash # run Solidity compiler of given version, pass all parameters # you can run “SOLC_DOCKER_VERSION=0.4.20 solc --version” SOLC_DOCKER_VERSION="${SOLC_DOCKER_VERSION:-0.4.24}" docker run \ --entrypoint "" \ --tmpfs /tmp \ -v $(pwd):/project \ -v $(pwd)/node_modules:/project/node_modules \ -w /project \ ethereum/solc:$SOLC_DOCKER_VERSION \ /usr/bin/solc \ "$@"
Ini mengunduh dan cache gambar buruh pelabuhan dengan versi solc
, pergi ke direktori saat ini dan menjalankan /usr/bin/solc
dengan parameter yang dilewati. Bukan cara yang sangat baik, tapi mungkin untuk beberapa tugas itu cocok untuk Anda.
Kode rata
Sekarang mari kita cari tahu sumbernya. Tentu saja, dalam teori, autoanalyzers (terutama untuk analisis sumber statis) harus mengumpulkan kontrak, menarik semua dependensi, menggabungkan semuanya menjadi satu monolit dan menganalisanya. Tapi, seperti yang sudah saya katakan, perubahan dari versi ke versi bisa serius, dan saya selalu tersandung pada kebutuhan untuk menjatuhkan direktori tambahan ke buruh pelabuhan, mengkonfigurasinya di dalam path, dan semua ini sehingga benar menarik impor yang diperlukan. Beberapa analis memahami semuanya, yang kedua bukan, karena itu, opsi universal, agar tidak menderita melempar direktori tambahan, lebih mudah bagi analis yang memakan satu file untuk menggabungkan semuanya menjadi satu file dan hanya menganalisisnya.
Untuk melakukan ini, gunakan truffle-flattener biasa .
Ini adalah modul npm standar, digunakan sangat sederhana:
truffle-flattener contracts/Booking.sol > contracts/flattened.sol
: https://github.com/trailofbits/slither
Jika Anda perlu mengubahsuaikan perataan, Anda dapat menulis perataan sendiri, misalnya, sebelum kami menggunakan opsi berbasis python: https://github.com/mixbytes/solidity-flattener
Mari kita mulai analisisnya.
Pada contoh orang tua yang sama https://github.com/smartzplatform/constructor-eth-booking, kami melanjutkan analisis. Kontrak menunjukkan versi lama dari kompiler "0.4.20", dan saya sengaja mengambil kontrak lama untuk menyelesaikan masalah dengan kompiler. Situasi diperparah oleh fakta bahwa autoanalyzer, misalnya, mempelajari bytecode, dapat bergantung pada versi solc ini, dan di sini perbedaan versi dapat sangat mempengaruhi hasil atau bahkan menghancurkan segalanya. jadi bahkan jika Anda melakukan segalanya dengan halal menggunakan versi terbaru, Anda masih bisa menemui penganalisis yang telah disetel ke versi kompiler sebelumnya.
Mengompilasi dan menjalankan tes
Untuk memulai, cukup tarik proyek dari github dan coba kompilasi:
git clone https://github.com/smartzplatform/constructor-eth-booking.git cd constructor-eth-booking npm install truffle compile
Tentunya Anda memiliki masalah dengan versi kompiler. Selain itu, autoanalyzers juga memiliki masalah ini, jadi gunakan segala cara untuk mendapatkan kompiler 0.4.20 dan membangun proyek. Saya baru saja mendaftarkan versi yang diperlukan dari kompiler di truffle.js dan semuanya sudah dirakit seperti dijelaskan di atas.
Juga jalankan
truffle-flattener contracts/Booking.sol > contracts/flattened.sol
seperti yang ditunjukkan dalam paragraf tentang perataan, itu adalah contracts/flattened.sol
akan kami berikan untuk analisis kepada analis
Kesimpulan dari pengantar
Sekarang, setelah diratakan.sol dan kemampuan untuk menggunakan solc
versi sewenang-wenang, Anda dapat mulai menganalisis. Saya akan menghilangkan masalah dengan menjalankan truffle dan tes, ada banyak dokumentasi tentang masalah ini, atur sendiri. Tentu saja, tes harus dijalankan dan dijalankan dengan sukses. Juga, untuk memeriksa logika, auditor sering harus menambahkan tesnya sendiri, memeriksa tempat-tempat yang berpotensi bocor, misalnya, memeriksa fungsionalitas kontrak pada batas array, mencakup semua variabel dengan tes, bahkan yang dimaksudkan hanya untuk penyimpanan data, dll. Ada banyak rekomendasi, selain ini hanya produk yang dipasok oleh perusahaan kami ke pasar, jadi studi logika adalah tugas murni manusia.
Kami akan pergi ke penganalisa yang menarik dari sudut pandang kami, mencoba memasukkan kontrak kami ke dalamnya, dan kami akan secara artifisial memperkenalkan kerentanan ke dalamnya untuk mengevaluasi bagaimana penganalisa otomatis akan bereaksi terhadap mereka. Artikel selanjutnya akan dikhususkan untuk penganalisa Slither, dan secara umum, rencana tindakan kira-kira sebagai berikut:
Bagian 1. Pendahuluan. Kompilasi, perataan, versi Solidity (artikel ini)
Bagian 2. Meluncur
Bagian 3. Mythril
Bagian 4. Manticore
Bagian 5. Echidna
Bagian 6. Alat tidak dikenal 1
Bagian 7. Alat tidak dikenal 2
Set analisis ini diperoleh karena penting bagi auditor untuk dapat menggunakan berbagai jenis analisis - statis dan dinamis, dan mereka memerlukan pendekatan yang sama sekali berbeda. Tugas kita adalah mempelajari cara menggunakan alat dasar di setiap jenis analisis dan memahami yang mana yang digunakan saat itu.
Mungkin dalam proses studi terperinci, kandidat baru akan muncul untuk dipertimbangkan, atau urutan artikel akan berubah, jadi tetaplah disini. Untuk pergi ke bagian selanjutnya, "klik di sini"