Menabrak RPC: Analisis Jenis Kerentanan Baru di Node RPC Blockchain yang Aman-Memori

Tim Skyfall CertiK baru-baru ini menemukan beberapa kerentanan dalam node RPC berbasis Rust di beberapa blockchain termasuk Aptos, StarCoin, dan Sui. Karena node RPC adalah komponen infrastruktur penting yang menghubungkan dApps dan blockchain yang mendasarinya, ketahanannya sangat penting untuk kelancaran operasi. Desainer Blockchain mengetahui pentingnya layanan RPC yang stabil, sehingga mereka mengadopsi bahasa yang aman untuk memori seperti Rust untuk menghindari kerentanan umum yang dapat merusak node RPC.

Mengadopsi bahasa yang aman untuk memori seperti Rust membantu node RPC menghindari banyak serangan berdasarkan kerentanan kerusakan memori. Namun, melalui audit baru-baru ini, kami menemukan bahwa bahkan implementasi Rust yang aman untuk memori, jika tidak dirancang dan diperiksa dengan hati-hati, dapat menjadi rentan terhadap ancaman keamanan tertentu yang dapat mengganggu ketersediaan layanan RPC.

Pada artikel ini, kami akan memperkenalkan penemuan kami tentang serangkaian kerentanan melalui kasus-kasus praktis.

Peran node RPC Blockchain

Layanan remote procedure call (RPC) dari blockchain adalah komponen infrastruktur inti dari blockchain Layer 1. Ini memberi pengguna front-end API yang penting dan bertindak sebagai pintu gerbang ke jaringan blockchain back-end. Namun, layanan RPC blockchain berbeda dari layanan RPC tradisional karena memfasilitasi interaksi pengguna tanpa otentikasi. Ketersediaan berkelanjutan dari layanan sangat penting, dan gangguan apa pun dalam layanan dapat sangat memengaruhi ketersediaan blockchain yang mendasarinya.

Perspektif audit: server RPC tradisional VS server RPC blockchain

Audit server RPC tradisional terutama berfokus pada verifikasi input, otorisasi/otentikasi, pemalsuan permintaan lintas situs/pemalsuan permintaan sisi server (CSRF/SSRF), kerentanan injeksi (seperti injeksi SQL, injeksi perintah) dan kebocoran informasi.

Namun, situasinya berbeda untuk server RPC blockchain. Selama transaksi ditandatangani, tidak perlu mengautentikasi klien yang meminta pada lapisan RPC. Sebagai front end dari blockchain, salah satu tujuan utama dari layanan RPC adalah untuk menjamin ketersediaannya. Jika gagal, pengguna tidak dapat berinteraksi dengan blockchain, mencegah mereka menanyakan data on-chain, mengirimkan transaksi, atau mengeluarkan fungsi kontrak.

Oleh karena itu, aspek yang paling rentan dari server RPC blockchain adalah "ketersediaan". Jika server mati, pengguna kehilangan kemampuan untuk berinteraksi dengan blockchain. Yang lebih serius adalah bahwa beberapa serangan akan menyebar ke rantai, memengaruhi sejumlah besar node, dan bahkan menyebabkan kelumpuhan seluruh jaringan.

Mengapa blockchain baru akan menggunakan RPC yang aman dari memori

Beberapa blockchain Layer 1 yang terkenal, seperti Aptos dan Sui, menggunakan bahasa pemrograman yang aman dari memori Rust untuk mengimplementasikan layanan RPC mereka. Berkat keamanannya yang kuat dan pemeriksaan waktu kompilasi yang ketat, Rust membuat program hampir kebal terhadap kerentanan korupsi memori, seperti stack overflows, dan dereferensi penunjuk nol dan kerentanan referensi-setelah-bebas.

Untuk lebih mengamankan basis kode, pengembang secara ketat mengikuti praktik terbaik, seperti tidak memasukkan kode yang tidak aman. Gunakan #![forbid(unsafe_code)] dalam kode sumber untuk memastikan bahwa kode yang tidak aman diblokir dan difilter.

