Pertama, saya akan membantu Anda memahami dengan cara sederhana apa itu serangan reentrancy dan bagaimana Anda dapat mencegahnya, dan kemudian, saya akan mendalami lebih dalam contoh kode untuk menunjukkan di mana kerentanannya, apa kode penyerangnya. dan yang terpenting, saya akan menunjukkan kepada Anda metode terverifikasi terbaru untuk melindungi tidak hanya satu tetapi semua kontrak pintar di proyek Anda.

Spoiler: Jika Anda sudah mendengar tentang pengubah nonReentrant(), teruslah membaca karena Anda akan menemukan beberapa baris di bawah pengubah globalNonReentrant() dan pola check-effects-interactions.



Pada gambar di atas kita memiliki ContractA dan ContractB. Sekarang, seperti yang Anda ketahui, kontrak pintar dapat berinteraksi dengan kontrak pintar lainnya, seperti dalam kasus ini, ContractA dapat memanggil ContractB. Jadi, ide dasar masuknya kembali adalah bahwa ContractB dapat memanggil kembali ke ContractA saat ContractA masih dijalankan.

Jadi, bagaimana penyerang bisa menggunakan ini?

Di atas kita memiliki KontrakA yang memiliki 10 Eter dan kita melihat bahwa KontrakB telah menyimpan 1 Eter di KontrakA. Dalam hal ini, ContractB akan dapat menggunakan fungsi penarikan dari ContractA dan mengirim Ether kembali ke dirinya sendiri saat melewati pemeriksaan di mana saldonya lebih besar dari 0, untuk kemudian total saldonya diubah menjadi 0.



Sekarang mari kita lihat bagaimana ContractB menggunakan reentrancy untuk mengeksploitasi fungsi penarikan dan mencuri semua Ether dari ContractA. Pada dasarnya, penyerang memerlukan dua fungsi: serangan() dan fallback().

Di Solidity, fungsi fallback adalah fungsi eksternal yang tidak memiliki nama, parameter, atau nilai kembalian. Siapapun dapat memanggil fungsi fallback dengan: Memanggil fungsi yang tidak ada di dalam kontrak; Memanggil suatu fungsi tanpa meneruskan data yang diperlukan; Mengirim Ether tanpa data apa pun ke kontrak.

Cara kerja reentrancy (mari ikuti tanda panah langkah demi langkah) adalah dengan penyerang memanggil fungsi serangan() yang di dalamnya memanggil fungsi penarikan() dari ContractA. Di dalam fungsi tersebut, ia akan memverifikasi apakah saldo KontrakB lebih besar dari 0 dan jika demikian maka eksekusi akan dilanjutkan.



Karena saldo ContractB lebih besar dari 0, maka 1 Ether tersebut akan dikirim kembali dan memicu fungsi fallback. Perhatikan bahwa saat ini KontrakA memiliki 9 Eter dan KontrakB sudah memiliki 1 Eter.



Selanjutnya, ketika fungsi fallback dijalankan, fungsi penarikan KontrakA akan terpicu lagi, dan memeriksa lagi apakah saldo KontrakB lebih besar dari 0. Jika Anda memeriksa kembali gambar di atas, Anda akan melihat bahwa saldonya masih 1 Eter.



Itu berarti pemeriksaan lolos dan mengirimkan Ether lain ke ContractB, yang memicu fungsi fallback. Perhatikan bahwa karena baris di mana kita memiliki “saldo=0” tidak pernah dieksekusi, ini akan berlanjut sampai semua Ether dari KontrakA hilang.

____________

Sekarang mari kita lihat kontrak pintar di mana kita dapat mengidentifikasi reentrancy dengan kode Soliditas.



Dalam kontrak EtherStore, kita memiliki fungsi deposit() yang menyimpan dan memperbarui saldo pengirim dan kemudian fungsi withdrawAll() yang akan mengambil semua saldo yang disimpan sekaligus. Harap perhatikan penerapan withdrawAll() di mana ia memeriksa terlebih dahulu dengan wajib bahwa saldo lebih besar dari 0 dan segera setelah mengirim Eter, sekali lagi, dengan sisa akhir pembaruan saldo pengirim ke 0.



Di sini kita memiliki kontrak Serangan yang akan menggunakan reentrancy untuk menguras kontrak EtherStore. Mari kita analisis kodenya:

  • Dalam konstruktornya, penyerang akan meneruskan alamat EtherStore untuk membuat sebuah instance sehingga dapat menggunakan fungsinya.

  • Di sana kita melihat fungsi fallback() yang akan dipanggil ketika EtherStore mengirimkan Ether ke kontrak ini. Di dalamnya akan ada pemanggilan penarikan dari EtherStore selama saldo sama atau lebih besar dari 1.

  • Dan di dalam fungsi serangan() kita memiliki logika yang akan mengeksploitasi EtherStore. Seperti yang bisa kita lihat, pertama-tama kita akan memulai serangan dengan memastikan kita memiliki cukup ether, kemudian menyetor 1 ether untuk memiliki saldo lebih besar dari 0 di EtherStore dan melewati pemeriksaan sebelum mulai menarik.

