Pemeriksaan Pemilik
Pemeriksaan pemilik adalah garis pertahanan pertama dalam keamanan program Solana. Pemeriksaan ini memverifikasi bahwa akun yang dimasukkan ke dalam handler instruksi benar-benar dimiliki oleh program yang diharapkan, mencegah penyerang mengganti akun tiruan yang berbahaya.
Setiap akun dalam struct AccountInfo
Solana berisi field owner yang mengidentifikasi program mana yang mengendalikan akun tersebut. Pemeriksaan pemilik memastikan field owner
ini cocok dengan program_id
yang diharapkan sebelum program Anda mempercayai data akun tersebut.
Struct AccountInfo
berisi beberapa field, termasuk owner, yang merepresentasikan program yang memiliki akun tersebut. Pemeriksaan pemilik memastikan bahwa field owner
dalam AccountInfo
cocok dengan program_id
yang diharapkan.
Tanpa pemeriksaan pemilik, penyerang dapat membuat "replika" sempurna dari struktur data akun Anda, lengkap dengan diskriminator yang benar dan semua field yang tepat, dan menggunakannya untuk memanipulasi instruksi yang bergantung pada validasi data. Ini seperti seseorang membuat ID palsu yang terlihat identik dengan yang asli, tetapi dikendalikan oleh otoritas yang salah.
Anchor
Perhatikan instruksi rentan ini yang mengeksekusi logika berdasarkan owner
dari sebuah program_account
:
#[program]
pub mod insecure_check{
use super::*;
//..
pub fn instruction(ctx: Context<Instruction>) -> Result<()> {
let account_data = ctx.accounts.program_account.try_borrow_data()?;
let mut account_data_slice: &[u8] = &account_data;
let account_state = ProgramAccount::try_deserialize(&mut account_data_slice)?;
if account_state.owner != ctx.accounts.owner.key() {
return Err(ProgramError::InvalidArgument.into());
}
//..do something
Ok(())
}
//..
}
#[derive(Accounts)]
pub struct Instruction<'info> {
pub owner: Signer<'info>,
#[account(mut)]
/// CHECK: This account will not be checked by Anchor
pub program_account: UncheckedAccount<'info>,
}
#[account]
pub struct ProgramAccount {
owner: Pubkey,
}
Tipe UncheckedAccount
adalah cara Anchor untuk mengatakan "Saya tidak memeriksa apa pun, tangani dengan sangat hati-hati." Meskipun data akun mungkin terdeserialize dengan sempurna dan terlihat sah, pemeriksaan pemilik yang hilang menciptakan kerentanan kritis.
Penyerang dapat membuat akun mereka sendiri dengan struktur data yang identik dan meneruskannya ke instruksi Anda. Program Anda akan dengan senang hati memeriksa bidang kepemilikan, tetapi karena penyerang mengendalikan akun tersebut, mereka dapat melakukan apa pun yang mereka inginkan di dalam instruksi.
Perbaikannya sederhana namun penting: selalu verifikasi bahwa akun dimiliki oleh program Anda sebelum mempercayai isinya.
Ini sangat mudah dengan Anchor
karena dimungkinkan untuk melakukan pemeriksaan ini langsung di struct akun dengan hanya mengubah UncheckedAccount
menjadi ProgramAccount
seperti ini:
#[derive(Accounts)]
pub struct Instruction<'info> {
pub owner: Signer<'info>,
#[account(mut)]
/// CHECK: This account will not be checked by Anchor
pub program_account: Account<'info, ProgramAccount>,
}
Atau Anda dapat menambahkan batasan akun owner
seperti ini:
#[derive(Accounts)]
pub struct Instruction<'info> {
pub owner: Signer<'info>,
#[account(mut, owner = ID)]
/// CHECK: This account will not be checked by Anchor
pub program_account: UncheckedAccount<'info>,
}
Atau Anda dapat menambahkan pemeriksaan pemilik dalam instruksi menggunakan pemeriksaan ctx.accounts.program_account.owner
seperti ini:
pub fn update_ownership(ctx: Context<UpdateOwnership>) -> Result<()> {
if ctx.accounts.program_account.owner != ID {
return Err(ProgramError::IncorrectProgramId.into());
}
//..do something
Ok(())
}
Dengan menambahkan pemeriksaan ini, handler instruksi hanya akan melanjutkan jika akun memiliki program_id
yang benar. Jika akun tidak dimiliki oleh program kita, transaksi akan gagal.
Pinocchio
Dalam Pinocchio, karena kita tidak memiliki kemungkinan untuk menambahkan pemeriksaan keamanan langsung di dalam struct akun, kita terpaksa melakukannya dalam logika instruksi.
Kita dapat melakukannya dengan cara yang sangat mirip dengan anchor dengan menggunakan fungsi is_owned_by()
seperti ini:
if !self.accounts.owner.is_owned_by(ID) {
return Err(ProgramError::IncorrectProgramId.into());
}