Contoh pengembang blockchain yang menerapkan praktik pemrograman Rust

Untuk mencegah kelebihan bilangan bulat, pengembang biasanya menggunakan fungsi seperti dicentang_tambahkan, dicentang_sub, saturasi_tambahkan, saturasi_sub, dll. alih-alih penambahan dan pengurangan sederhana (+, -). Kurangi habisnya sumber daya dengan mengatur batas waktu yang sesuai, batas ukuran permintaan, dan batas item permintaan.

** Ancaman RPC Keamanan Memori di Lapisan 1 Blockchain **

Meskipun tidak rentan terhadap ketidakamanan memori dalam pengertian tradisional, node RPC terpapar input yang mudah dimanipulasi oleh penyerang. Dalam implementasi RPC yang aman dari memori, ada beberapa situasi yang dapat menyebabkan penolakan layanan. Misalnya, amplifikasi memori dapat menghabiskan memori layanan, sementara masalah logika dapat menimbulkan loop tak terbatas. Selain itu, kondisi balapan dapat menimbulkan ancaman di mana operasi bersamaan dapat memiliki rangkaian kejadian yang tidak terduga, meninggalkan sistem dalam keadaan tidak terdefinisi. Selain itu, dependensi yang tidak dikelola dengan benar dan pustaka pihak ketiga dapat menyebabkan kerentanan yang tidak diketahui ke dalam sistem.

Dalam posting ini, tujuan kami adalah untuk menarik perhatian pada cara-cara yang lebih cepat yang dapat memicu perlindungan runtime Rust, menyebabkan layanan dibatalkan sendiri.

Explicit Rust Panic: Cara untuk menghentikan layanan RPC secara langsung

Pengembang dapat memperkenalkan kode panik eksplisit, sengaja atau tidak sengaja. Kode-kode ini terutama digunakan untuk menangani kondisi yang tidak terduga atau luar biasa. Beberapa contoh umum meliputi:

menegaskan!(): Gunakan makro ini ketika kondisi harus dipenuhi. Jika kondisi yang dinyatakan gagal, program akan panik, menandakan bahwa ada kesalahan serius pada kode.

panic!(): Fungsi ini dipanggil ketika program menemui kesalahan yang tidak dapat dipulihkan dan tidak dapat dilanjutkan.

unreachable!(): Gunakan makro ini saat sepotong kode tidak boleh dieksekusi. Jika makro ini dipanggil, ini menunjukkan kesalahan logika yang serius.

tidak diimplementasikan!() dan todo!(): Makro ini adalah placeholder untuk fungsionalitas yang tidak diimplementasikan. Jika nilai ini tercapai, program akan macet.

unwrap(): Metode ini digunakan untuk tipe Option atau Result.Ketika variabel Err atau None ditemukan, program akan macet.

Kerentanan 1: Memicu penegasan di Pindahkan Pemverifikasi!

Blockchain Aptos menggunakan pemverifikasi bytecode Move untuk melakukan analisis keamanan referensi melalui interpretasi abstrak dari bytecode. Fungsi ute() adalah bagian dari implementasi sifat TransferFunctions dan mensimulasikan eksekusi instruksi bytecode dalam blok dasar.

Tugas dari fungsi ute_inner() adalah menginterpretasikan instruksi bytecode saat ini dan memperbarui status sesuai dengan itu. Jika kita telah mengeksekusi instruksi terakhir di blok dasar, seperti yang ditunjukkan oleh index == last_index, fungsi akan memanggil assert!(self.stack.is_empty()) untuk memastikan stack kosong. Tujuan di balik perilaku ini adalah untuk menjamin bahwa semua operasi seimbang, yang juga berarti bahwa setiap dorongan memiliki letupan yang sesuai.

Dalam aliran eksekusi normal, tumpukan selalu seimbang selama interpretasi abstrak. Ini dijamin oleh Stack Balance Checker, yang memverifikasi bytecode sebelum menafsirkannya. Namun, begitu kami memperluas perspektif kami ke ranah penafsir abstrak, kami melihat bahwa asumsi keseimbangan tumpukan tidak selalu valid.

