Pola proxy memungkinkan kontrak pintar untuk meningkatkan logikanya sambil mempertahankan alamat on-chain dan nilai statusnya. Panggilan ke kontrak proxy akan mengeksekusi kode dari kontrak logika melalui delegateCall untuk mengubah keadaan kontrak proxy.
Artikel ini akan memberikan ikhtisar tentang jenis kontrak proxy, rekomendasi dan insiden keamanan terkait, serta praktik terbaik untuk menggunakan kontrak proxy.
Pengantar Kontrak yang Dapat Ditingkatkan dan Mode Proksi
Kita semua tahu fitur "non-tamperable" dari blockchain, dan kode smart contract tidak dapat dimodifikasi setelah digunakan di blockchain.
Jadi, saat pengembang ingin memperbarui kode kontrak untuk peningkatan logika, perbaikan bug, atau pembaruan keamanan, mereka harus menerapkan kontrak baru, dan alamat kontrak baru akan dibuat.
Untuk mengatasi masalah ini, Anda dapat menggunakan mode proxy.
Mode proxy menyadari peningkatan kemampuan kontrak tanpa mengubah alamat penerapan kontrak, yang saat ini merupakan mode peningkatan kontrak yang paling umum.
Mode proxy adalah sistem kontrak yang dapat diupgrade, termasuk kontrak proxy dan kontrak implementasi logika.
Kontrak proxy menangani interaksi pengguna dan data serta penyimpanan status kontrak. Panggilan pengguna ke kontrak proxy akan mengeksekusi kode dari kontrak logika melalui delegatecall(), sehingga mengubah status kontrak proxy. Pemutakhiran diwujudkan dengan memperbarui alamat kontrak logis yang dicatat dalam slot penyimpanan yang telah ditentukan kontrak proxy.
Tiga mode proxy yang lebih konvensional adalah proxy transparan, proxy UUPS, dan proxy Beacon.
Proksi Transparan
Dalam mode proxy transparan, fungsi pemutakhiran diimplementasikan dalam kontrak proxy. Peran administrator kontrak proxy diberikan otoritas langsung untuk mengoperasikan kontrak proxy untuk memperbarui alamat implementasi logis yang sesuai dengan proxy. Penelepon tanpa hak istimewa admin akan mendelegasikan panggilan mereka ke kontrak pelaksana.
Catatan: Administrator kontrak proxy tidak dapat menjadi peran kunci dalam implementasi logis dari kontrak, bahkan tidak dapat menjadi pengguna biasa, karena administrator proxy tidak dapat berinteraksi dengan kontrak implementasi.
** Proksi UUPS **
Dalam mode UUPS (Universal Upgradeable Proxy Standard), fungsi peningkatan kontrak diimplementasikan dalam kontrak logika. Karena mekanisme pemutakhiran disimpan dalam kontrak logika, versi pemutakhiran dapat menghapus logika terkait pemutakhiran untuk melarang pemutakhiran di masa mendatang. Dalam mode ini, semua panggilan ke kontrak proxy diteruskan ke kontrak implementasi logika.
Proksi Suar
Mode proxy Beacon memungkinkan beberapa kontrak proxy untuk berbagi implementasi logika yang sama dengan mereferensikan kontrak Beacon. Kontrak Beacon memberikan alamat kontrak implementasi logika untuk kontrak proxy yang dipanggil. Saat meningkatkan ke alamat implementasi logika baru, hanya alamat yang dicatat dalam kontrak Beacon yang perlu diperbarui.
Penyalahgunaan Proksi dan Insiden Keamanan
Pengembang dapat menggunakan kontrak mode proxy untuk menerapkan sistem kontrak yang dapat ditingkatkan. Namun, mode proxy juga memiliki ambang operasional tertentu, jika digunakan secara tidak benar, dapat menyebabkan masalah keamanan yang menghancurkan proyek. Bagian berikut menampilkan insiden terkait penyalahgunaan proxy, dan risiko sentralisasi yang ditimbulkan oleh proxy.
Pengungkapan Kunci yang Dikelola Proxy
Administrator proxy mengontrol mekanisme pemutakhiran mode proxy transparan, jika kunci pribadi administrator bocor, penyerang dapat memutakhirkan kontrak logika dan menjalankan logika berbahaya mereka sendiri di status proxy.
Pada tanggal 5 Maret 2021, PAID Network mengalami serangan "pencetakan" yang disebabkan oleh pengelolaan kunci pribadi yang buruk. Jaringan DIBAYAR dieksploitasi oleh penyerang yang mencuri kunci pribadi administrator proxy dan memicu mekanisme pemutakhiran untuk mengubah kontrak logika.
Setelah peningkatan, penyerang dapat menghancurkan DIBAYAR pengguna dan membuat kumpulan DIBAYAR untuk dirinya sendiri, yang dapat dijual nanti. Tidak ada kerentanan keamanan dalam kode itu sendiri, tetapi penyerang mendapatkan kunci pribadi untuk memutakhirkan kontrak dari administrator.
** Implementasi proxy UUPS yang tidak diinisialisasi **
Untuk mode proxy UUPS, selama inisialisasi kontrak proxy, parameter awal diteruskan ke kontrak proxy oleh pemanggil, dan kemudian kontrak proxy memanggil fungsi initialize() dalam kontrak logika untuk mencapai inisialisasi.
Fungsi initialize() biasanya dilindungi dengan pengubah "initializer" untuk membatasi fungsi agar dipanggil hanya sekali. Setelah memanggil fungsi initialize(), dari perspektif kontrak proxy, kontrak logika diinisialisasi.
Namun, dari perspektif kontrak logika, kontrak logika tidak diinisialisasi karena initialize() tidak dipanggil secara langsung dalam kontrak logika. Mengingat kontrak logika itu sendiri tidak diinisialisasi, siapa pun dapat memanggil fungsi initialize() untuk menginisialisasinya, menyetel variabel status ke nilai berbahaya, dan berpotensi mengambil alih kontrak logika.
Dampak dari kontrak logika yang diambil alih bergantung pada kode kontrak dalam sistem. Dalam kasus terburuk, penyerang dapat memutakhirkan kontrak logika dalam mode proxy UUPS menjadi kontrak jahat dan menjalankan panggilan fungsi "penghancuran diri", yang dapat menyebabkan seluruh kontrak proxy menjadi tidak berguna, dan aset dalam kontrak akan dihancurkan secara permanen hilang.
kasus
① Parity Multisig Freeze: Kontrak logika tidak diinisialisasi. Penyerang memicu inisialisasi banyak dompet dan mengunci ether dalam kontrak dengan memanggil selfdestruct().
② Harvest Finance, Teller, KeeperDAO, dan Rivermen semuanya menggunakan kontrak logika yang tidak diinisialisasi, yang memungkinkan penyerang untuk mengatur parameter inisialisasi kontrak secara sewenang-wenang, dan mengeksekusi penghancuran diri() selama panggilan delegasi() untuk menghancurkan kontrak proxy.
Konflik Penyimpanan
Dalam sistem kontrak yang dapat ditingkatkan, kontrak proxy tidak mendeklarasikan variabel status, tetapi menggunakan slot penyimpanan acak semu untuk menyimpan data penting.
Kontrak proxy menyimpan nilai variabel status kontrak logis relatif terhadap tempat mereka dideklarasikan. Jika kontrak proxy mendeklarasikan variabel statusnya sendiri dan baik proxy maupun kontrak logika mencoba menggunakan slot penyimpanan yang sama, konflik penyimpanan akan terjadi.
Kontrak proxy yang disediakan oleh pustaka OpenZeppelin tidak mendeklarasikan variabel status dalam kontrak, tetapi berdasarkan standar EIP 1967, menyimpan nilai yang perlu disimpan (seperti alamat manajemen) di slot penyimpanan khusus untuk mencegah konflik.
kasus
Pada tanggal 23 Juli 2022 waktu Beijing, platform musik terdesentralisasi Audius diretas, insiden tersebut disebabkan oleh pengenalan logika baru dalam kontrak proxy, yang mengakibatkan konflik penyimpanan.
Kontrak proxy mendeklarasikan variabel status alamat proxyAdmin, dan nilainya akan salah dibaca saat kode kontrak logika dijalankan.
Nilai proxyAdmin yang dikustomisasi oleh pihak proyek secara keliru dianggap sebagai nilai diinisialisasi dan diinisialisasi, sehingga pengubah penginisialisasi mengembalikan hasil yang salah, yang memungkinkan penyerang memanggil fungsi initialize() lagi dan memberi dirinya wewenang untuk mengelola kontrak. Penyerang kemudian mengubah parameter pemungutan suara dan memberikan proposal jahat mereka untuk mencuri aset Audius.
Panggil panggilan delegasi() dalam kontrak logika atau kontrak tidak tepercaya
Anggaplah delegatecall() ada dalam kontrak logis, dan kontrak tersebut tidak memvalidasi target panggilan dengan benar. Dalam hal ini, penyerang dapat mengeksploitasi fungsi ini untuk mengeksekusi panggilan ke kontrak jahat yang mereka kontrol, untuk menumbangkan implementasi logika, atau untuk mengeksekusi logika kustom.
Demikian pula, jika ada fungsi address.call() yang tidak dibatasi dalam kontrak logika, setelah penyerang dengan jahat memberikan alamat dan bidang data, itu dapat digunakan sebagai kontrak proxy.
kasus
Serangan Pickle Finance, Furucombo, dan dYdX.
Dalam insiden ini, kontrak yang rentan disetujui oleh token pengguna, dan ada call()/delegatecall() dalam kontrak yang disediakan oleh pengguna untuk memanggil alamat kontrak dan data, penyerang akan dapat memanggil kontrak fungsi transferFrom() untuk menarik saldo pengguna. Selama insiden dYdX, dYdX melakukan serangan topi putih mereka sendiri untuk melindungi dana.
Praktik terbaik
umumnya
(1) Gunakan mode proxy hanya jika diperlukan
Tidak semua kontrak perlu ditingkatkan. Seperti yang ditunjukkan di atas, ada banyak risiko dalam penggunaan pola proxy. Properti "dapat ditingkatkan" juga menimbulkan masalah kepercayaan, karena administrator proxy dapat meningkatkan kontrak tanpa persetujuan komunitas. Kami merekomendasikan untuk mengintegrasikan pola proxy ke dalam proyek hanya jika diperlukan.
(2) Jangan memodifikasi perpustakaan proxy
Pustaka kontrak proxy itu kompleks, terutama bagian yang berhubungan dengan manajemen penyimpanan dan mekanisme pemutakhiran. Setiap kesalahan dalam modifikasi akan mempengaruhi pekerjaan kontrak proxy dan logika. Sejumlah besar bug terkait agen dengan tingkat keparahan tinggi yang kami temukan selama audit kami disebabkan oleh modifikasi pustaka agen yang salah. Insiden Audius adalah contoh utama dari konsekuensi modifikasi kontrak agensi yang tidak tepat.
Poin-Poin Utama Operasi dan Manajemen Kontrak Keagenan
(1) Inisialisasi kontrak logika
Penyerang dapat mengambil alih kontrak logika yang tidak diinisialisasi dan berpotensi membahayakan sistem kontrak proxy. Jadi harap inisialisasi kontrak logika setelah penerapan, atau gunakan _disableInitializers() dalam konstruktor kontrak logika untuk menonaktifkan inisialisasi secara otomatis.
(2) Pastikan keamanan akun manajemen agen
Sistem kontrak yang dapat diupgrade biasanya memerlukan peran istimewa dari "administrator proxy" untuk mengelola peningkatan kontrak. Jika kunci manajemen bocor, penyerang dapat dengan bebas meningkatkan kontrak menjadi kontrak jahat, yang dapat mencuri aset pengguna. Kami merekomendasikan pengelolaan kunci privat akun admin proxy dengan hati-hati untuk menghindari potensi risiko peretasan. Dompet multi-tanda tangan dapat digunakan untuk mencegah kegagalan manajemen kunci satu titik.
(3) Gunakan akun terpisah untuk manajemen proxy yang transparan
Manajemen proxy dan tata kelola logika harus menjadi alamat terpisah untuk mencegah hilangnya interaksi dengan implementasi logika. Jika manajemen proxy dan tata kelola logis merujuk ke alamat yang sama, tidak ada panggilan yang diteruskan untuk melakukan fungsi istimewa, sehingga melarang perubahan pada fungsi tata kelola.
Terkait penyimpanan kontrak proxy
(1) Berhati-hatilah saat mendeklarasikan variabel status dalam kontrak proxy
Seperti yang dijelaskan dalam peretasan Audius, kontrak proxy harus berhati-hati saat mendeklarasikan variabel statusnya sendiri. Variabel status yang dideklarasikan dengan cara biasa dalam kontrak proxy dapat menyebabkan konflik data saat membaca dan menulis data. Jika kontrak proxy memerlukan variabel keadaan, simpan nilai dalam slot penyimpanan seperti EIP1967 untuk mencegah konflik saat menjalankan kode kontrak logika.
(2) Pertahankan urutan deklarasi variabel dan jenis kontrak logika
Setiap versi kontrak logika harus mempertahankan urutan dan jenis variabel status yang sama, dan variabel status baru perlu ditambahkan di akhir variabel yang ada. Jika tidak, panggilan delegasi dapat menyebabkan kontrak proxy membaca atau menimpa nilai tersimpan yang salah, dan data lama dapat dikaitkan dengan variabel yang baru dideklarasikan, yang dapat menyebabkan masalah serius bagi aplikasi.
(3) Sertakan celah penyimpanan dalam kontrak dasar
Kontrak logika perlu menyertakan celah penyimpanan dalam kode kontrak untuk mengantisipasi variabel status baru saat menerapkan implementasi logika baru. Setelah menambahkan variabel status baru, ukuran celah perlu diperbarui dengan tepat.
(4) Jangan menetapkan nilai variabel status dalam proses konstruktor atau deklarasi
Menetapkan variabel status selama deklarasi atau dalam konstruktor hanya memengaruhi nilai dalam kontrak logika, bukan kontrak proxy. Parameter yang tidak dapat diubah harus ditetapkan menggunakan fungsi initialize().
Warisan Kontrak
(1) Kontrak yang dapat ditingkatkan hanya dapat mewarisi dari kontrak lain yang dapat ditingkatkan
Kontrak yang dapat ditingkatkan memiliki struktur yang berbeda dari kontrak yang tidak dapat ditingkatkan. Misalnya, konstruktor tidak kompatibel dengan perubahan status agen, konstruktor menggunakan fungsi initialize() untuk menyetel variabel status.
Setiap kontrak yang mewarisi dari kontrak lain perlu menggunakan fungsi initialize() dari kontrak yang diwarisi untuk menetapkan variabelnya masing-masing. Saat menggunakan pustaka OpenZeppelin atau menulis kode Anda sendiri, pastikan bahwa kontrak yang dapat ditingkatkan hanya dapat mewarisi kontrak lain yang dapat ditingkatkan.
(2) Jangan membuat kontrak baru dalam kontrak logika
Kontrak yang dibuat dan dibuat melalui Solidity tidak akan dapat ditingkatkan. Kontrak harus dikerahkan secara individual dan meneruskan alamatnya sebagai parameter ke kontrak logika yang dapat diupgrade untuk mencapai status yang dapat diupgrade.
(3) Risiko inisialisasi kontrak induk
Saat menginisialisasi kontrak Induk, fungsi __{ContractName}_init akan menginisialisasi kontrak Induknya. Beberapa panggilan ke __{ContractName}_init dapat mengakibatkan inisialisasi kedua dari kontrak Induk. Perhatikan bahwa __{ContractName}_init_unchained() hanya akan menginisialisasi parameter {ContractName}, dan tidak akan memanggil penginisialisasi kontrak Induknya.
Namun, ini bukan praktik yang disarankan, karena semua kontrak Induk harus diinisialisasi, dan tidak menginisialisasi kontrak yang diperlukan akan menyebabkan masalah eksekusi di masa mendatang.
Implementasi kontrak logika
Hindari selfdestruct() atau selegatecall()/call() ke kontrak yang tidak dipercaya
Jika ada selfdestruct() atau delegatecall() dalam kontrak, penyerang dapat menggunakan fungsi ini untuk menghentikan implementasi logika atau menjalankan logika kustom. Pengembang harus memvalidasi input pengguna dan tidak mengizinkan kontrak mengeksekusi panggilan delegasi/panggilan ke kontrak yang tidak dipercaya. Selain itu, tidak disarankan untuk menggunakan delegatecall() dalam kontrak logika karena akan merepotkan untuk mengelola tata letak penyimpanan dalam rantai delegasi dari beberapa kontrak.
Ditulis di bagian akhir
Kontrak proxy mem-bypass sifat abadi dari blockchain dengan mengaktifkan protokol untuk memperbarui logika kode mereka setelah penerapan. Namun, pengembangan kontrak proxy masih harus sangat hati-hati, dan penerapan yang salah dapat menyebabkan masalah keamanan dan logika proyek.
Secara keseluruhan, praktik terbaik adalah menggunakan solusi resmi dan teruji secara ekstensif, karena mode Transparan, UUPS, dan Beacon Proxy masing-masing memiliki mekanisme pemutakhiran yang terbukti untuk kasus penggunaannya masing-masing. Selain itu, peran istimewa untuk agen eskalasi juga harus dikelola dengan aman untuk mencegah penyerang mengubah logika agen.
Kontrak implementasi logika juga harus berhati-hati untuk tidak menggunakan delegatecall(), yang dapat mencegah penyerang mengeksekusi beberapa kode berbahaya, seperti selfdestruct().
Meskipun mengikuti praktik terbaik memastikan penerapan kontrak proxy yang stabil sambil mempertahankan fleksibilitas yang dapat ditingkatkan, semua kode rentan terhadap masalah keamanan atau logika baru yang dapat membahayakan proyek. Oleh karena itu, semua kode sebaiknya diaudit oleh tim pakar keamanan yang berpengalaman dalam mengaudit dan mengamankan protokol kontrak proxy.
Lihat Asli
Konten ini hanya untuk referensi, bukan ajakan atau tawaran. Tidak ada nasihat investasi, pajak, atau hukum yang diberikan. Lihat Penafian untuk pengungkapan risiko lebih lanjut.
Melanggar kekekalan blockchain: Bagaimana model proxy dapat mencapai peningkatan kontrak pintar
Pola proxy memungkinkan kontrak pintar untuk meningkatkan logikanya sambil mempertahankan alamat on-chain dan nilai statusnya. Panggilan ke kontrak proxy akan mengeksekusi kode dari kontrak logika melalui delegateCall untuk mengubah keadaan kontrak proxy.
Artikel ini akan memberikan ikhtisar tentang jenis kontrak proxy, rekomendasi dan insiden keamanan terkait, serta praktik terbaik untuk menggunakan kontrak proxy.
Pengantar Kontrak yang Dapat Ditingkatkan dan Mode Proksi
Kita semua tahu fitur "non-tamperable" dari blockchain, dan kode smart contract tidak dapat dimodifikasi setelah digunakan di blockchain.
Jadi, saat pengembang ingin memperbarui kode kontrak untuk peningkatan logika, perbaikan bug, atau pembaruan keamanan, mereka harus menerapkan kontrak baru, dan alamat kontrak baru akan dibuat.
Untuk mengatasi masalah ini, Anda dapat menggunakan mode proxy.
Mode proxy menyadari peningkatan kemampuan kontrak tanpa mengubah alamat penerapan kontrak, yang saat ini merupakan mode peningkatan kontrak yang paling umum.
Mode proxy adalah sistem kontrak yang dapat diupgrade, termasuk kontrak proxy dan kontrak implementasi logika.
Kontrak proxy menangani interaksi pengguna dan data serta penyimpanan status kontrak. Panggilan pengguna ke kontrak proxy akan mengeksekusi kode dari kontrak logika melalui delegatecall(), sehingga mengubah status kontrak proxy. Pemutakhiran diwujudkan dengan memperbarui alamat kontrak logis yang dicatat dalam slot penyimpanan yang telah ditentukan kontrak proxy.
Tiga mode proxy yang lebih konvensional adalah proxy transparan, proxy UUPS, dan proxy Beacon.
Proksi Transparan
Dalam mode proxy transparan, fungsi pemutakhiran diimplementasikan dalam kontrak proxy. Peran administrator kontrak proxy diberikan otoritas langsung untuk mengoperasikan kontrak proxy untuk memperbarui alamat implementasi logis yang sesuai dengan proxy. Penelepon tanpa hak istimewa admin akan mendelegasikan panggilan mereka ke kontrak pelaksana.
Catatan: Administrator kontrak proxy tidak dapat menjadi peran kunci dalam implementasi logis dari kontrak, bahkan tidak dapat menjadi pengguna biasa, karena administrator proxy tidak dapat berinteraksi dengan kontrak implementasi.
** Proksi UUPS **
Dalam mode UUPS (Universal Upgradeable Proxy Standard), fungsi peningkatan kontrak diimplementasikan dalam kontrak logika. Karena mekanisme pemutakhiran disimpan dalam kontrak logika, versi pemutakhiran dapat menghapus logika terkait pemutakhiran untuk melarang pemutakhiran di masa mendatang. Dalam mode ini, semua panggilan ke kontrak proxy diteruskan ke kontrak implementasi logika.
Proksi Suar
Mode proxy Beacon memungkinkan beberapa kontrak proxy untuk berbagi implementasi logika yang sama dengan mereferensikan kontrak Beacon. Kontrak Beacon memberikan alamat kontrak implementasi logika untuk kontrak proxy yang dipanggil. Saat meningkatkan ke alamat implementasi logika baru, hanya alamat yang dicatat dalam kontrak Beacon yang perlu diperbarui.
Penyalahgunaan Proksi dan Insiden Keamanan
Pengembang dapat menggunakan kontrak mode proxy untuk menerapkan sistem kontrak yang dapat ditingkatkan. Namun, mode proxy juga memiliki ambang operasional tertentu, jika digunakan secara tidak benar, dapat menyebabkan masalah keamanan yang menghancurkan proyek. Bagian berikut menampilkan insiden terkait penyalahgunaan proxy, dan risiko sentralisasi yang ditimbulkan oleh proxy.
Pengungkapan Kunci yang Dikelola Proxy
Administrator proxy mengontrol mekanisme pemutakhiran mode proxy transparan, jika kunci pribadi administrator bocor, penyerang dapat memutakhirkan kontrak logika dan menjalankan logika berbahaya mereka sendiri di status proxy.
Pada tanggal 5 Maret 2021, PAID Network mengalami serangan "pencetakan" yang disebabkan oleh pengelolaan kunci pribadi yang buruk. Jaringan DIBAYAR dieksploitasi oleh penyerang yang mencuri kunci pribadi administrator proxy dan memicu mekanisme pemutakhiran untuk mengubah kontrak logika.
Setelah peningkatan, penyerang dapat menghancurkan DIBAYAR pengguna dan membuat kumpulan DIBAYAR untuk dirinya sendiri, yang dapat dijual nanti. Tidak ada kerentanan keamanan dalam kode itu sendiri, tetapi penyerang mendapatkan kunci pribadi untuk memutakhirkan kontrak dari administrator.
** Implementasi proxy UUPS yang tidak diinisialisasi **
Untuk mode proxy UUPS, selama inisialisasi kontrak proxy, parameter awal diteruskan ke kontrak proxy oleh pemanggil, dan kemudian kontrak proxy memanggil fungsi initialize() dalam kontrak logika untuk mencapai inisialisasi.
Fungsi initialize() biasanya dilindungi dengan pengubah "initializer" untuk membatasi fungsi agar dipanggil hanya sekali. Setelah memanggil fungsi initialize(), dari perspektif kontrak proxy, kontrak logika diinisialisasi.
Namun, dari perspektif kontrak logika, kontrak logika tidak diinisialisasi karena initialize() tidak dipanggil secara langsung dalam kontrak logika. Mengingat kontrak logika itu sendiri tidak diinisialisasi, siapa pun dapat memanggil fungsi initialize() untuk menginisialisasinya, menyetel variabel status ke nilai berbahaya, dan berpotensi mengambil alih kontrak logika.
Dampak dari kontrak logika yang diambil alih bergantung pada kode kontrak dalam sistem. Dalam kasus terburuk, penyerang dapat memutakhirkan kontrak logika dalam mode proxy UUPS menjadi kontrak jahat dan menjalankan panggilan fungsi "penghancuran diri", yang dapat menyebabkan seluruh kontrak proxy menjadi tidak berguna, dan aset dalam kontrak akan dihancurkan secara permanen hilang.
kasus
① Parity Multisig Freeze: Kontrak logika tidak diinisialisasi. Penyerang memicu inisialisasi banyak dompet dan mengunci ether dalam kontrak dengan memanggil selfdestruct().
② Harvest Finance, Teller, KeeperDAO, dan Rivermen semuanya menggunakan kontrak logika yang tidak diinisialisasi, yang memungkinkan penyerang untuk mengatur parameter inisialisasi kontrak secara sewenang-wenang, dan mengeksekusi penghancuran diri() selama panggilan delegasi() untuk menghancurkan kontrak proxy.
Konflik Penyimpanan
Dalam sistem kontrak yang dapat ditingkatkan, kontrak proxy tidak mendeklarasikan variabel status, tetapi menggunakan slot penyimpanan acak semu untuk menyimpan data penting.
Kontrak proxy menyimpan nilai variabel status kontrak logis relatif terhadap tempat mereka dideklarasikan. Jika kontrak proxy mendeklarasikan variabel statusnya sendiri dan baik proxy maupun kontrak logika mencoba menggunakan slot penyimpanan yang sama, konflik penyimpanan akan terjadi.
Kontrak proxy yang disediakan oleh pustaka OpenZeppelin tidak mendeklarasikan variabel status dalam kontrak, tetapi berdasarkan standar EIP 1967, menyimpan nilai yang perlu disimpan (seperti alamat manajemen) di slot penyimpanan khusus untuk mencegah konflik.
kasus
Pada tanggal 23 Juli 2022 waktu Beijing, platform musik terdesentralisasi Audius diretas, insiden tersebut disebabkan oleh pengenalan logika baru dalam kontrak proxy, yang mengakibatkan konflik penyimpanan.
Kontrak proxy mendeklarasikan variabel status alamat proxyAdmin, dan nilainya akan salah dibaca saat kode kontrak logika dijalankan.
Nilai proxyAdmin yang dikustomisasi oleh pihak proyek secara keliru dianggap sebagai nilai diinisialisasi dan diinisialisasi, sehingga pengubah penginisialisasi mengembalikan hasil yang salah, yang memungkinkan penyerang memanggil fungsi initialize() lagi dan memberi dirinya wewenang untuk mengelola kontrak. Penyerang kemudian mengubah parameter pemungutan suara dan memberikan proposal jahat mereka untuk mencuri aset Audius.
Panggil panggilan delegasi() dalam kontrak logika atau kontrak tidak tepercaya
Anggaplah delegatecall() ada dalam kontrak logis, dan kontrak tersebut tidak memvalidasi target panggilan dengan benar. Dalam hal ini, penyerang dapat mengeksploitasi fungsi ini untuk mengeksekusi panggilan ke kontrak jahat yang mereka kontrol, untuk menumbangkan implementasi logika, atau untuk mengeksekusi logika kustom.
Demikian pula, jika ada fungsi address.call() yang tidak dibatasi dalam kontrak logika, setelah penyerang dengan jahat memberikan alamat dan bidang data, itu dapat digunakan sebagai kontrak proxy.
kasus
Serangan Pickle Finance, Furucombo, dan dYdX.
Dalam insiden ini, kontrak yang rentan disetujui oleh token pengguna, dan ada call()/delegatecall() dalam kontrak yang disediakan oleh pengguna untuk memanggil alamat kontrak dan data, penyerang akan dapat memanggil kontrak fungsi transferFrom() untuk menarik saldo pengguna. Selama insiden dYdX, dYdX melakukan serangan topi putih mereka sendiri untuk melindungi dana.
Praktik terbaik
umumnya
(1) Gunakan mode proxy hanya jika diperlukan
Tidak semua kontrak perlu ditingkatkan. Seperti yang ditunjukkan di atas, ada banyak risiko dalam penggunaan pola proxy. Properti "dapat ditingkatkan" juga menimbulkan masalah kepercayaan, karena administrator proxy dapat meningkatkan kontrak tanpa persetujuan komunitas. Kami merekomendasikan untuk mengintegrasikan pola proxy ke dalam proyek hanya jika diperlukan.
(2) Jangan memodifikasi perpustakaan proxy
Pustaka kontrak proxy itu kompleks, terutama bagian yang berhubungan dengan manajemen penyimpanan dan mekanisme pemutakhiran. Setiap kesalahan dalam modifikasi akan mempengaruhi pekerjaan kontrak proxy dan logika. Sejumlah besar bug terkait agen dengan tingkat keparahan tinggi yang kami temukan selama audit kami disebabkan oleh modifikasi pustaka agen yang salah. Insiden Audius adalah contoh utama dari konsekuensi modifikasi kontrak agensi yang tidak tepat.
Poin-Poin Utama Operasi dan Manajemen Kontrak Keagenan
(1) Inisialisasi kontrak logika
Penyerang dapat mengambil alih kontrak logika yang tidak diinisialisasi dan berpotensi membahayakan sistem kontrak proxy. Jadi harap inisialisasi kontrak logika setelah penerapan, atau gunakan _disableInitializers() dalam konstruktor kontrak logika untuk menonaktifkan inisialisasi secara otomatis.
(2) Pastikan keamanan akun manajemen agen
Sistem kontrak yang dapat diupgrade biasanya memerlukan peran istimewa dari "administrator proxy" untuk mengelola peningkatan kontrak. Jika kunci manajemen bocor, penyerang dapat dengan bebas meningkatkan kontrak menjadi kontrak jahat, yang dapat mencuri aset pengguna. Kami merekomendasikan pengelolaan kunci privat akun admin proxy dengan hati-hati untuk menghindari potensi risiko peretasan. Dompet multi-tanda tangan dapat digunakan untuk mencegah kegagalan manajemen kunci satu titik.
(3) Gunakan akun terpisah untuk manajemen proxy yang transparan
Manajemen proxy dan tata kelola logika harus menjadi alamat terpisah untuk mencegah hilangnya interaksi dengan implementasi logika. Jika manajemen proxy dan tata kelola logis merujuk ke alamat yang sama, tidak ada panggilan yang diteruskan untuk melakukan fungsi istimewa, sehingga melarang perubahan pada fungsi tata kelola.
Terkait penyimpanan kontrak proxy
(1) Berhati-hatilah saat mendeklarasikan variabel status dalam kontrak proxy
Seperti yang dijelaskan dalam peretasan Audius, kontrak proxy harus berhati-hati saat mendeklarasikan variabel statusnya sendiri. Variabel status yang dideklarasikan dengan cara biasa dalam kontrak proxy dapat menyebabkan konflik data saat membaca dan menulis data. Jika kontrak proxy memerlukan variabel keadaan, simpan nilai dalam slot penyimpanan seperti EIP1967 untuk mencegah konflik saat menjalankan kode kontrak logika.
(2) Pertahankan urutan deklarasi variabel dan jenis kontrak logika
Setiap versi kontrak logika harus mempertahankan urutan dan jenis variabel status yang sama, dan variabel status baru perlu ditambahkan di akhir variabel yang ada. Jika tidak, panggilan delegasi dapat menyebabkan kontrak proxy membaca atau menimpa nilai tersimpan yang salah, dan data lama dapat dikaitkan dengan variabel yang baru dideklarasikan, yang dapat menyebabkan masalah serius bagi aplikasi.
(3) Sertakan celah penyimpanan dalam kontrak dasar
Kontrak logika perlu menyertakan celah penyimpanan dalam kode kontrak untuk mengantisipasi variabel status baru saat menerapkan implementasi logika baru. Setelah menambahkan variabel status baru, ukuran celah perlu diperbarui dengan tepat.
(4) Jangan menetapkan nilai variabel status dalam proses konstruktor atau deklarasi
Menetapkan variabel status selama deklarasi atau dalam konstruktor hanya memengaruhi nilai dalam kontrak logika, bukan kontrak proxy. Parameter yang tidak dapat diubah harus ditetapkan menggunakan fungsi initialize().
Warisan Kontrak
(1) Kontrak yang dapat ditingkatkan hanya dapat mewarisi dari kontrak lain yang dapat ditingkatkan
Kontrak yang dapat ditingkatkan memiliki struktur yang berbeda dari kontrak yang tidak dapat ditingkatkan. Misalnya, konstruktor tidak kompatibel dengan perubahan status agen, konstruktor menggunakan fungsi initialize() untuk menyetel variabel status.
Setiap kontrak yang mewarisi dari kontrak lain perlu menggunakan fungsi initialize() dari kontrak yang diwarisi untuk menetapkan variabelnya masing-masing. Saat menggunakan pustaka OpenZeppelin atau menulis kode Anda sendiri, pastikan bahwa kontrak yang dapat ditingkatkan hanya dapat mewarisi kontrak lain yang dapat ditingkatkan.
(2) Jangan membuat kontrak baru dalam kontrak logika
Kontrak yang dibuat dan dibuat melalui Solidity tidak akan dapat ditingkatkan. Kontrak harus dikerahkan secara individual dan meneruskan alamatnya sebagai parameter ke kontrak logika yang dapat diupgrade untuk mencapai status yang dapat diupgrade.
(3) Risiko inisialisasi kontrak induk
Saat menginisialisasi kontrak Induk, fungsi __{ContractName}_init akan menginisialisasi kontrak Induknya. Beberapa panggilan ke __{ContractName}_init dapat mengakibatkan inisialisasi kedua dari kontrak Induk. Perhatikan bahwa __{ContractName}_init_unchained() hanya akan menginisialisasi parameter {ContractName}, dan tidak akan memanggil penginisialisasi kontrak Induknya.
Namun, ini bukan praktik yang disarankan, karena semua kontrak Induk harus diinisialisasi, dan tidak menginisialisasi kontrak yang diperlukan akan menyebabkan masalah eksekusi di masa mendatang.
Implementasi kontrak logika
Hindari selfdestruct() atau selegatecall()/call() ke kontrak yang tidak dipercaya
Jika ada selfdestruct() atau delegatecall() dalam kontrak, penyerang dapat menggunakan fungsi ini untuk menghentikan implementasi logika atau menjalankan logika kustom. Pengembang harus memvalidasi input pengguna dan tidak mengizinkan kontrak mengeksekusi panggilan delegasi/panggilan ke kontrak yang tidak dipercaya. Selain itu, tidak disarankan untuk menggunakan delegatecall() dalam kontrak logika karena akan merepotkan untuk mengelola tata letak penyimpanan dalam rantai delegasi dari beberapa kontrak.
Ditulis di bagian akhir
Kontrak proxy mem-bypass sifat abadi dari blockchain dengan mengaktifkan protokol untuk memperbarui logika kode mereka setelah penerapan. Namun, pengembangan kontrak proxy masih harus sangat hati-hati, dan penerapan yang salah dapat menyebabkan masalah keamanan dan logika proyek.
Secara keseluruhan, praktik terbaik adalah menggunakan solusi resmi dan teruji secara ekstensif, karena mode Transparan, UUPS, dan Beacon Proxy masing-masing memiliki mekanisme pemutakhiran yang terbukti untuk kasus penggunaannya masing-masing. Selain itu, peran istimewa untuk agen eskalasi juga harus dikelola dengan aman untuk mencegah penyerang mengubah logika agen.
Kontrak implementasi logika juga harus berhati-hati untuk tidak menggunakan delegatecall(), yang dapat mencegah penyerang mengeksekusi beberapa kode berbahaya, seperti selfdestruct().
Meskipun mengikuti praktik terbaik memastikan penerapan kontrak proxy yang stabil sambil mempertahankan fleksibilitas yang dapat ditingkatkan, semua kode rentan terhadap masalah keamanan atau logika baru yang dapat membahayakan proyek. Oleh karena itu, semua kode sebaiknya diaudit oleh tim pakar keamanan yang berpengalaman dalam mengaudit dan mengamankan protokol kontrak proxy.