Lompat ke konten utama
Change page

Keamanan kontrak pintar

Pembaruan terakhir halaman: 26 Februari 2026

Kontrak pintar sangat fleksibel, dan mampu mengendalikan sejumlah besar nilai dan data, sambil menjalankan logika yang tetap berdasarkan kode yang disebarkan di blockchain. Hal ini telah menciptakan ekosistem aplikasi terdesentralisasi dan tanpa kepercayaan (trustless) yang dinamis yang memberikan banyak keuntungan dibandingkan sistem lama. Mereka juga mewakili peluang bagi penyerang yang mencari keuntungan dengan mengeksploitasi kerentanan dalam kontrak pintar.

Blockchain publik, seperti Ethereum, semakin memperumit masalah pengamanan kontrak pintar. Kode kontrak yang disebarkan biasanya tidak dapat diubah untuk menambal celah keamanan, sementara aset yang dicuri dari kontrak pintar sangat sulit dilacak dan sebagian besar tidak dapat dipulihkan karena sifatnya yang tetap.

Meskipun angkanya bervariasi, diperkirakan bahwa jumlah total nilai yang dicuri atau hilang akibat cacat keamanan dalam kontrak pintar dengan mudah mencapai lebih dari $1 miliar. Ini termasuk insiden tingkat tinggi, seperti peretasan DAO (opens in a new tab) (3,6 juta ETH dicuri, bernilai lebih dari $1 miliar pada harga saat ini), peretasan dompet multi tanda tangan Parity (opens in a new tab) ($30 juta hilang oleh peretas), dan masalah dompet beku Parity (opens in a new tab) (lebih dari $300 juta dalam bentuk ETH terkunci selamanya).

Masalah-masalah yang disebutkan di atas membuatnya sangat penting bagi pengembang untuk menginvestasikan upaya dalam membangun kontrak pintar yang aman, kuat, dan tangguh. Keamanan kontrak pintar adalah urusan yang serius, dan setiap pengembang akan sangat terbantu jika mempelajarinya. Panduan ini akan mencakup pertimbangan keamanan untuk pengembang Ethereum dan menjelajahi sumber daya untuk meningkatkan keamanan kontrak pintar.

Prasyarat

Pastikan Anda familier dengan dasar-dasar pengembangan kontrak pintar sebelum menangani keamanan.

Panduan untuk membangun kontrak pintar Ethereum yang aman

1. Rancang kontrol akses yang tepat

Dalam kontrak pintar, fungsi yang ditandai public atau external dapat dipanggil oleh akun yang dimiliki secara eksternal (EOA) atau akun kontrak mana pun. Menentukan visibilitas publik untuk fungsi diperlukan jika Anda ingin orang lain berinteraksi dengan kontrak Anda. Namun, fungsi yang ditandai private hanya dapat dipanggil oleh fungsi di dalam kontrak pintar tersebut, dan bukan oleh akun eksternal. Memberikan akses ke fungsi kontrak kepada setiap peserta jaringan dapat menyebabkan masalah, terutama jika itu berarti siapa pun dapat melakukan operasi sensitif (misalnya, melakukan mint token baru).

Untuk mencegah penggunaan fungsi kontrak pintar yang tidak sah, penting untuk menerapkan kontrol akses yang aman. Mekanisme kontrol akses membatasi kemampuan untuk menggunakan fungsi tertentu dalam kontrak pintar hanya untuk entitas yang disetujui, seperti akun yang bertanggung jawab untuk mengelola kontrak. Pola Ownable dan kontrol berbasis peran (role-based control) adalah dua pola yang berguna untuk menerapkan kontrol akses dalam kontrak pintar:

Pola Ownable

Dalam pola Ownable, sebuah alamat ditetapkan sebagai “pemilik” (owner) kontrak selama proses pembuatan kontrak. Fungsi yang dilindungi diberi pengubah (modifier) OnlyOwner, yang memastikan kontrak mengautentikasi identitas alamat pemanggil sebelum mengeksekusi fungsi. Panggilan ke fungsi yang dilindungi dari alamat lain selain pemilik kontrak akan selalu dikembalikan (revert), mencegah akses yang tidak diinginkan.

Kontrol akses berbasis peran

Mendaftarkan satu alamat sebagai Owner dalam kontrak pintar memunculkan risiko sentralisasi dan mewakili titik kegagalan tunggal (single point-of-failure). Jika kunci akun pemilik disusupi, penyerang dapat menyerang kontrak yang dimiliki. Inilah sebabnya mengapa menggunakan pola kontrol akses berbasis peran dengan beberapa akun administratif mungkin merupakan pilihan yang lebih baik.

Dalam kontrol akses berbasis peran, akses ke fungsi sensitif didistribusikan di antara sekumpulan peserta tepercaya. Misalnya, satu akun mungkin bertanggung jawab untuk melakukan mint token, sementara akun lain melakukan peningkatan atau menjeda kontrak. Mendesentralisasi kontrol akses dengan cara ini menghilangkan titik kegagalan tunggal dan mengurangi asumsi kepercayaan bagi pengguna.

Menggunakan dompet multi tanda tangan

Pendekatan lain untuk menerapkan kontrol akses yang aman adalah menggunakan akun multi tanda tangan untuk mengelola kontrak. Tidak seperti EOA biasa, akun multi tanda tangan dimiliki oleh beberapa entitas dan memerlukan tanda tangan dari jumlah minimum akun—katakanlah 3-dari-5—untuk mengeksekusi transaksi.

Menggunakan multi tanda tangan untuk kontrol akses memberikan lapisan keamanan ekstra karena tindakan pada kontrak target memerlukan persetujuan dari beberapa pihak. Ini sangat berguna jika penggunaan pola Ownable diperlukan, karena membuatnya lebih sulit bagi penyerang atau orang dalam yang jahat untuk memanipulasi fungsi kontrak sensitif untuk tujuan jahat.

2. Gunakan pernyataan require(), assert(), dan revert() untuk menjaga operasi kontrak