Patch untuk analisis_fungsi kerentanan di AbstractInterpreter

Pada intinya, interpreter abstrak mengemulasi bytecode pada level blok dasar. Dalam implementasi aslinya, menemukan kesalahan selama ute_block akan mendorong proses analisis untuk mencatat kesalahan dan melanjutkan eksekusi ke blok berikutnya dalam grafik alur kontrol. Ini dapat menciptakan situasi di mana kesalahan dalam blok eksekusi dapat menyebabkan tumpukan menjadi tidak seimbang. Jika eksekusi berlanjut dalam kasus ini, pemeriksaan tegas! akan dilakukan jika tumpukan tidak kosong, menyebabkan kepanikan.

Ini memberi penyerang kesempatan untuk mengeksploitasi. Penyerang dapat memicu kesalahan dengan mendesain bytecode tertentu di ute_block(), lalu ute() dapat mengeksekusi pernyataan jika tumpukan tidak kosong, menyebabkan pemeriksaan pernyataan gagal. Ini akan semakin panik dan menghentikan layanan RPC, yang memengaruhi ketersediaannya.

Untuk mencegah hal ini, perbaikan yang diterapkan memastikan bahwa seluruh proses analisis dihentikan saat fungsi ute_block pertama kali menemui kesalahan, sehingga menghindari risiko crash berikutnya yang mungkin terjadi saat melanjutkan analisis karena ketidakseimbangan tumpukan akibat kesalahan. Modifikasi ini menghilangkan kondisi yang dapat menyebabkan kepanikan dan membantu meningkatkan ketahanan dan keamanan penafsir abstrak.

** Kerentanan 2: Memicu kepanikan di StarCoin! **

Blockchain Starcoin memiliki fork implementasi Move-nya sendiri. Di repo Pindah ini, ada kepanikan di konstruktor tipe Struct! Jika StructDefinition yang disediakan memiliki informasi bidang Native, kepanikan akan dipicu secara eksplisit! .

Kepanikan eksplisit untuk struktur yang diinisialisasi dalam rutinitas normalisasi

Risiko potensial ini ada dalam proses pendistribusian ulang modul. Jika modul yang diterbitkan sudah ada di penyimpanan data, normalisasi modul diperlukan untuk modul yang ada dan modul masukan yang dikontrol penyerang. Selama proses ini, fungsi "normalized::Module::new" membangun struktur modul dari modul masukan yang dikontrol penyerang, memicu "panik!".

Prasyarat untuk rutinitas normalisasi

Kepanikan ini dapat dipicu dengan mengirimkan muatan yang dibuat khusus dari klien. Oleh karena itu, pelaku jahat dapat mengganggu ketersediaan layanan RPC.

Patch panik inisialisasi struktur

Patch Starcoin memperkenalkan perilaku baru untuk menangani kasus Native. Sekarang, alih-alih panik, ini mengembalikan ec kosong. Ini mengurangi kemungkinan pengguna mengirimkan data yang menyebabkan kepanikan.

Kepanikan Karat Tersirat: Cara yang mudah diabaikan untuk mengakhiri layanan RPC

Kepanikan eksplisit mudah diidentifikasi dalam kode sumber, sedangkan kepanikan implisit cenderung diabaikan oleh pengembang. Kepanikan implisit biasanya terjadi saat menggunakan API yang disediakan oleh pustaka standar atau pihak ketiga. Pengembang perlu membaca dan memahami dokumentasi API secara menyeluruh, atau program Rust mereka mungkin berhenti tiba-tiba.

Kepanikan tersirat di BTreeMap

Mari kita ambil BTreeMap dari Rust STD sebagai contoh. BTreeMap adalah struktur data yang umum digunakan yang mengatur pasangan kunci-nilai dalam pohon biner yang diurutkan. BTreeMap menyediakan dua metode untuk mengambil nilai dengan kunci: get(&self, key: &Q) dan index(&self, key: &Q).

