Solana Security Workshop
Setup - Full¶
Compiling the Contracts and Running the Exploits¶
- PoC 框架代码位于
pocs目录下 - VSCode 中
Ctrl+Shift+B再选择level -
或通过命令行的方式
-
Docker 内有部分命令缺失(e.g.
bash>m<)可能导致构建失败,可在本地构建后上传
Exploit Outline¶
初始持有 1 SOL,目标是获得更多的 SOL
Level 0 - A First Vulnerability¶
-
查看
WalletInstruction,初步了解程序的功能 -
程序入口点函数
processor::process_instruction反序列化instruction_data并调用指定函数 -
调用其它程序可通过
invoke()或invoke_signed()(当需要 PDA 作为 instruction 的 signer 时) -
withdraw中wallet、vault等账户均由调用者提供,且未检查账户wallet的owner,因而可以创建一个攻击者作为authority的wallet账户,从而调用withdraw获取 SOL
Exploit¶
参考资料¶
- Calling Between Programs | Solana Docs
- Environment in poc_framework - Rust
- Option & unwrap - Rust By Example
- Pubkey::find_program_address - Rust
Level 1 - Personal Vault¶
-
相比于 Level 0,
Wallet移除了vault,并保持了除vault外其它功能的一致性 -
withdraw中wallet_info、authority_info仍然由调用者提供,且只检查wallet的owner是否为对应程序以及wallet中存储的authority与提供的authority_info是否匹配,并没有检查authority_info是否为 signer
Exploit¶
Level 2 - Secure Personal Vault¶
-
在 Level 1 的基础上,修复了
withdraw未检查 signer 的问题 -
在
debug模式下编译程序,Rust 将对整型溢出抛出异常,而在release模式下,Rust 将进行 two's complement wrapping,以u8为例,结果等同于模 \(256\) - 可以通过
amount使wallet_info账户中的lamports下溢出来获取资金,并使destination_info账户中的lamports上溢出来减少其资金- 另外还需通过上溢出绕过检查
min_balance + amount > **wallet_info.lamports.borrow_mut()
- 另外还需通过上溢出绕过检查
- 推荐使用
checked_sub、checked_add
Exploit¶
参考资料¶
- Data Types - The Rust Programming Language
- anchor - How to avoid SendTransactionError "This transaction has already been processed" - Solana Stack Exchange
Level 3 - Tip Pool¶
-
查看
TipInstruction,初步了解程序的功能,任何人可以创建TipPool来接收 tips,资金存储在Vault中,withdraw时将依据TipPool中存储的valueTipInstruction
-
两种账户类型,
Vault和TipPool,注意到Vault的字段恰好能覆盖TipPool的字段deserialize根据给定数据类型解析,并更新 buffer,使其指向剩余字节
-
withdraw中未检查pool_info是否是TipPool类型的数据,因而可以传入Vault类型的数据 -
通过
initialize来控制Vault类型账户各个字段的值,并使用Vault类型的账户来代替TipPool进行withdraw -
可增加类型字段来避免 Account Confusion
Exploit¶
参考资料¶
- Solana Smart Contracts: Common Pitfalls and How to Avoid Them
- BorshDeserialize in borsh::de - Rust
- Program Derived Addresses (PDAs) | Solana Cookbook
Level 4 - SPL1-Token Vault¶
- 每一种类型的 SPL 代币通过创建一个
mint账户来声明,mint账户存储代币元数据,每个 SPL 代币账户关联mint账户- Associated Token Account Program 根据用户系统账户和
mint账户确定性地派生 SPL 代币账户。无论创建者,create_associated_token_account的所有者都是对应用户的系统账户 - 若 SPL 代币账户关联原生
mint(SOL),则账户 SOL 余额与代币余额保持一致
- Associated Token Account Program 根据用户系统账户和
-
spl_token在版本 3.1.1 有重要变更 👀 -
对比 3.1.1 和 3.1.0 的源码2,发现版本 3.1.1 主要新增了对提供的 SPL 代币程序 ID 的检查
check_program_account(token_program_id)?;,而token_program_id是可控的,那么在版本 3.1.0 可以部署恶意程序来操控数据 - 由
wallet_owner的公钥和wallet_program获得程序派生地址wallet_address,是持有 SPL 代币的账户地址 -
withdraw()中调用了spl_token::instruction::transfer_checked(),那么将spl_token指向可控程序,从而能够交换source和destination
Exploit¶
参考资料¶
- Supporting the SPL Token Standard
- Associated Token Account Program | Solana Program Library Docs
- TokenInstruction in spl_token::instruction - Rust
- spl_token::instruction - Rust
- instruction.rs - source
- invoke_signed in solana_sdk::program - Rust
- Program examples written in Rust