Seperti yang disebutkan, siapa pun dapat memanggil fungsi publik dalam kontrak pintar Anda setelah disebarkan di blockchain. Karena Anda tidak dapat mengetahui sebelumnya bagaimana akun eksternal akan berinteraksi dengan kontrak, sangat ideal untuk menerapkan pengamanan internal terhadap operasi bermasalah sebelum penyebaran. Anda dapat menegakkan perilaku yang benar dalam kontrak pintar dengan menggunakan pernyataan require(), assert(), dan revert() untuk memicu pengecualian dan mengembalikan perubahan status jika eksekusi gagal memenuhi persyaratan tertentu.

require(): require didefinisikan di awal fungsi dan memastikan kondisi yang telah ditentukan terpenuhi sebelum fungsi yang dipanggil dieksekusi. Pernyataan require dapat digunakan untuk memvalidasi input pengguna, memeriksa variabel status, atau mengautentikasi identitas akun pemanggil sebelum melanjutkan dengan suatu fungsi.

assert(): assert() digunakan untuk mendeteksi kesalahan internal dan memeriksa pelanggaran “invarian” dalam kode Anda. Invarian adalah pernyataan logis tentang status kontrak yang harus selalu benar untuk semua eksekusi fungsi. Contoh invarian adalah total pasokan maksimum atau saldo dari kontrak token. Menggunakan assert() memastikan bahwa kontrak Anda tidak pernah mencapai status rentan, dan jika ya, semua perubahan pada variabel status akan dibatalkan (rolled back).

revert(): revert() dapat digunakan dalam pernyataan if-else yang memicu pengecualian jika kondisi yang diperlukan tidak terpenuhi. Contoh kontrak di bawah ini menggunakan revert() untuk menjaga eksekusi fungsi:

1pragma solidity ^0.8.4;
2
3contract VendingMachine {
4 address owner;
5 error Unauthorized();
6 function buy(uint amount) public payable {
7 if (amount > msg.value / 2 ether)
8 revert("Not enough Ether provided.");
9 // Perform the purchase.
10 }
11 function withdraw() public {
12 if (msg.sender != owner)
13 revert Unauthorized();
14
15 payable(msg.sender).transfer(address(this).balance);
16 }
17}
Tampilkan semua

3. Uji kontrak pintar dan verifikasi kebenaran kode

Sifat tetap dari kode yang berjalan di Mesin Virtual Ethereum berarti kontrak pintar menuntut tingkat penilaian kualitas yang lebih tinggi selama fase pengembangan. Menguji kontrak Anda secara ekstensif dan mengamatinya untuk hasil yang tidak terduga akan sangat meningkatkan keamanan dan melindungi pengguna Anda dalam jangka panjang.

Metode yang biasa dilakukan adalah menulis pengujian unit kecil menggunakan data tiruan (mock data) yang diharapkan diterima kontrak dari pengguna. Pengujian unit baik untuk menguji fungsionalitas fungsi tertentu dan memastikan kontrak pintar berfungsi seperti yang diharapkan.

Sayangnya, pengujian unit kurang efektif untuk meningkatkan keamanan kontrak pintar jika digunakan secara terpisah. Pengujian unit mungkin membuktikan suatu fungsi dieksekusi dengan benar untuk data tiruan, tetapi pengujian unit hanya seefektif pengujian yang ditulis. Hal ini menyulitkan untuk mendeteksi kasus ekstrem (edge cases) yang terlewat dan kerentanan yang dapat merusak keamanan kontrak pintar Anda.

Pendekatan yang lebih baik adalah menggabungkan pengujian unit dengan pengujian berbasis properti yang dilakukan menggunakan analisis statis dan dinamis. Analisis statis bergantung pada representasi tingkat rendah, seperti grafik aliran kontrol (opens in a new tab) dan pohon sintaksis abstrak (opens in a new tab) untuk menganalisis status program dan jalur eksekusi yang dapat dicapai. Sementara itu, teknik analisis dinamis, seperti fuzzing kontrak pintar (opens in a new tab), mengeksekusi kode kontrak dengan nilai input acak untuk mendeteksi operasi yang melanggar properti keamanan.

Verifikasi formal adalah teknik lain untuk memverifikasi properti keamanan dalam kontrak pintar. Tidak seperti pengujian biasa, verifikasi formal dapat secara meyakinkan membuktikan tidak adanya kesalahan dalam kontrak pintar. Hal ini dicapai dengan membuat spesifikasi formal yang menangkap properti keamanan yang diinginkan dan membuktikan bahwa model formal dari kontrak mematuhi spesifikasi ini.

4. Mintalah tinjauan independen atas kode Anda

Setelah menguji kontrak Anda, ada baiknya meminta orang lain untuk memeriksa kode sumber untuk masalah keamanan apa pun. Pengujian tidak akan mengungkap setiap kelemahan dalam kontrak pintar, tetapi mendapatkan tinjauan independen meningkatkan kemungkinan menemukan kerentanan.

Audit

Menugaskan audit kontrak pintar adalah salah satu cara untuk melakukan tinjauan kode independen. Auditor memainkan peran penting dalam memastikan bahwa kontrak pintar aman dan bebas dari cacat kualitas serta kesalahan desain.

Meskipun demikian, Anda harus menghindari memperlakukan audit sebagai solusi ajaib (silver bullet). Audit kontrak pintar tidak akan menangkap setiap bug dan sebagian besar dirancang untuk memberikan putaran tinjauan tambahan, yang dapat membantu mendeteksi masalah yang terlewatkan oleh pengembang selama pengembangan dan pengujian awal. Anda juga harus mengikuti praktik terbaik untuk bekerja dengan auditor, seperti mendokumentasikan kode dengan benar dan menambahkan komentar sebaris (inline comments), untuk memaksimalkan manfaat dari audit kontrak pintar.

Bug bounty

Menyiapkan program bug bounty adalah pendekatan lain untuk menerapkan tinjauan kode eksternal. Bug bounty adalah hadiah finansial yang diberikan kepada individu (biasanya peretas topi putih/whitehat hackers) yang menemukan kerentanan dalam suatu aplikasi.