Metode get(&self, key: &Q) mengambil nilai menggunakan kunci dan mengembalikan sebuah Opsi. Opsi dapat berupa Some(&V), jika kuncinya ada, kembalikan referensi nilainya, jika kuncinya tidak ditemukan di BTreeMap, kembalikan Tidak Ada.

Di sisi lain, index(&self, key: &Q) secara langsung mengembalikan referensi ke nilai yang sesuai dengan kunci. Namun, ini memiliki risiko besar: akan memicu kepanikan implisit jika kuncinya tidak ada di BTreeMap. Jika tidak ditangani dengan baik, program dapat tiba-tiba macet, menjadikannya potensi kerentanan.

Faktanya, metode index(&self, key: &Q) adalah implementasi dasar dari sifat std::ops::Index. Sifat ini adalah operasi indeks dalam konteks yang tidak dapat diubah (yaitu container [index] ) menyediakan gula sintaksis yang nyaman. Pengembang dapat langsung menggunakan btree_map [key] , panggil metode index(&self, key: &Q). Namun, mereka mungkin mengabaikan fakta bahwa penggunaan ini dapat membuat panik jika kunci tidak ditemukan, sehingga menimbulkan ancaman implisit terhadap stabilitas program.

Kerentanan 3: Memicu kepanikan implisit di Sui RPC

Rutin rilis modul Sui memungkinkan pengguna mengirimkan muatan modul melalui RPC. Penangan RPC menggunakan fungsi SuiCommand::Publish untuk langsung membongkar modul yang diterima sebelum meneruskan permintaan ke jaringan verifikasi backend untuk verifikasi bytecode.

Selama pembongkaran ini, bagian code_unit dari modul yang dikirimkan digunakan untuk membuat VMControlFlowGraph. Proses build terdiri dari pembuatan blok dasar, yang disimpan dalam BTreeMap bernama "'blok'". Prosesnya termasuk membuat dan memanipulasi Peta, di mana kepanikan tersirat dipicu dalam kondisi tertentu.

Berikut adalah kode yang disederhanakan:

Kepanikan tersirat saat membuat VMControlFlowGraph

Dalam kode tersebut, VMControlFlowGraph baru dibuat dengan menelusuri kode dan membuat blok dasar baru untuk setiap unit kode. Blok dasar disimpan dalam blok bernama BTreeMap.

Peta blok diindeks menggunakan block[&block] dalam satu lingkaran yang berulang di atas tumpukan, yang telah diinisialisasi dengan ENTRY_BLOCK_ID. Asumsinya di sini adalah setidaknya ada satu ENTRY_BLOCK_ID di peta blok.

Namun, asumsi ini tidak selalu berlaku. Misalnya, jika kode yang dikomit kosong, "peta blok" akan tetap kosong setelah proses "buat blok dasar". Ketika kode kemudian mencoba melintasi peta blok menggunakan for succ di &blocks[&block].successors , kepanikan implisit dapat dimunculkan jika kunci tidak ditemukan. Ini karena ekspresi blocks[&block] pada dasarnya adalah panggilan ke metode index(), yang, seperti disebutkan sebelumnya, akan panik jika kuncinya tidak ada di BTreeMap.

Penyerang dengan akses jarak jauh dapat mengeksploitasi kerentanan dalam fungsi ini dengan mengirimkan muatan modul yang salah bentuk dengan kolom kode_unit kosong. Permintaan RPC sederhana ini merusak seluruh proses JSON-RPC. Jika penyerang terus mengirimkan muatan yang cacat tersebut dengan upaya minimal, hal itu akan mengakibatkan gangguan layanan yang berkelanjutan. Dalam jaringan blockchain, ini berarti bahwa jaringan mungkin tidak dapat mengonfirmasi transaksi baru, yang mengakibatkan situasi penolakan layanan (DoS). Fungsionalitas jaringan dan kepercayaan pengguna pada sistem akan sangat terpengaruh.

Perbaikan Sui: hapus pembongkaran dari rutinitas masalah RPC

