-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
710 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
[alias] | ||
wasm = "build --release --lib --target wasm32-unknown-unknown" | ||
unit-test = "test --lib" | ||
schema = "run --bin schema" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
[package] | ||
name = "oaksecurity-cosmwasm-ctf-05" | ||
version = "0.1.0" | ||
authors = ["Oak Security <info@oaksecurity.io>"] | ||
edition = "2021" | ||
|
||
exclude = [ | ||
# Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. | ||
"contract.wasm", | ||
"hash.txt", | ||
] | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[lib] | ||
crate-type = ["cdylib", "rlib"] | ||
|
||
[profile.release] | ||
opt-level = 3 | ||
debug = false | ||
rpath = false | ||
lto = true | ||
debug-assertions = false | ||
codegen-units = 1 | ||
panic = 'abort' | ||
incremental = false | ||
overflow-checks = true | ||
|
||
[features] | ||
# for more explicit tests, cargo test --features=backtraces | ||
backtraces = ["cosmwasm-std/backtraces"] | ||
# use library feature to disable all instantiate/execute/query exports | ||
library = [] | ||
|
||
[package.metadata.scripts] | ||
optimize = """docker run --rm -v "$(pwd)":/code \ | ||
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ | ||
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ | ||
cosmwasm/rust-optimizer:0.12.10 | ||
""" | ||
|
||
[dependencies] | ||
cosmwasm-schema = "1.1.3" | ||
cosmwasm-std = "1.1.3" | ||
cosmwasm-storage = "1.1.3" | ||
cw-storage-plus = "1.0.1" | ||
cw2 = "1.0.1" | ||
cw-utils = "1.0.1" | ||
schemars = "0.8.10" | ||
serde = { version = "1.0.145", default-features = false, features = ["derive"] } | ||
thiserror = { version = "1.0.31" } | ||
|
||
[dev-dependencies] | ||
cw-multi-test = "0.16.2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Awesomwasm 2023 CTF | ||
|
||
## Challenge 05: *Draupnir* | ||
|
||
Simplified vault where users can deposit and withdraw their tokens which will be internally accounted. | ||
The vault's `owner` can perform arbitrary actions through the `OwnerAction` entry point. | ||
In addition, a two step address transfer is implemented for the `owner` role. | ||
|
||
### Execute entry points: | ||
```rust | ||
pub enum ExecuteMsg { | ||
Deposit {}, | ||
Withdraw { amount: Uint128 }, | ||
OwnerAction { msg: CosmosMsg }, | ||
ProposeNewOwner { new_owner: String }, | ||
AcceptOwnership {}, | ||
DropOwnershipProposal {}, | ||
} | ||
``` | ||
|
||
Please check the challenge's [integration_tests](./src/integration_tests.rs) for expected usage examples. | ||
You can use these tests as a base to create your exploit Proof of Concept. | ||
|
||
**:house: Base scenario:** | ||
- The contract has been instantiated with zero funds. | ||
- `USER1` and `USER2` deposit `10_000` tokens each. | ||
- The owner role is assigned to the `ADMIN` address. | ||
|
||
**:star: Goal for the challenge:** | ||
- Demonstrate how an unprivileged user can drain all the funds inside the contract. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
fn main() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
#[cfg(not(feature = "library"))] | ||
use cosmwasm_std::{ | ||
coin, entry_point, to_binary, BankMsg, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, | ||
Response, StdResult, Uint128, | ||
}; | ||
|
||
use crate::error::ContractError; | ||
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; | ||
use crate::state::{assert_owner, State, BALANCES, STATE}; | ||
use cw_utils::must_pay; | ||
|
||
pub const DENOM: &str = "uawesome"; | ||
|
||
#[cfg_attr(not(feature = "library"), entry_point)] | ||
pub fn instantiate( | ||
deps: DepsMut, | ||
_env: Env, | ||
_info: MessageInfo, | ||
msg: InstantiateMsg, | ||
) -> Result<Response, ContractError> { | ||
let state = State { | ||
current_owner: deps.api.addr_validate(&msg.owner)?, | ||
proposed_owner: None, | ||
}; | ||
STATE.save(deps.storage, &state)?; | ||
|
||
Ok(Response::new() | ||
.add_attribute("action", "instantiate") | ||
.add_attribute("owner", msg.owner)) | ||
} | ||
|
||
#[cfg_attr(not(feature = "library"), entry_point)] | ||
pub fn execute( | ||
deps: DepsMut, | ||
_env: Env, | ||
info: MessageInfo, | ||
msg: ExecuteMsg, | ||
) -> Result<Response, ContractError> { | ||
match msg { | ||
ExecuteMsg::Deposit {} => deposit(deps, info), | ||
ExecuteMsg::Withdraw { amount } => withdraw(deps, info, amount), | ||
ExecuteMsg::OwnerAction { msg } => owner_action(deps, info, msg), | ||
ExecuteMsg::ProposeNewOwner { new_owner } => propose_owner(deps, info, new_owner), | ||
ExecuteMsg::AcceptOwnership {} => accept_owner(deps, info), | ||
ExecuteMsg::DropOwnershipProposal {} => drop_owner(deps, info), | ||
} | ||
} | ||
|
||
/// Deposit entry point for user | ||
pub fn deposit(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> { | ||
// validate denom | ||
let amount = must_pay(&info, DENOM).unwrap(); | ||
|
||
// increase total stake | ||
let mut user_balance = BALANCES | ||
.load(deps.storage, &info.sender) | ||
.unwrap_or_default(); | ||
user_balance += amount; | ||
|
||
BALANCES.save(deps.storage, &info.sender, &user_balance)?; | ||
|
||
Ok(Response::new() | ||
.add_attribute("action", "deposit") | ||
.add_attribute("user", info.sender) | ||
.add_attribute("amount", amount)) | ||
} | ||
|
||
/// Withdrawal entry point for user | ||
pub fn withdraw( | ||
deps: DepsMut, | ||
info: MessageInfo, | ||
amount: Uint128, | ||
) -> Result<Response, ContractError> { | ||
// decrease total stake | ||
let mut user_balance = BALANCES.load(deps.storage, &info.sender)?; | ||
|
||
// Cosmwasm's Uint128 checks math operations | ||
user_balance -= amount; | ||
|
||
BALANCES.save(deps.storage, &info.sender, &user_balance)?; | ||
|
||
let msg = BankMsg::Send { | ||
to_address: info.sender.to_string(), | ||
amount: vec![coin(amount.u128(), DENOM)], | ||
}; | ||
|
||
Ok(Response::new() | ||
.add_attribute("action", "withdraw") | ||
.add_attribute("user", info.sender) | ||
.add_attribute("amount", amount) | ||
.add_message(msg)) | ||
} | ||
|
||
/// Entry point for owner to execute arbitrary Cosmos messages | ||
pub fn owner_action( | ||
deps: DepsMut, | ||
info: MessageInfo, | ||
msg: CosmosMsg, | ||
) -> Result<Response, ContractError> { | ||
assert_owner(deps.storage, info.sender)?; | ||
|
||
Ok(Response::new() | ||
.add_attribute("action", "owner_action") | ||
.add_message(msg)) | ||
} | ||
|
||
/// Entry point for current owner to propose a new owner | ||
pub fn propose_owner( | ||
deps: DepsMut, | ||
info: MessageInfo, | ||
new_owner: String, | ||
) -> Result<Response, ContractError> { | ||
assert_owner(deps.storage, info.sender)?; | ||
|
||
STATE.update(deps.storage, |mut state| -> StdResult<_> { | ||
state.proposed_owner = Some(deps.api.addr_validate(&new_owner)?); | ||
Ok(state) | ||
})?; | ||
|
||
Ok(Response::new() | ||
.add_attribute("action", "propose_owner") | ||
.add_attribute("new proposal", new_owner)) | ||
} | ||
|
||
/// Entry point for new owner to accept a pending ownership transfer | ||
pub fn accept_owner(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> { | ||
let state = STATE.load(deps.storage)?; | ||
|
||
if state.proposed_owner != Some(info.sender.clone()) { | ||
ContractError::Unauthorized {}; | ||
} | ||
|
||
STATE.update(deps.storage, |mut state| -> StdResult<_> { | ||
state.current_owner = info.sender.clone(); | ||
state.proposed_owner = None; | ||
Ok(state) | ||
})?; | ||
|
||
Ok(Response::new() | ||
.add_attribute("action", "accept_owner") | ||
.add_attribute("new owner", info.sender)) | ||
} | ||
|
||
/// Entry point for current owner to drop pending ownership | ||
pub fn drop_owner(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> { | ||
assert_owner(deps.storage, info.sender)?; | ||
|
||
STATE.update(deps.storage, |mut state| -> StdResult<_> { | ||
state.proposed_owner = None; | ||
Ok(state) | ||
})?; | ||
|
||
Ok(Response::new().add_attribute("action", "drop_owner")) | ||
} | ||
|
||
#[cfg_attr(not(feature = "library"), entry_point)] | ||
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> { | ||
match msg { | ||
QueryMsg::State {} => to_binary(&query_state(deps)?), | ||
QueryMsg::UserBalance { address } => to_binary(&query_balance(deps, address)?), | ||
} | ||
} | ||
|
||
/// Returns user balance | ||
pub fn query_balance(deps: Deps, address: String) -> StdResult<Uint128> { | ||
let address = deps.api.addr_validate(&address)?; | ||
BALANCES.load(deps.storage, &address) | ||
} | ||
|
||
/// Returns contract state | ||
pub fn query_state(deps: Deps) -> StdResult<State> { | ||
STATE.load(deps.storage) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
use cosmwasm_std::StdError; | ||
use thiserror::Error; | ||
|
||
#[derive(Error, Debug)] | ||
pub enum ContractError { | ||
#[error("{0}")] | ||
Std(#[from] StdError), | ||
|
||
#[error("Unauthorized")] | ||
Unauthorized {}, | ||
} |
Oops, something went wrong.