Jika digunakan dengan benar, bug bounty memberikan insentif kepada anggota komunitas peretas untuk memeriksa kode Anda dari kelemahan kritis. Contoh di dunia nyata adalah “bug uang tak terbatas” yang akan memungkinkan penyerang membuat ether dalam jumlah tak terbatas di Optimism (opens in a new tab), sebuah protokol layer 2 yang berjalan di Ethereum. Untungnya, seorang peretas topi putih menemukan kelemahan tersebut (opens in a new tab) dan memberi tahu tim, serta mendapatkan bayaran besar dalam prosesnya (opens in a new tab).

Strategi yang berguna adalah menetapkan pembayaran program bug bounty sebanding dengan jumlah dana yang dipertaruhkan. Dideskripsikan sebagai “scaling bug bounty (opens in a new tab)”, pendekatan ini memberikan insentif finansial bagi individu untuk secara bertanggung jawab mengungkapkan kerentanan alih-alih mengeksploitasinya.

5. Ikuti praktik terbaik selama pengembangan kontrak pintar

Keberadaan audit dan bug bounty tidak membebaskan tanggung jawab Anda untuk menulis kode berkualitas tinggi. Keamanan kontrak pintar yang baik dimulai dengan mengikuti proses desain dan pengembangan yang tepat:

  • Simpan semua kode dalam sistem kontrol versi, seperti git

  • Lakukan semua modifikasi kode melalui pull request

  • Pastikan pull request memiliki setidaknya satu peninjau independen—jika Anda bekerja sendiri dalam sebuah proyek, pertimbangkan untuk mencari pengembang lain dan bertukar tinjauan kode

  • Gunakan lingkungan pengembangan untuk menguji, mengompilasi, dan menyebarkan kontrak pintar

  • Jalankan kode Anda melalui alat analisis kode dasar, seperti Cyfrin Aderyn (opens in a new tab), Mythril, dan Slither. Idealnya, Anda harus melakukan ini sebelum setiap pull request digabungkan dan membandingkan perbedaan dalam output

  • Pastikan kode Anda dikompilasi tanpa kesalahan, dan kompiler Solidity tidak mengeluarkan peringatan

  • Dokumentasikan kode Anda dengan benar (menggunakan NatSpec (opens in a new tab)) dan jelaskan detail tentang arsitektur kontrak dalam bahasa yang mudah dipahami. Ini akan memudahkan orang lain untuk mengaudit dan meninjau kode Anda.

6. Terapkan rencana pemulihan bencana yang kuat

Merancang kontrol akses yang aman, menerapkan pengubah fungsi, dan saran lainnya dapat meningkatkan keamanan kontrak pintar, tetapi tidak dapat mengesampingkan kemungkinan eksploitasi berbahaya. Membangun kontrak pintar yang aman membutuhkan “persiapan untuk kegagalan” dan memiliki rencana cadangan untuk merespons serangan secara efektif. Rencana pemulihan bencana yang tepat akan menggabungkan beberapa atau semua komponen berikut:

Peningkatan kontrak

Meskipun kontrak pintar Ethereum secara default bersifat tetap, dimungkinkan untuk mencapai tingkat mutabilitas tertentu dengan menggunakan pola peningkatan (upgrade patterns). Meningkatkan kontrak diperlukan dalam kasus di mana kelemahan kritis membuat kontrak lama Anda tidak dapat digunakan dan menyebarkan logika baru adalah opsi yang paling layak.

Mekanisme peningkatan kontrak bekerja secara berbeda, tetapi “pola proksi” (proxy pattern) adalah salah satu pendekatan yang lebih populer untuk meningkatkan kontrak pintar. Pola proksi (opens in a new tab) membagi status dan logika aplikasi di antara dua kontrak. Kontrak pertama (disebut 'kontrak proksi') menyimpan variabel status (misalnya, saldo pengguna), sedangkan kontrak kedua (disebut 'kontrak logika') menyimpan kode untuk mengeksekusi fungsi kontrak.

Akun berinteraksi dengan kontrak proksi, yang mengirimkan semua panggilan fungsi ke kontrak logika menggunakan panggilan tingkat rendah delegatecall() (opens in a new tab). Tidak seperti panggilan pesan biasa, delegatecall() memastikan kode yang berjalan di alamat kontrak logika dieksekusi dalam konteks kontrak pemanggil. Ini berarti kontrak logika akan selalu menulis ke penyimpanan proksi (bukan penyimpanannya sendiri) dan nilai asli dari msg.sender serta msg.value dipertahankan.

Mendelegasikan panggilan ke kontrak logika memerlukan penyimpanan alamatnya di penyimpanan kontrak proksi. Oleh karena itu, meningkatkan logika kontrak hanyalah masalah menyebarkan kontrak logika lain dan menyimpan alamat baru di kontrak proksi. Karena panggilan berikutnya ke kontrak proksi secara otomatis dirutekan ke kontrak logika baru, Anda akan “meningkatkan” kontrak tanpa benar-benar memodifikasi kodenya.

Lebih lanjut tentang peningkatan kontrak.

Penghentian darurat

Seperti yang disebutkan, audit dan pengujian ekstensif tidak mungkin menemukan semua bug dalam kontrak pintar. Jika kerentanan muncul dalam kode Anda setelah penyebaran, menambalnya tidak mungkin dilakukan karena Anda tidak dapat mengubah kode yang berjalan di alamat kontrak. Selain itu, mekanisme peningkatan (misalnya, pola proksi) mungkin memerlukan waktu untuk diterapkan (sering kali memerlukan persetujuan dari berbagai pihak), yang hanya memberi penyerang lebih banyak waktu untuk menyebabkan lebih banyak kerusakan.

Opsi nuklir adalah menerapkan fungsi “penghentian darurat” (emergency stop) yang memblokir panggilan ke fungsi yang rentan dalam suatu kontrak. Penghentian darurat biasanya terdiri dari komponen-komponen berikut:

  1. Variabel Boolean global yang menunjukkan apakah kontrak pintar dalam status berhenti atau tidak. Variabel ini diatur ke false saat menyiapkan kontrak, tetapi akan berubah menjadi true setelah kontrak dihentikan.

  2. Fungsi yang mereferensikan variabel Boolean dalam eksekusinya. Fungsi tersebut dapat diakses saat kontrak pintar tidak dihentikan, dan menjadi tidak dapat diakses saat fitur penghentian darurat dipicu.

  3. Entitas yang memiliki akses ke fungsi penghentian darurat, yang mengatur variabel Boolean ke true. Untuk mencegah tindakan jahat, panggilan ke fungsi ini dapat dibatasi pada alamat tepercaya (misalnya, pemilik kontrak).