Perlu dicatat bahwa CodeUnitVerifier di Move Bytecode Verifier bertanggung jawab untuk memastikan bahwa bagian code_unit tidak pernah kosong. Namun, urutan operasi membuat penangan RPC rentan terhadap potensi kerentanan. Hal ini dikarenakan proses validasi berlangsung pada node Validator, yaitu tahapan setelah RPC memproses modul input.

Menanggapi masalah ini, Sui memecahkan kerentanan dengan menghapus fungsi pembongkaran di rutinitas RPC rilis modul. Ini adalah cara yang efektif untuk mencegah layanan RPC memproses bytecode yang berpotensi berbahaya dan tidak divalidasi.

Juga, perlu dicatat bahwa metode RPC lain yang terkait dengan pencarian objek juga mengandung kemampuan pembongkaran, tetapi tidak rentan terhadap penggunaan sel kode kosong. Ini karena mereka selalu menanyakan dan membongkar modul yang sudah diterbitkan. Modul yang diterbitkan harus sudah diverifikasi, sehingga asumsi sel kode yang tidak kosong selalu berlaku saat membuat VMControlFlowGraph.

Saran untuk pengembang

Setelah memahami ancaman terhadap stabilitas layanan RPC di blockchain dari kepanikan eksplisit dan implisit, pengembang harus menguasai strategi untuk mencegah atau mengurangi risiko ini. Strategi ini dapat mengurangi kemungkinan pemadaman layanan yang tidak direncanakan dan meningkatkan ketahanan sistem. Oleh karena itu, tim pakar CertiK mengajukan saran berikut dan mencantumkannya sebagai praktik terbaik untuk pemrograman Rust.

Rust Panic Abstraction: Jika memungkinkan, pertimbangkan untuk menggunakan fungsi catch_unwind Rust untuk menangkap kepanikan dan mengubahnya menjadi pesan kesalahan. Ini mencegah seluruh program mogok dan memungkinkan pengembang untuk menangani kesalahan secara terkendali.

Gunakan API dengan hati-hati: Kepanikan implisit biasanya terjadi karena penyalahgunaan API yang disediakan oleh pustaka standar atau pihak ketiga. Oleh karena itu, sangat penting untuk sepenuhnya memahami API dan belajar menangani potensi kesalahan dengan tepat. Pengembang harus selalu berasumsi bahwa API mungkin gagal dan bersiap untuk situasi seperti itu.

Penanganan kesalahan yang tepat: gunakan tipe Hasil dan Opsi untuk penanganan kesalahan alih-alih panik. Yang pertama memberikan cara yang lebih terkontrol untuk menangani kesalahan dan kasus khusus.

Tambahkan dokumentasi dan komentar: Pastikan kode Anda terdokumentasi dengan baik dan tambahkan komentar ke bagian penting (termasuk di mana kepanikan dapat terjadi). Ini akan membantu pengembang lain memahami potensi risiko dan menanganinya secara efektif.

Meringkaskan

Node RPC berbasis karat memainkan peran penting dalam sistem blockchain seperti Aptos, StarCoin, dan Sui. Karena mereka digunakan untuk menghubungkan DApps dan blockchain yang mendasarinya, keandalannya sangat penting untuk kelancaran pengoperasian sistem blockchain. Meskipun sistem ini menggunakan bahasa yang aman untuk memori, Rust, masih ada risiko desain yang buruk. Tim peneliti CertiK menjelajahi risiko ini dengan contoh dunia nyata yang menunjukkan perlunya desain yang hati-hati dan hati-hati dalam pemrograman yang aman dari memori.

Lihat Asli
This page may contain third-party content, which is provided for information purposes only (not representations/warranties) and should not be considered as an endorsement of its views by Gate, nor as financial or professional advice. See Disclaimer for details.
  • Hadiah
  • Komentar
  • Bagikan
Komentar
0/400
Tidak ada komentar
  • Sematkan
Perdagangkan Kripto Di Mana Saja Kapan Saja
qrCode
Pindai untuk mengunduh aplikasi Gate
Komunitas
Bahasa Indonesia
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)