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
中存储的value
TipInstruction
-
两种账户类型,
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