From 5f67b81a3f06cca8c6f7c6a0eddf900e7b0cbe0a Mon Sep 17 00:00:00 2001 From: sprtd Date: Mon, 21 Oct 2024 13:48:31 +0100 Subject: [PATCH 1/3] feat(IVulnerableToken): create vulnerable mint_token --- .gitignore | 3 ++- src/no_owner_token.cairo | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/no_owner_token.cairo diff --git a/.gitignore b/.gitignore index 3f9ab24..754cb91 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target -.snfoundry.toml \ No newline at end of file +.snfoundry.toml +.snfoundry_cache \ No newline at end of file diff --git a/src/no_owner_token.cairo b/src/no_owner_token.cairo new file mode 100644 index 0000000..a18fd1d --- /dev/null +++ b/src/no_owner_token.cairo @@ -0,0 +1,37 @@ +// NB +// this is not an ERC-20 token contract as it doesn't implement the ERC-20 standard +// This minimalistic VulnerableToken contract is a token contract whose `mint_token` method lacks the requisite access control to for the execution of critical operations like minting/burning of tokens; in our case here, we are targetting the increase total supply operation of the contract. +// It is being used here to showcase the security vulnerability of exposing critical functions without access control + +#[starknet::interface] +pub trait IVulnerableToken { + fn mint_token(ref self: T); + fn get_token_supply(self: @T) -> u256; +} + +#[starknet::contract] +mod VulnerableToken { + use starknet::ContractAddress; + const MINT_AMOUNT: u256 = 1000; + + #[storage] + struct Storage { + total_supply: u256 + } + + + #[abi(embed_v0)] + impl VulnerableTokenImpl of super::IVulnerableToken { + // increase token total supply by MINT_AMOUNT + fn mint_token(ref self: ContractState) { + let current_supply: u256 = self.total_supply.read(); + self.total_supply.write(current_supply + MINT_AMOUNT) + } + + // get total supply of token + fn get_token_supply(self: @ContractState) -> u256 { + self.total_supply.read() + } + } +} + From b7e787d06c3086ae4f86ad31f0e28b16d9b85ca1 Mon Sep 17 00:00:00 2001 From: sprtd Date: Mon, 21 Oct 2024 13:58:42 +0100 Subject: [PATCH 2/3] feat(VulnerableStake): add vulnerable stake --- src/lib.cairo | 2 + src/vulnerable_stake.cairo | 41 +++++++++++++++++++ ...ner_token.cairo => vulnerable_token.cairo} | 1 - 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/vulnerable_stake.cairo rename src/{no_owner_token.cairo => vulnerable_token.cairo} (97%) diff --git a/src/lib.cairo b/src/lib.cairo index 7c80cbf..2d65d28 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -10,4 +10,6 @@ pub mod ownable_counter; pub mod ownable; pub mod addition; pub mod aggregator; +pub mod vulnerable_token; +pub mod vulnerable_stake; diff --git a/src/vulnerable_stake.cairo b/src/vulnerable_stake.cairo new file mode 100644 index 0000000..43a2cfb --- /dev/null +++ b/src/vulnerable_stake.cairo @@ -0,0 +1,41 @@ +use starknet::ContractAddress; +#[starknet::interface] +pub trait IVulnerableStake { + fn deposit(ref self: T, amount: u256, stake_addr: ContractAddress); + fn withdraw(ref self: T, amount: u256, reward_addr: ContractAddress); +} + + +#[derive(Debug, Drop, Serde, Copy, starknet::Store)] +pub struct StakerInfo { + pub stake_addr: ContractAddress, + pub reward_amount: u256, + pub stake_amount: u256, + pub unclaimed_rewards_own: u256, +} + + +#[starknet::contract] +mod VulnerableStake { + use starknet::{ContractAddress, get_caller_address}; + use starknet::storage::{StoragePointerWriteAccess, StoragePathEntry, Map}; + use super::{StakerInfo, IVulnerableStake,}; + #[storage] + struct Storage { + staker_info: Map, + } + + #[abi(embed_v0)] + impl StakeImpl of IVulnerableStake { + fn deposit(ref self: ContractState, amount: u256, stake_addr: ContractAddress) { + let staker_info = StakerInfo { + stake_addr, reward_amount: 0, stake_amount: amount, unclaimed_rewards_own: 0 + }; + let caller = get_caller_address(); + self.staker_info.entry(caller).write(staker_info); + } + + + fn withdraw(ref self: ContractState, amount: u256, reward_addr: ContractAddress) {} + } +} diff --git a/src/no_owner_token.cairo b/src/vulnerable_token.cairo similarity index 97% rename from src/no_owner_token.cairo rename to src/vulnerable_token.cairo index a18fd1d..78a9e0e 100644 --- a/src/no_owner_token.cairo +++ b/src/vulnerable_token.cairo @@ -11,7 +11,6 @@ pub trait IVulnerableToken { #[starknet::contract] mod VulnerableToken { - use starknet::ContractAddress; const MINT_AMOUNT: u256 = 1000; #[storage] From 27f9c25b6c40a92f74a70a2cd788cbacb759586d Mon Sep 17 00:00:00 2001 From: sprtd Date: Mon, 21 Oct 2024 14:11:27 +0100 Subject: [PATCH 3/3] chore: add desc comments --- src/attack_counter.cairo | 48 ++++++++++++++++++++++++++++++++++++++ src/lib.cairo | 1 + src/vulnerable_stake.cairo | 2 ++ 3 files changed, 51 insertions(+) create mode 100644 src/attack_counter.cairo diff --git a/src/attack_counter.cairo b/src/attack_counter.cairo new file mode 100644 index 0000000..1e070f7 --- /dev/null +++ b/src/attack_counter.cairo @@ -0,0 +1,48 @@ +// AttackCounter Contract +// Whereas a function is considered as a read-only function if its self is a snapshot of storage as depicted thus (self: @TContractState), it is not true that such a functin cannot modify state +// By leveraging syscalls like `call_contract_syscall`, such function can modify state +// In this contract, we showcase this possibility of using `call_contract_syscall` in our AttackCounter contract to modify the `count` state of our simple counter contract + +#[starknet::interface] +pub trait IAttackCounter { + // get count - retrieve the count from storage + // a read-only function + fn counter_count(self: @TContractState) -> u32; + + // set count + fn attack_count(self: @TContractState); +} + + +#[starknet::contract] +pub mod AttackCounter { + use crate::counter::{ ICounterDispatcher, ICounterDispatcherTrait}; + use starknet::{ContractAddress, syscalls:: call_contract_syscall}; + #[storage] + struct Storage { + counter_address: ContractAddress + } + + #[constructor] + fn constructor(ref self: ContractState, counter_addr: ContractAddress) { + self.counter_address.write(counter_addr) + } + + #[abi(embed_v0)] + impl CounterImpl of super::IAttackCounter { + fn counter_count(self: @ContractState) -> u32 { + let counter_addr = self.counter_address.read(); + ICounterDispatcher { contract_address: counter_addr }.get_count() + } + + fn attack_count(self: @ContractState) { + let counter_addr = self.counter_address.read(); + let selector = selector!("set_count"); + + let mut args: Array = array![]; + let value = 100; + value.serialize(ref args); + call_contract_syscall(counter_addr, selector, args.span()); + } + } +} \ No newline at end of file diff --git a/src/lib.cairo b/src/lib.cairo index 2d65d28..895b24b 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -12,4 +12,5 @@ pub mod addition; pub mod aggregator; pub mod vulnerable_token; pub mod vulnerable_stake; +pub mod attack_counter; diff --git a/src/vulnerable_stake.cairo b/src/vulnerable_stake.cairo index 43a2cfb..978eb31 100644 --- a/src/vulnerable_stake.cairo +++ b/src/vulnerable_stake.cairo @@ -1,3 +1,5 @@ +// VulnerableStake contract +// Both deposit and withdraw functions of this contract contains flawed logic use starknet::ContractAddress; #[starknet::interface] pub trait IVulnerableStake {