Setelah kontrak mengaktifkan penghentian darurat, fungsi tertentu tidak akan dapat dipanggil. Hal ini dicapai dengan membungkus fungsi yang dipilih dalam pengubah yang mereferensikan variabel global. Di bawah ini adalah contoh (opens in a new tab) yang menjelaskan implementasi pola ini dalam kontrak:

1// This code has not been professionally audited and makes no promises about safety or correctness. Use at your own risk. // Kode ini belum diaudit secara profesional dan tidak memberikan jaminan tentang keamanan atau kebenaran. Gunakan dengan risiko Anda sendiri.
2
3contract EmergencyStop {
4
5 bool isStopped = false;
6
7 modifier stoppedInEmergency {
8 require(!isStopped);
9 _;
10 }
11
12 modifier onlyWhenStopped {
13 require(isStopped);
14 _;
15 }
16
17 modifier onlyAuthorized {
18 // Check for authorization of msg.sender here // Periksa otorisasi msg.sender di sini
19 _;
20 }
21
22 function stopContract() public onlyAuthorized {
23 isStopped = true;
24 }
25
26 function resumeContract() public onlyAuthorized {
27 isStopped = false;
28 }
29
30 function deposit() public payable stoppedInEmergency {
31 // Deposit logic happening here // Logika deposit terjadi di sini
32 }
33
34 function emergencyWithdraw() public onlyWhenStopped {
35 // Emergency withdraw happening here // Penarikan darurat terjadi di sini
36 }
37}
Tampilkan semua

Contoh ini menunjukkan fitur dasar dari penghentian darurat:

  • isStopped adalah Boolean yang mengevaluasi ke false pada awalnya dan true saat kontrak memasuki mode darurat.

  • Pengubah fungsi onlyWhenStopped dan stoppedInEmergency memeriksa variabel isStopped. stoppedInEmergency digunakan untuk mengontrol fungsi yang seharusnya tidak dapat diakses saat kontrak rentan (misalnya, deposit()). Panggilan ke fungsi-fungsi ini hanya akan dikembalikan (revert).

onlyWhenStopped digunakan untuk fungsi yang seharusnya dapat dipanggil selama keadaan darurat (misalnya, emergencyWithdraw()). Fungsi semacam itu dapat membantu menyelesaikan situasi, karenanya pengecualiannya dari daftar “fungsi yang dibatasi”.

Menggunakan fungsionalitas penghentian darurat memberikan solusi sementara yang efektif untuk menangani kerentanan serius dalam kontrak pintar Anda. Namun, hal ini meningkatkan kebutuhan pengguna untuk memercayai pengembang agar tidak mengaktifkannya untuk alasan kepentingan pribadi. Untuk tujuan ini, mendesentralisasi kontrol penghentian darurat baik dengan menundukkannya pada mekanisme pemungutan suara onchain, timelock, atau persetujuan dari dompet multi tanda tangan adalah solusi yang memungkinkan.

Pemantauan peristiwa

Peristiwa (Events) (opens in a new tab) memungkinkan Anda melacak panggilan ke fungsi kontrak pintar dan memantau perubahan pada variabel status. Sangat ideal untuk memprogram kontrak pintar Anda agar memancarkan peristiwa setiap kali ada pihak yang mengambil tindakan kritis terhadap keselamatan (misalnya, menarik dana).

Mencatat peristiwa dan memantaunya secara offchain memberikan wawasan tentang operasi kontrak dan membantu penemuan tindakan jahat dengan lebih cepat. Ini berarti tim Anda dapat merespons peretasan dengan lebih cepat dan mengambil tindakan untuk memitigasi dampak pada pengguna, seperti menjeda fungsi atau melakukan peningkatan.

Anda juga dapat memilih alat pemantauan siap pakai yang secara otomatis meneruskan peringatan setiap kali seseorang berinteraksi dengan kontrak Anda. Alat-alat ini akan memungkinkan Anda membuat peringatan khusus berdasarkan pemicu yang berbeda, seperti volume transaksi, frekuensi panggilan fungsi, atau fungsi spesifik yang terlibat. Misalnya, Anda dapat memprogram peringatan yang masuk saat jumlah yang ditarik dalam satu transaksi melewati ambang batas tertentu.

7. Rancang sistem tata kelola yang aman

Anda mungkin ingin mendesentralisasi aplikasi Anda dengan menyerahkan kendali kontrak pintar inti kepada anggota komunitas. Dalam hal ini, sistem kontrak pintar akan mencakup modul tata kelola—sebuah mekanisme yang memungkinkan anggota komunitas untuk menyetujui tindakan administratif melalui sistem tata kelola onchain. Misalnya, proposal untuk meningkatkan kontrak proksi ke implementasi baru dapat dipilih oleh pemegang token.

Tata kelola terdesentralisasi dapat bermanfaat, terutama karena menyelaraskan kepentingan pengembang dan pengguna akhir. Meskipun demikian, mekanisme tata kelola kontrak pintar dapat menimbulkan risiko baru jika diterapkan secara tidak benar. Skenario yang masuk akal adalah jika penyerang memperoleh kekuatan suara yang sangat besar (diukur dalam jumlah token yang dipegang) dengan mengambil flash loan dan mendorong proposal jahat.

Salah satu cara untuk mencegah masalah terkait tata kelola onchain adalah dengan menggunakan timelock (opens in a new tab). Timelock mencegah kontrak pintar mengeksekusi tindakan tertentu hingga jumlah waktu tertentu berlalu. Strategi lain termasuk menetapkan “bobot suara” untuk setiap token berdasarkan berapa lama token tersebut telah dikunci, atau mengukur kekuatan suara dari suatu alamat pada periode historis (misalnya, 2-3 blok di masa lalu) alih-alih blok saat ini. Kedua metode tersebut mengurangi kemungkinan mengumpulkan kekuatan suara dengan cepat untuk mengayunkan suara onchain.

