The withdraw
function in the provided StarkNet auction contract is susceptible to a reentrancy attack. This issue arises because the contract allows external token transfers (via ERC20) before updating its internal state. As a result, an attacker could exploit the vulnerability by re-entering the contract and calling certain functions multiple times before the state is properly updated, potentially draining funds from the contract.
Affected function:
withdraw(ref self: ContractState)
In the withdraw
function, the contract makes external calls to transfer ERC20 tokens before updating critical state variables. Specifically, after initiating a transfer of tokens to the caller, the state change (like resetting bid_values
) occurs. This opens the door for a reentrancy attack, where an attacker can trigger the withdraw
function repeatedly, before the contract updates its internal state, leading to multiple withdrawals of funds.
Steps for reentrancy attack:
Step 1: An attacker places a bid on the auction and calls the withdraw
function to initiate a withdrawal.
Step 2: During the token transfer (before the contract state is updated), the attacker makes another call to the withdraw
function, using the first call to reenter.
Step 3: Since the contract's internal state has not been updated yet, the attacker is able to withdraw multiple times, draining more funds than they originally deposited.
Here’s a Proof of Concept (PoC) to demonstrate how a reentrancy attack could be exploited in the withdraw function. This PoC simulates an attacker exploiting the vulnerability by repeatedly calling withdraw before the contract updates its internal state.
Step 1: Bid: The attacker first bids on the auction by calling vulnerableAuction.bid()
. This ensures that the attacker has some funds stored in the auction contract.
Step 2: Withdraw: After placing the bid, the attacker calls withdraw()
on the vulnerable auction contract. This triggers the first withdrawal of funds from the contract.
Step 3: Reentrancy (Fallback Function): During the external token transfer in the withdraw function of the auction contract, control is passed back to the attacker’s fallback function. At this point, the attacker re-enters the withdraw
function and calls it again before the auction contract has a chance to update its internal state.
Since the vulnerable contract does not update its state before performing external calls, the attacker is able to withdraw funds multiple times, draining the contract’s balance beyond their original bid.
An attacker withdraw more funds than allowed, potentially draining the contract's ERC20 token balance.
It cause incorrect final balances for legitimate bidders and disrupt the fair functioning of the auction.
The contract will likely have to handle multiple calls, leading to gas inefficiencies.
Manual review.
Always update the contract's state before making external calls. For instance, the contract should update the bidder's balance or highest_bid before calling the transfer_from
function to avoid the contract being reentered during the external call.
Add a reentrancy guard to prevent the function from being called recursively. This guard can be implemented by setting a flag when the function is first called and resetting it after the execution completes.
Adhere to the "Checks-Effects-Interactions" design pattern to prevent reentrancy. Perform checks first, then update internal state, and only then make external calls.
Utilize well-established libraries such as OpenZeppelin’s ReentrancyGuard
to simplify the implementation and ensure that reentrancy is properly handled throughout the contract.
The `withdraw` function doesn't reset the `bid_values` to 0 after the withdraw. That means the bidder can call multiple time the `withdraw` function and receive the whole balance of the protocol.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.