Penulis: Xiao Bai

Edit: Liz

Gambaran umum latar belakang

Dalam ekosistem Ethereum, mekanisme pembuatan alamat kontrak yang deterministik memberikan kemudahan bagi pengembang, tetapi juga memperkenalkan permukaan serangan baru. Dalam edisi ini, kita akan menganalisis teknik serangan yang menggunakan opcode CREATE dan CREATE2 untuk menerapkan kontrak berbeda ke alamat yang sama pada waktu yang berbeda serta strategi pertahanannya. Artikel pengantar audit keamanan kontrak pintar sebelumnya dapat dilihat dalam koleksi.

Pengetahuan awal

Mari kita mulai dengan memahami dua aturan dalam menghasilkan alamat Ethereum:

1. CREATE

CREATE adalah opcode asli yang digunakan untuk menerapkan kontrak pintar secara dinamis di Ethereum Virtual Machine (EVM). Sejak blok genesis Ethereum, semua penerapan kontrak bergantung pada mekanisme ini. Ciri utama dari mekanisme ini adalah bahwa penghasilan alamat bergantung pada nonce akun penerap, sehingga alamatnya bersifat tidak deterministik (tidak dapat diprediksi secara tepat sebelum penerapan).

Alamat kontrak yang dihasilkan oleh CREATE ditentukan oleh alamat penerapan dan nonce alamat tersebut:

2. CREATE2

CREATE2 adalah opcode pembuatan kontrak baru yang diperkenalkan Ethereum dalam hard fork Konstantinopel (Februari 2019). Berbeda dengan CREATE tradisional, sebelum penerapan kontrak, peserta di luar rantai dapat menghitung alamat kontrak sebelumnya, ini memungkinkan interaksi di luar rantai (seperti saluran status) dan arsitektur kontrak yang kompleks.

Alamat kontrak yang dihasilkan oleh CREATE2 ditentukan oleh empat parameter berikut:

Sampai di sini, saya yakin Anda sudah memahami dua cara untuk menghasilkan alamat kontrak di Ethereum. Pembaca yang teliti mungkin bertanya, bagaimana jika alamat kontrak yang dihitung sudah ada? Mengenai hal ini, Anda tidak perlu khawatir, di Ethereum, baik melalui CREATE maupun CREATE2, jika alamat yang dihasilkan sudah ada di blockchain (baik akun eksternal EOA maupun akun kontrak), EVM akan menolak permintaan untuk membuat kontrak. Berikut adalah dua skenario konflik alamat:

1. Alamat target adalah akun eksternal (EOA)

  • Aturan: Jika alamat target adalah EOA yang sudah ada (misalnya, alamat dompet pengguna), EVM akan menolak permintaan penerapan kontrak.

  • Hasil: Transaksi gagal, Gas terpakai, kontrak tidak akan dibuat, dan tidak akan menimpa data apa pun di alamat tersebut.

2. Alamat target adalah akun kontrak

  • Aturan: Jika alamat target adalah akun kontrak yang sudah diterapkan, EVM juga akan menolak permintaan penerapan.

  • Hasil: Transaksi gagal, Gas terpakai, kode dan data penyimpanan kontrak yang ada tetap tidak berubah.

Tentu saja, ada pengecualian, jika alamat target adalah kontrak dan telah dihancurkan melalui selfdestruct, maka dapat menerapkan kontrak baru di alamat tersebut.

Dengan ini, berbagai karakteristik dari dua opcode CREATE dan CREATE2 telah diperkenalkan. Selanjutnya, mari kita lihat bagaimana memanfaatkan karakteristik kedua opcode ini untuk melakukan serangan kontrak.

Contoh kerentanan

Analisis kerentanan

Kontrak DAO ini mengimplementasikan mekanisme pemerintahan yang dasar: Pemilik melalui fungsi approve meninjau dan mencatat alamat kontrak proposal ke dalam array Proposals, pengguna mana pun kemudian dapat mengeksekusi proposal yang telah ditinjau melalui fungsi execute. Kontrol hak akses yang tampaknya ketat (hanya Pemilik yang dapat meninjau proposal) dikombinasikan dengan pemeriksaan eksekusi (proposal harus ditinjau dan belum dieksekusi), sebenarnya terdapat celah logika yang tersembunyi: alamat proposal yang telah ditinjau mungkin pada saat eksekusi mengarah ke kode kontrak yang sama sekali berbeda. Penyerang dapat melaksanakan serangan dalam tiga langkah berikut:

1. Terapkan kontrak normal, dapatkan otorisasi

Penyerang pertama kali menerapkan kontrak A yang berisi fungsi executeProposal() yang tidak berbahaya, melalui tinjauan Pemilik, alamat tersebut ditambahkan ke dalam daftar proposal.

2. Hancurkan kontrak asli, ambil alih alamat

