本文深入探讨了 Anchor 框架中 init 和 init_if_needed 约束的工作原理,重点分析了 init_if_needed 可能导致的重初始化攻击风险。文章详细解释了攻击者如何通过使账户未初始化(例如,通过耗尽 lamports 或更改所有权)来强制重新初始化,并提供了避免此类漏洞的安全实践建议,强调在关键状态账户上应优先使用 init。
程序示例 (顺便说一句,我用 Neovim 🙂): github link
为了理解重新初始化攻击如何工作,我们首先必须理解 Anchor ⚓️中 init 和 init_if_needed 约束的内部运作方式。
init:简单来说,init 确保对于给定的地址(由 seeds 和 bump 决定),账户只被创建和初始化一次。init 在内部执行以下两组指令:
create_account 指令。Rent::minimum_balance 为免租金的 lamports 提供资金。#[account] 结构定义账户数据结构。##[account]
##[derive(InitSpace)]
pub struct User {
pub user_pubkey: Pubkey,
#[max_len(30)]
pub user_name: Option<String>,
pub balance: u64,
pub user_vault_bump: u8,
pub user_bump: u8,
}
##[account(
init,
payer = USER,
space = 8 + User::INIT_SPACE,
seeds = [
USER,
user.key().to_bytes().as_ref(),
chau_config.key().to_bytes().as_ref()
],
bump
)]
pub user_profile: Account<'info, User>,
init 约束的安全性 🛡️:init 约束的安全性是严格的,不像 init_if_needed。你不能创建一个假的 PDA 并将其传递给程序,也不能初始化同一个账户两次,因为这样做会抛出一个错误。
Anchor 通过评估以下条件来检查账户是否已初始化:
init 可以创建和初始化账户(因为它是一个新账户)。Anchor 如何检查 PDA 🔍:
User)。User。这个 discriminator 就像一个标签,以便 Anchor 可以将其反序列化回正确的结构。User PDA,如第一个示例代码所示,攻击者试图将一个恶意的 PDA 发送到 User 地址。Fake 账户的 discriminator,并将其与 User 的 discriminator 进行比较。如果 discriminator 不匹配,Anchor 将拒绝该账户并抛出一个错误。你可能(并且你应该)开始欣赏 Anchor。它在底层处理了很多重要的安全检查,以及账户的序列化和反序列化(以及其他一些易出错的地方)。
注意: 这里,由 Anchor 创建的 Account 是 AccountInfo (如下所示) 的一个包装器,它可以帮助 Anchor 验证程序的所有权。
pub struct AccountInfo<'a> {
/// Public key of the account
pub key: &'a Pubkey,
/// The lamports in the account. Modifiable by programs.
pub lamports: Rc<RefCell<&'a mut u64>>,
/// The data held in this account. Modifiable by programs.
pub data: Rc<RefCell<&'a mut [u8]>>,
/// Program that owns this account
pub owner: &'a Pubkey,
/// The epoch at which this account will next owe rent
pub rent_epoch: u64,
/// Was the transaction signed by this account's public key?
pub is_signer: bool,
/// Is the account writable?
pub is_writable: bool,
/// This account's data contains a loaded program (and is now read-only)
pub executable: bool,
}
init_if_needed:与 init 不同,init_if_needed 不那么严格,使用起来很灵活。init_if_needed 类似于 init,但它不像 init 那么严格。当你使用 init_if_needed 代替 init 时,Anchor 遵循以下检查:
注意:
当出现以下情况时,Anchor 认为一个账户 "未初始化":
- 缺少或无效的 8 字节 discriminator (与预期的哈希不匹配)。
- 账户所有权与预期的程序不匹配。
- 账户有零 lamports (实际上使其在链上不存在)。
在正常情况下,init_if_needed 用于可能需要延迟初始化的账户。例如:
##[account(
init_if_needed,
payer = user,
space = 8 + User::INIT_SPACE,
seeds = [b"user", user.key().as_ref()],
bump
)]
pub user_account: Account<'info, User>,
在这里,只有当账户不存在或未初始化时,才会安全地初始化该账户。
攻击者可以利用 init_if_needed 来:
init_if_needed 的指令来强制重新初始化,从而欺骗程序重置账户的状态。init_if_needed 的安全实践 🛡️:init_if_needed:
init 来防止重新初始化。init_if_needed 之前,手动验证账户是否已经初始化。##[account]
##[derive(InitSpace)]
pub struct User {
pub user_pubkey: Pubkey,
#[max_len(30)]
pub user_name: Option<String>,
pub balance: u64,
pub is_initialized: bool,
pub user_vault_bump: u8,
pub user_bump: u8,
}
require!(user_account.discriminator.is_empty(), AlreadyInitialized);
虽然 init_if_needed 提供了灵活性,但如果使用不当,它会带来风险。对于安全至关重要的账户,始终首选 init,只有在绝对必要时才使用 init_if_needed,并采取适当的保护措施。Anchor 的 discriminator 检查有助于防止一些攻击,但开发人员必须实施额外的检查,以充分保护他们的程序免受重新初始化攻击。
solana developer course <br/> rare skill blog <br/> init vs init_if_needed <br/> security concern in init_if_needed<br/> stackoverflow_discussion <br/>
https://github.com/baindlapranayraj/reinitialization-attack-demo
- 原文链接: github.com/baindlapranay...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!