Lebih lanjut tentang merancang sistem tata kelola yang aman (opens in a new tab), berbagai mekanisme pemungutan suara di DAO (opens in a new tab), dan vektor serangan DAO umum yang memanfaatkan DeFi (opens in a new tab) di tautan yang dibagikan.

8. Kurangi kompleksitas dalam kode seminimal mungkin

Pengembang perangkat lunak tradisional akrab dengan prinsip KISS (“keep it simple, stupid”), yang menyarankan untuk tidak memasukkan kompleksitas yang tidak perlu ke dalam desain perangkat lunak. Ini mengikuti pemikiran yang telah lama dipegang bahwa “sistem yang kompleks gagal dengan cara yang kompleks” dan lebih rentan terhadap kesalahan yang merugikan.

Menjaga segala sesuatunya tetap sederhana sangat penting saat menulis kontrak pintar, mengingat kontrak pintar berpotensi mengendalikan nilai dalam jumlah besar. Tip untuk mencapai kesederhanaan saat menulis kontrak pintar adalah menggunakan kembali pustaka yang ada, seperti OpenZeppelin Contracts (opens in a new tab), jika memungkinkan. Karena pustaka ini telah diaudit dan diuji secara ekstensif oleh pengembang, menggunakannya mengurangi kemungkinan memasukkan bug dengan menulis fungsionalitas baru dari awal.

Saran umum lainnya adalah menulis fungsi kecil dan menjaga kontrak tetap modular dengan membagi logika bisnis di beberapa kontrak. Menulis kode yang lebih sederhana tidak hanya mengurangi permukaan serangan dalam kontrak pintar, tetapi juga memudahkan untuk menalar kebenaran sistem secara keseluruhan dan mendeteksi kemungkinan kesalahan desain sejak dini.

9. Bertahan terhadap kerentanan kontrak pintar yang umum

Reentrancy

EVM tidak mengizinkan konkurensi, yang berarti dua kontrak yang terlibat dalam panggilan pesan tidak dapat berjalan secara bersamaan. Panggilan eksternal menjeda eksekusi dan memori kontrak pemanggil hingga panggilan kembali, pada titik mana eksekusi dilanjutkan secara normal. Proses ini dapat secara formal digambarkan sebagai mentransfer aliran kontrol (opens in a new tab) ke kontrak lain.

Meskipun sebagian besar tidak berbahaya, mentransfer aliran kontrol ke kontrak yang tidak tepercaya dapat menyebabkan masalah, seperti reentrancy. Serangan reentrancy terjadi ketika kontrak jahat memanggil kembali ke kontrak yang rentan sebelum pemanggilan fungsi asli selesai. Jenis serangan ini paling baik dijelaskan dengan sebuah contoh.

Pertimbangkan kontrak pintar sederhana ('Victim') yang memungkinkan siapa saja untuk menyetor dan menarik ether:

1// This contract is vulnerable. Do not use in production // Kontrak ini rentan. Jangan gunakan di produksi
2
3contract Victim {
4 mapping (address => uint256) public balances;
5
6 function deposit() external payable {
7 balances[msg.sender] += msg.value;
8 }
9
10 function withdraw() external {
11 uint256 amount = balances[msg.sender];
12 (bool success, ) = msg.sender.call.value(amount)("");
13 require(success);
14 balances[msg.sender] = 0;
15 }
16}
Tampilkan semua

Kontrak ini mengekspos fungsi withdraw() untuk memungkinkan pengguna menarik ETH yang sebelumnya disetorkan ke dalam kontrak. Saat memproses penarikan, kontrak melakukan operasi berikut:

  1. Memeriksa saldo ETH pengguna
  2. Mengirim dana ke alamat pemanggil
  3. Mengatur ulang saldo mereka menjadi 0, mencegah penarikan tambahan dari pengguna

Fungsi withdraw() dalam kontrak Victim mengikuti pola “checks-interactions-effects” (pemeriksaan-interaksi-efek). Fungsi ini memeriksa apakah kondisi yang diperlukan untuk eksekusi terpenuhi (yaitu, pengguna memiliki saldo ETH positif) dan melakukan interaksi dengan mengirimkan ETH ke alamat pemanggil, sebelum menerapkan efek dari transaksi (yaitu, mengurangi saldo pengguna).

Jika withdraw() dipanggil dari akun yang dimiliki secara eksternal (EOA), fungsi tersebut dieksekusi seperti yang diharapkan: msg.sender.call.value() mengirimkan ETH ke pemanggil. Namun, jika msg.sender adalah akun kontrak pintar yang memanggil withdraw(), mengirimkan dana menggunakan msg.sender.call.value() juga akan memicu kode yang disimpan di alamat tersebut untuk berjalan.

Bayangkan ini adalah kode yang disebarkan di alamat kontrak:

1 contract Attacker {
2 function beginAttack() external payable {
3 Victim(victim_address).deposit.value(1 ether)();
4 Victim(victim_address).withdraw();
5 }
6
7 function() external payable {
8 if (gasleft() > 40000) {
9 Victim(victim_address).withdraw();
10 }
11 }
12}
Tampilkan semua

Kontrak ini dirancang untuk melakukan tiga hal:

  1. Menerima setoran dari akun lain (kemungkinan EOA penyerang)
  2. Menyetorkan 1 ETH ke dalam kontrak Victim
  3. Menarik 1 ETH yang disimpan dalam kontrak pintar

Tidak ada yang salah di sini, kecuali bahwa Attacker memiliki fungsi lain yang memanggil withdraw() di Victim lagi jika gas yang tersisa dari msg.sender.call.value yang masuk lebih dari 40.000. Ini memberi Attacker kemampuan untuk masuk kembali ke Victim dan menarik lebih banyak dana sebelum pemanggilan pertama withdraw selesai. Siklusnya terlihat seperti ini:

1- Attacker's EOA calls `Attacker.beginAttack()` with 1 ETH
2- `Attacker.beginAttack()` deposits 1 ETH into `Victim`
3- `Attacker` calls `withdraw() in `Victim`
4- `Victim` checks `Attacker`’s balance (1 ETH)
5- `Victim` sends 1 ETH to `Attacker` (which triggers the default function)
6- `Attacker` calls `Victim.withdraw()` again (note that `Victim` hasn’t reduced `Attacker`’s balance from the first withdrawal)
7- `Victim` checks `Attacker`’s balance (which is still 1 ETH because it hasn’t applied the effects of the first call)
8- `Victim` sends 1 ETH to `Attacker` (which triggers the default function and allows `Attacker` to reenter the `withdraw` function)
9- The process repeats until `Attacker` runs out of gas, at which point `msg.sender.call.value` returns without triggering additional withdrawals
10- `Victim` finally applies the results of the first transaction (and subsequent ones) to its state, so `Attacker`’s balance is set to 0
Tampilkan semua

Ringkasannya adalah karena saldo pemanggil tidak diatur ke 0 hingga eksekusi fungsi selesai, pemanggilan berikutnya akan berhasil dan memungkinkan pemanggil untuk menarik saldo mereka beberapa kali. Jenis serangan ini dapat digunakan untuk menguras dana kontrak pintar, seperti yang terjadi pada peretasan DAO tahun 2016 (opens in a new tab). Serangan reentrancy masih menjadi masalah kritis untuk kontrak pintar saat ini seperti yang ditunjukkan oleh daftar publik eksploitasi reentrancy (opens in a new tab).

Cara mencegah serangan reentrancy

Pendekatan untuk menangani reentrancy adalah mengikuti pola checks-effects-interactions (opens in a new tab). Pola ini mengurutkan eksekusi fungsi sedemikian rupa sehingga kode yang melakukan pemeriksaan yang diperlukan sebelum melanjutkan eksekusi didahulukan, diikuti oleh kode yang memanipulasi status kontrak, dengan kode yang berinteraksi dengan kontrak lain atau EOA berada di urutan terakhir.

Pola checks-effect-interaction digunakan dalam versi revisi dari kontrak Victim yang ditunjukkan di bawah ini:

1contract NoLongerAVictim {
2 function withdraw() external {
3 uint256 amount = balances[msg.sender];
4 balances[msg.sender] = 0;
5 (bool success, ) = msg.sender.call.value(amount)("");
6 require(success);
7 }
8}

Kontrak ini melakukan pemeriksaan pada saldo pengguna, menerapkan efek dari fungsi withdraw() (dengan mengatur ulang saldo pengguna menjadi 0), dan melanjutkan untuk melakukan interaksi (mengirimkan ETH ke alamat pengguna). Ini memastikan kontrak memperbarui penyimpanannya sebelum panggilan eksternal, menghilangkan kondisi re-entrancy yang memungkinkan serangan pertama. Kontrak Attacker masih dapat memanggil kembali ke NoLongerAVictim, tetapi karena balances[msg.sender] telah diatur ke 0, penarikan tambahan akan memunculkan kesalahan.

Opsi lain adalah menggunakan kunci pengecualian bersama (mutual exclusion lock, umumnya digambarkan sebagai "mutex") yang mengunci sebagian status kontrak hingga pemanggilan fungsi selesai. Ini diimplementasikan menggunakan variabel Boolean yang diatur ke true sebelum fungsi dieksekusi dan kembali ke false setelah pemanggilan selesai. Seperti yang terlihat pada contoh di bawah ini, menggunakan mutex melindungi fungsi dari panggilan rekursif saat pemanggilan asli masih diproses, yang secara efektif menghentikan reentrancy.

1pragma solidity ^0.7.0;
2
3contract MutexPattern {
4 bool locked = false;
5 mapping(address => uint256) public balances;
6
7 modifier noReentrancy() {
8 require(!locked, "Blocked from reentrancy.");
9 locked = true;
10 _;
11 locked = false;
12 }
13 // This function is protected by a mutex, so reentrant calls from within `msg.sender.call` cannot call `withdraw` again. // Fungsi ini dilindungi oleh mutex, sehingga panggilan reentrant dari dalam `msg.sender.call` tidak dapat memanggil `withdraw` lagi.
14 // The `return` statement evaluates to `true` but still evaluates the `locked = false` statement in the modifier // Pernyataan `return` dievaluasi menjadi `true` tetapi tetap mengevaluasi pernyataan `locked = false` di dalam modifier
15 function withdraw(uint _amount) public payable noReentrancy returns(bool) {
16 require(balances[msg.sender] >= _amount, "No balance to withdraw.");
17
18 balances[msg.sender] -= _amount;
19 (bool success, ) = msg.sender.call{value: _amount}("");
20 require(success);
21
22 return true;
23 }
24}
Tampilkan semua

Anda juga dapat menggunakan sistem pembayaran tarik (pull payments) (opens in a new tab) yang mengharuskan pengguna untuk menarik dana dari kontrak pintar, alih-alih sistem "pembayaran dorong" (push payments) yang mengirimkan dana ke akun. Ini menghilangkan kemungkinan memicu kode secara tidak sengaja di alamat yang tidak diketahui (dan juga dapat mencegah serangan penolakan layanan/denial-of-service tertentu).

Underflow dan overflow bilangan bulat

Overflow bilangan bulat terjadi ketika hasil operasi aritmatika berada di luar rentang nilai yang dapat diterima, menyebabkannya "bergulir" (roll over) ke nilai terendah yang dapat direpresentasikan. Misalnya, uint8 hanya dapat menyimpan nilai hingga 2^8-1=255. Operasi aritmatika yang menghasilkan nilai lebih tinggi dari 255 akan meluap (overflow) dan mengatur ulang uint ke 0, mirip dengan bagaimana odometer pada mobil diatur ulang ke 0 setelah mencapai jarak tempuh maksimum (999999).

Underflow bilangan bulat terjadi karena alasan yang sama: hasil operasi aritmatika berada di bawah rentang yang dapat diterima. Katakanlah Anda mencoba mengurangi 0 dalam uint8, hasilnya akan bergulir ke nilai maksimum yang dapat direpresentasikan (255).

Baik overflow maupun underflow bilangan bulat dapat menyebabkan perubahan tak terduga pada variabel status kontrak dan mengakibatkan eksekusi yang tidak direncanakan. Di bawah ini adalah contoh yang menunjukkan bagaimana penyerang dapat mengeksploitasi overflow aritmatika dalam kontrak pintar untuk melakukan operasi yang tidak valid:

1pragma solidity ^0.7.6;
2
3// This contract is designed to act as a time vault.
4// User can deposit into this contract but cannot withdraw for at least a week.
5// User can also extend the wait time beyond the 1 week waiting period.
6
7/*
81. Deploy TimeLock
92. Deploy Attack with address of TimeLock
103. Call Attack.attack sending 1 ether. You will immediately be able to
11 withdraw your ether.
12
13What happened?
14Attack caused the TimeLock.lockTime to overflow and was able to withdraw
15before the 1 week waiting period.
16*/
17
18contract TimeLock {
19 mapping(address => uint) public balances;
20 mapping(address => uint) public lockTime;
21
22 function deposit() external payable {
23 balances[msg.sender] += msg.value;
24 lockTime[msg.sender] = block.timestamp + 1 weeks;
25 }
26
27 function increaseLockTime(uint _secondsToIncrease) public {
28 lockTime[msg.sender] += _secondsToIncrease;
29 }
30
31 function withdraw() public {
32 require(balances[msg.sender] > 0, "Insufficient funds");
33 require(block.timestamp > lockTime[msg.sender], "Lock time not expired");
34
35 uint amount = balances[msg.sender];
36 balances[msg.sender] = 0;
37
38 (bool sent, ) = msg.sender.call{value: amount}("");
39 require(sent, "Failed to send Ether");
40 }
41}
42
43contract Attack {
44 TimeLock timeLock;
45
46 constructor(TimeLock _timeLock) {
47 timeLock = TimeLock(_timeLock);
48 }
49
50 fallback() external payable {}
51
52 function attack() public payable {
53 timeLock.deposit{value: msg.value}();
54 /*
55 if t = current lock time then we need to find x such that
56 x + t = 2**256 = 0
57 so x = -t
58 2**256 = type(uint).max + 1
59 so x = type(uint).max + 1 - t
60 */
61 timeLock.increaseLockTime(
62 type(uint).max + 1 - timeLock.lockTime(address(this))
63 );
64 timeLock.withdraw();
65 }
66}
Tampilkan semua
Cara mencegah underflow dan overflow bilangan bulat

Mulai versi 0.8.0, kompiler Solidity menolak kode yang menghasilkan underflow dan overflow bilangan bulat. Namun, kontrak yang dikompilasi dengan versi kompiler yang lebih rendah harus melakukan pemeriksaan pada fungsi yang melibatkan operasi aritmatika atau menggunakan pustaka (misalnya, SafeMath (opens in a new tab)) yang memeriksa underflow/overflow.

Manipulasi oracle

Oracle mengambil informasi offchain dan mengirimkannya secara onchain untuk digunakan oleh kontrak pintar. Dengan oracle, Anda dapat merancang kontrak pintar yang beroperasi dengan sistem offchain, seperti pasar modal, yang sangat memperluas aplikasinya.

Namun jika oracle rusak dan mengirimkan informasi yang salah secara onchain, kontrak pintar akan dieksekusi berdasarkan input yang salah, yang dapat menyebabkan masalah. Ini adalah dasar dari “masalah oracle”, yang berkaitan dengan tugas memastikan informasi dari oracle blockchain akurat, mutakhir, dan tepat waktu.

Masalah keamanan terkait adalah menggunakan oracle onchain, seperti pertukaran terdesentralisasi, untuk mendapatkan harga spot suatu aset. Platform pinjaman di industri keuangan terdesentralisasi (DeFi) sering melakukan ini untuk menentukan nilai agunan pengguna guna menentukan berapa banyak yang dapat mereka pinjam.

Harga DEX sering kali akurat, sebagian besar karena arbitrase yang memulihkan paritas di pasar. Namun, harga tersebut terbuka untuk manipulasi, terutama jika oracle onchain menghitung harga aset berdasarkan pola perdagangan historis (seperti yang biasanya terjadi).

Misalnya, penyerang dapat memompa harga spot suatu aset secara artifisial dengan mengambil flash loan tepat sebelum berinteraksi dengan kontrak pinjaman Anda. Meminta harga aset ke DEX akan mengembalikan nilai yang lebih tinggi dari biasanya (karena “pesanan beli” besar penyerang yang membelokkan permintaan aset), memungkinkan mereka meminjam lebih dari yang seharusnya. "Serangan flash loan" semacam itu telah digunakan untuk mengeksploitasi ketergantungan pada oracle harga di antara aplikasi DeFi, yang merugikan protokol jutaan dana yang hilang.

Cara mencegah manipulasi oracle

Persyaratan minimum untuk menghindari manipulasi oracle (opens in a new tab) adalah menggunakan jaringan oracle terdesentralisasi yang meminta informasi dari berbagai sumber untuk menghindari titik kegagalan tunggal. Dalam kebanyakan kasus, oracle terdesentralisasi memiliki insentif kriptoekonomi bawaan untuk mendorong node oracle melaporkan informasi yang benar, menjadikannya lebih aman daripada oracle terpusat.

Jika Anda berencana meminta harga aset ke oracle onchain, pertimbangkan untuk menggunakan oracle yang menerapkan mekanisme harga rata-rata tertimbang waktu (time-weighted average price/TWAP). Oracle TWAP (opens in a new tab) meminta harga aset pada dua titik waktu yang berbeda (yang dapat Anda modifikasi) dan menghitung harga spot berdasarkan rata-rata yang diperoleh. Memilih periode waktu yang lebih lama melindungi protokol Anda dari manipulasi harga karena pesanan besar yang dieksekusi baru-baru ini tidak dapat memengaruhi harga aset.

Sumber daya keamanan kontrak pintar untuk pengembang

Alat untuk menganalisis kontrak pintar dan memverifikasi kebenaran kode

  • Alat dan pustaka pengujian - Koleksi alat dan pustaka standar industri untuk melakukan pengujian unit, analisis statis, dan analisis dinamis pada kontrak pintar.

  • Alat verifikasi formal - Alat untuk memverifikasi kebenaran fungsional dalam kontrak pintar dan memeriksa invarian.

  • Layanan audit kontrak pintar - Daftar organisasi yang menyediakan layanan audit kontrak pintar untuk proyek pengembangan Ethereum.

  • Platform bug bounty - Platform untuk mengoordinasikan bug bounty dan memberikan hadiah atas pengungkapan kerentanan kritis yang bertanggung jawab dalam kontrak pintar.

  • Fork Checker (opens in a new tab) - Alat online gratis untuk memeriksa semua informasi yang tersedia mengenai kontrak yang di-fork.

  • ABI Encoder (opens in a new tab) - Layanan online gratis untuk mengenkode fungsi kontrak Solidity dan argumen konstruktor Anda.

  • Aderyn (opens in a new tab) - Penganalisis Statis Solidity, menelusuri Abstract Syntax Trees (AST) untuk menunjukkan dengan tepat kerentanan yang dicurigai dan mencetak masalah dalam format markdown yang mudah dipahami.

Alat untuk memantau kontrak pintar

Alat untuk administrasi kontrak pintar yang aman

  • Safe (opens in a new tab) - Dompet kontrak pintar yang berjalan di Ethereum yang memerlukan jumlah minimum orang untuk menyetujui transaksi sebelum dapat terjadi (M-of-N).

  • OpenZeppelin Contracts (opens in a new tab) - Pustaka kontrak untuk mengimplementasikan fitur administratif, termasuk kepemilikan kontrak, peningkatan, kontrol akses, tata kelola, kemampuan jeda, dan banyak lagi.

Layanan audit kontrak pintar

  • ConsenSys Diligence (opens in a new tab) - Layanan audit kontrak pintar yang membantu proyek di seluruh ekosistem blockchain memastikan protokol mereka siap diluncurkan dan dibangun untuk melindungi pengguna.

  • CertiK (opens in a new tab) - Perusahaan keamanan blockchain yang memelopori penggunaan teknologi Verifikasi formal mutakhir pada kontrak pintar dan jaringan blockchain.

  • Trail of Bits (opens in a new tab) - Perusahaan keamanan siber yang menggabungkan penelitian keamanan dengan mentalitas penyerang untuk mengurangi risiko dan memperkuat kode.

  • PeckShield (opens in a new tab) - Perusahaan keamanan blockchain yang menawarkan produk dan layanan untuk keamanan, privasi, dan kegunaan seluruh ekosistem blockchain.

  • QuantStamp (opens in a new tab) - Layanan audit yang memfasilitasi adopsi arus utama teknologi blockchain melalui layanan penilaian keamanan dan risiko.

  • OpenZeppelin (opens in a new tab) - Perusahaan keamanan kontrak pintar yang menyediakan audit keamanan untuk sistem terdistribusi.

  • Runtime Verification (opens in a new tab) - Perusahaan keamanan yang berspesialisasi dalam pemodelan formal dan verifikasi kontrak pintar.

  • Hacken (opens in a new tab) - Auditor keamanan siber Web3 yang membawa pendekatan 360 derajat ke keamanan blockchain.

  • Nethermind (opens in a new tab) - Layanan audit Solidity dan Cairo, memastikan integritas kontrak pintar dan keselamatan pengguna di seluruh Ethereum dan Starknet.

  • HashEx (opens in a new tab) - HashEx berfokus pada audit blockchain dan kontrak pintar untuk memastikan keamanan mata uang kripto, menyediakan layanan seperti pengembangan kontrak pintar, pengujian penetrasi, konsultasi blockchain.

  • Code4rena (opens in a new tab) - Platform audit kompetitif yang memberi insentif kepada pakar keamanan kontrak pintar untuk menemukan kerentanan dan membantu membuat web3 lebih aman.

  • CodeHawks (opens in a new tab) - Platform audit kompetitif yang menyelenggarakan kompetisi audit kontrak pintar untuk peneliti keamanan.

  • Cyfrin (opens in a new tab) - Pusat kekuatan keamanan Web3, menginkubasi keamanan kripto melalui produk dan layanan audit kontrak pintar.

  • ImmuneBytes (opens in a new tab) - Firma keamanan Web3 yang menawarkan audit keamanan untuk sistem blockchain melalui tim auditor berpengalaman dan alat terbaik di kelasnya.

  • Oxorio (opens in a new tab) - Audit kontrak pintar dan layanan keamanan blockchain dengan keahlian dalam EVM, Solidity, ZK, teknologi Lintas-rantai (Cross-chain) untuk perusahaan kripto dan proyek DeFi.

  • Inference (opens in a new tab) - Perusahaan audit keamanan, berspesialisasi dalam audit kontrak pintar untuk blockchain berbasis EVM. Berkat auditor ahlinya, mereka mengidentifikasi potensi masalah dan menyarankan solusi yang dapat ditindaklanjuti untuk memperbaikinya sebelum penerapan.

Platform bug bounty

  • Immunefi (opens in a new tab) - Platform bug bounty untuk kontrak pintar dan proyek DeFi, tempat peneliti keamanan meninjau kode, mengungkapkan kerentanan, mendapatkan bayaran, dan membuat kripto lebih aman.

  • HackerOne (opens in a new tab) - Platform koordinasi kerentanan dan bug bounty yang menghubungkan bisnis dengan penguji penetrasi dan peneliti keamanan siber.

  • HackenProof (opens in a new tab) - Platform bug bounty ahli untuk proyek kripto (DeFi, Kontrak Pintar, Dompet, CEX, dan lainnya), tempat profesional keamanan menyediakan layanan triase dan peneliti dibayar untuk laporan bug yang relevan dan terverifikasi.

  • Sherlock (opens in a new tab) - Penjamin emisi di Web3 untuk keamanan kontrak pintar, dengan pembayaran untuk auditor yang dikelola melalui kontrak pintar untuk memastikan bahwa bug yang relevan dibayar secara adil.

  • CodeHawks (opens in a new tab) - Platform bug bounty kompetitif tempat auditor mengambil bagian dalam kontes dan tantangan keamanan, dan (segera) dalam audit pribadi mereka sendiri.

Publikasi kerentanan dan eksploitasi kontrak pintar yang diketahui

Tantangan untuk mempelajari keamanan kontrak pintar

Praktik terbaik untuk mengamankan kontrak pintar

Tutorial tentang keamanan kontrak pintar

Apakah artikel ini membantu?