Saya jelaskan di atas dalam contoh ContractA dan ContractB langkah demi langkah bagaimana kode akan dijalankan, jadi sekarang, mari kita buat ringkasan bagaimana jadinya. Pertama-tama penyerang akan memanggil serangan(), yang di dalamnya akan memanggil penarikanSemua() dari EtherStore, yang kemudian akan mengirimkan Ether ke fungsi fallback kontrak Serangan. Dan di sana ia akan memulai proses masuk kembali dan menguras saldo EtherStore.

Jadi, bagaimana kita bisa melindungi kontrak kita dari serangan masuk kembali?

Saya akan menunjukkan kepada Anda tiga teknik pencegahan untuk melindungi mereka sepenuhnya. Saya akan membahas cara mencegah masuknya kembali dalam satu fungsi, masuk kembali lintas fungsi, dan masuk kembali lintas kontrak.



Teknik pertama untuk melindungi satu fungsi adalah menggunakan pengubah yang disebut noReentrant.

Pengubah adalah jenis fungsi khusus yang Anda gunakan untuk mengubah perilaku fungsi lainnya. Pengubah memungkinkan Anda menambahkan kondisi atau fungsionalitas tambahan ke suatu fungsi tanpa harus menulis ulang seluruh fungsi.

Apa yang kita lakukan di sini adalah mengunci kontrak saat fungsi dijalankan. Dengan cara ini, variabel tersebut tidak akan dapat masuk kembali ke fungsi tunggal karena ia harus menelusuri kode fungsi tersebut dan kemudian mengubah variabel status terkunci menjadi false agar dapat meneruskan lagi pemeriksaan yang dilakukan pada persyaratan.

____________



Teknik kedua adalah dengan memanfaatkan pola Checks-Effects-Interactions yang akan melindungi kontrak kita dari masuknya kembali lintas fungsi. Dapatkah Anda melihat dalam kontrak EtherStore yang diperbarui di atas apa yang berubah?

Untuk mendalami pola Interaksi-Efek, saya sarankan untuk membaca https://fravoll.github.io/solidity-patterns/checks_effects_interactions.html





Di atas kita melihat perbandingan antara kode yang rentan dari gambar di sebelah kiri di mana saldo diperbarui setelah pengiriman Ether, yang seperti terlihat di atas berpotensi tidak akan pernah tercapai, dan di sebelah kanan yang dilakukan adalah memindahkan saldo[ msg.sender] = 0 (atau efek) tepat setelah require(bal > 0) (centang) tetapi sebelum mengirim ether (interaksi).

Dengan cara ini kami akan memastikan bahwa meskipun fungsi lain mengakses withdrawAll(), kontrak ini akan terlindungi dari penyerang karena saldo akan selalu diperbarui sebelum mengirim Ether.

Pola dibuat oleh https://twitter.com/GMX_IO

Teknik ketiga yang akan saya tunjukkan kepada Anda adalah membuat kontrak GlobalReentrancyGuard untuk melindungi dari masuknya kembali lintas kontrak. Penting untuk dipahami bahwa hal ini berlaku untuk proyek dengan banyak kontrak yang berinteraksi satu sama lain.

Idenya di sini sama dengan pengubah noReentrant yang sudah saya jelaskan pada teknik pertama, ia memasukkan pengubah, memperbarui variabel untuk mengunci kontrak dan tidak membuka kuncinya hingga kode tidak selesai. Perbedaan besarnya di sini adalah kita menggunakan variabel yang disimpan dalam kontrak terpisah yang digunakan sebagai tempat untuk memeriksa apakah fungsi tersebut dimasukkan atau tidak.







Di sini saya telah membuat contoh tanpa kode aktual dan hanya dengan nama fungsi sebagai referensi untuk memahami idenya karena, dari pengalaman saya, ini dapat membantu memvisualisasikan situasi lebih dari sekadar menulisnya dengan kata-kata.

Di sini, penyerang akan memanggil fungsi dalam kontrak ScheduledTransfer yang setelah memenuhi ketentuan, ia akan mengirimkan Ether yang ditentukan ke kontrak AttackTransfer yang, oleh karena itu, akan memasuki fungsi fallback dan karenanya “membatalkan” transaksi dari titik kontrak ScheduledTransfer melihat namun menerima Eter. Dan dengan cara ini akan mulai terlihat hingga menguras semua Eter dari ScheduledTransfer.

Nah, dengan menggunakan GlobalReentrancyGuard yang telah saya sebutkan di atas, skenario serangan seperti itu akan dihindari.

__________________

Twitter @TheBlockChainer untuk menemukan pembaruan harian lainnya tentang Kontrak Cerdas, Keamanan Web3, Soliditas, Audit kontrak pintar, dan banyak lagi.

__________________