The vulnerable pattern
Imagine a contract that stakes and unstakes tokens on behalf of a user:Rust: vulnerable flow
How the exploit happens
Notice that the user cannot callunstake_callback directly, because there is a #[callback_result] argument. This is a special argument that the NEAR runtime fills in with the result of the preceding promise, rather than an argument supplied by the caller.
However, the user can still create a malicious contract with a function call_unstake_callback that calls unstake_callback directly, passing as parameters the original user and amount, and a positive result.
In this case, the #[callback_result] will be positive, and thus the function unstake_callback will credit the user with the unstaked amount, even though the unstaking failed.
The safe pattern
In Rust, mark the callback with#[private]. This checks that the immediate caller of a public function is your own contract:
#[private] means: only the env::current_account_id() can call this function. If any other account tries to call it, the NEAR runtime will reject the call.
For calls that attach NEAR tokens, see refund NEAR tokens for the failure path.