Kontrak A melakukan operasi penghancuran (selfdestruct) menghapus kode, kemudian penyerang menggunakan opcode CREATE2 untuk menerapkan kontrak berbahaya B di alamat yang sama (termasuk logika berbahaya dari executeProposal()).

3. Memicu eksekusi, mengambil alih kendali

Ketika pengguna memanggil execute, kontrak akan mengeksekusi kode kontrak berbahaya B yang baru diterapkan melalui delegatecall. Karena delegatecall akan menjaga konteks kontrak saat ini, penyerang dapat menggunakan operasi ini untuk mengubah status kontrak DAO (misalnya mengubah Pemilik) atau memindahkan aset.

Sekarang kita akan melihat alur serangan spesifik dengan menggabungkan kontrak serangan.

Kontrak serangan

Alur serangan adalah sebagai berikut:

1. Alice menerapkan kontrak DAO.

2. Evil menerapkan kontrak DeployerDeployer, alamatnya adalah DD.

3. Evil memanggil DD.deploy(), menggunakan CREATE2 untuk menerapkan Deployer ke alamat D (salt tetap).

4. Panggil D.deployProposal(), buat alamat kontrak Proposal P, pada saat ini nonce D adalah 0.

5. Alice menyetujui alamat P.

6. Penyerang memanggil D.kill(), menghancurkan D, pada saat ini akun alamat D dihapus, termasuk nonce yang juga direset ke 0.

7. Penyerang sekali lagi memanggil DD.deploy(), menerapkan Deployer ke alamat D (parameter CREATE2 yang sama).

8. Panggil D.deployAttack(), karena pada saat ini nonce D direset ke 0 dan alamat D tidak berubah, sehingga alamat kontrak Attack yang dibuat sama dengan alamat kontrak Proposal sebelumnya P.

9. Pada saat ini, ada satu proposal dalam array proposals DAO yang mengarah ke alamat P, tetapi sekarang alamat P telah diterapkan kembali sebagai kontrak Attack. Oleh karena itu, ketika memanggil execute(), akan mengeksekusi executeProposal() dari Attack, akhirnya melalui delegatecall mengubah Pemilik DAO menjadi msg.sender, yaitu kontrak Attack.

Prinsip serangan dapat dirangkum sebagai penyerang menggunakan CREATE2 untuk menerapkan kembali kontrak ke alamat yang sama, pertama menerapkan kontrak Proposal yang sah, lalu membiarkan DAO menyetujuinya. Kemudian menghancurkan kontrak Deployer, menerapkan kembali Deployer di alamat yang sama, mengatur ulang nonce alamat tersebut, dan akhirnya berhasil menerapkan kontrak berbahaya ke alamat yang sama dengan Proposal sebelumnya. Karena alamat proposal yang disimpan di DAO kini mengarah ke kontrak berbahaya, saat proposal dieksekusi, kode berbahaya dijalankan dalam konteks DAO, berhasil mengubah Pemilik menjadi penyerang.

Saran perbaikan

Sebagai pengembang:

  • Saat menyetujui proposal, tidak hanya alamat yang harus dicatat, tetapi juga hash kode alamat tersebut, dan saat mengeksekusi harus memverifikasi apakah hash kode tersebut konsisten.

  • Hindari menggunakan delegatecall untuk memanggil kontrak eksternal, kecuali ada langkah keamanan yang memadai.

  • Pertimbangkan kemungkinan kontrak dihancurkan dan diterapkan kembali ke alamat yang sama, saat melakukan panggilan eksternal, perlu memeriksa apakah kontrak eksternal dapat dipercaya, jika perlu memanggil kontrak eksternal yang asing, disarankan untuk memeriksa apakah terdapat mekanisme pencegahan penghancuran pada kontrak.

Sebagai auditor:

  • Identifikasi apakah terdapat fungsi selfdestruct dalam kontrak yang dipanggil secara eksternal, jika ada, perlu waspada terhadap risiko serangan dengan menghancurkan kontrak eksternal dan menerapkan kode berbahaya ke alamat yang sama.

  • Periksa apakah metode penerapan kontrak menggunakan CREATE2, pastikan nilai salt yang digunakan untuk menghasilkan alamat kontrak cukup acak, untuk menghindari serangan di mana alamat kontrak diprediksi sebelumnya.

  • Periksa apakah DAO memverifikasi konsistensi kode alamat target saat mengeksekusi proposal, misalnya dengan membandingkan hash kode saat ini dengan hash saat disetujui.

  • Analisis dengan hati-hati penggunaan delegatecall, pastikan alamat target dapat dipercaya dan tidak ada risiko pemanggilan alamat sembarang.

  • Tinjau manajemen siklus hidup proposal, misalnya apakah ada mekanisme untuk mencegah proposal diubah atau diganti, seperti mengunci status alamat target setelah disetujui.