All incidents

FlippazOne ungated ownerWithdrawAllTo lets attacker drain 1.15 ETH

Share
Jul 05, 2022 19:30 UTCAttackLoss: 1.15 ETHManually checked1 exploit txWindow: Atomic

Root Cause Analysis

FlippazOne ungated ownerWithdrawAllTo lets attacker drain 1.15 ETH

1. Incident Overview TL;DR

On Ethereum mainnet block 15084458, a helper contract at 0xb314fd4ac6e10a7e27929cbc8db96743739c82b6, controlled by EOA 0x0000000a5aab7e0b99e8b30028d790de05da0f09, invoked the public function ownerWithdrawAllTo(address) on the FlippazOne NFT auction contract at 0xe85a08cf316f695ebe7c13736c8cc38a7cc3e944. In a single transaction (0x670da209fb1168941c4565a9a86f87d1011b24b857ea64f658b126a43f031fa0), FlippazOne sent its entire 1.15 ETH balance to the helper, which immediately forwarded the same amount to the attacker EOA. Using on-chain balance deltas and gas data, the attacker’s net ETH balance increased by approximately 1.1453 ETH after paying transaction fees.

The root cause is a missing access-control check on FlippazOne’s withdrawal functions. The functions ownerWithdraw(), ownerWithdrawTo(address), ownerWithdrawAll(), and ownerWithdrawAllTo(address) are declared public and lack any onlyOwner-style restriction, allowing any caller to withdraw proceeds or even the contract’s full ETH balance. The adversary exploited this by calling ownerWithdrawAllTo(helper) via a helper contract, turning a latent “anyone-can-drain” condition into a realized theft of funds.

2. Key Background

FlippazOne is a verified ERC721-based NFT auction contract deployed at 0xe85a08cf316f695ebe7c13736c8cc38a7cc3e944. It manages bidding, buy-now functionality, and settlement for a single NFT, holding ETH from bidders and forwarding proceeds to the project owner at the end of the auction. The contract inherits standard OpenZeppelin ERC721 components and adds auction-specific state such as highestBid, highestBidder, auctionEnded, and associated withdrawal utilities.

The contract exposes the following owner withdrawal functions, all marked public:

// Extract from FlippazOne Contract.sol (verified source)
function ownerWithdraw() public  {
    require(auctionEnded || block.timestamp > auctionEndTimestamp, "Cannot withdraw until auction is ended");
    (bool success, ) = owner().call{value: highestBid}("");
    require(success, "Failed to withdraw funds.");
}

function ownerWithdrawTo(address toAddress) public  {
    require(auctionEnded || block.timestamp > auctionEndTimestamp, "Cannot withdraw until auction is ended");
    (bool success, ) = toAddress.call{value: highestBid}("");
    require(success, "Failed to withdraw funds.");
}

function ownerWithdrawAll() public  {
    (bool success, ) = owner().call{value: address(this).balance}("");
    require(success, "Failed to withdraw funds.");
}

function ownerWithdrawAllTo(address toAddress) public  {
    (bool success, ) = toAddress.call{value: address(this).balance}("");
    require(success, "Failed to withdraw funds.");
}

None of these functions enforce msg.sender == owner() or any equivalent access-control condition. As a result, any unprivileged caller can trigger a transfer of ETH from the contract balance either to the configured owner or to an arbitrary recipient address.

The adversary uses a separate helper contract deployed at 0xb314fd4ac6e10a7e27929cbc8db96743739c82b6. The seed transaction metadata and call trace show that:

  • The EOA 0x0000000a5aab7e0b99e8b30028d790de05da0f09 sends a type-2 transaction to the helper with zero ETH value and a complex calldata blob.
  • The helper decodes this calldata and issues a call into FlippazOne with selector 0x84097393, which, based on the verified ABI, corresponds to ownerWithdrawAllTo(address).
  • The helper passes its own address as the toAddress parameter, receiving the full contract balance and then forwarding it to the EOA.

The relevant portion of the seed transaction’s call trace is:

// Excerpt from callTracer trace for tx 0x670d…fa0
{
  "from": "0x0000000a5aab7e0b99e8b30028d790de05da0f09",
  "to": "0xb314fd4ac6e10a7e27929cbc8db96743739c82b6",
  "value": "0x0",
  "input": "0x442d9a20…",        // helper entrypoint
  "gasUsed": "0xb725",
  "calls": [
    {
      "from": "0xb314fd4ac6e10a7e27929cbc8db96743739c82b6",
      "to": "0xe85a08cf316f695ebe7c13736c8cc38a7cc3e944",
      "type": "CALL",
      "input": "0x84097393 000000000000000000000000b314fd4ac6e10a7e27929cbc8db96743739c82b6",
      "value": "0x0",
      "calls": [
        {
          "from": "0xe85a08cf316f695ebe7c13736c8cc38a7cc3e944",
          "to": "0xb314fd4ac6e10a7e27929cbc8db96743739c82b6",
          "type": "CALL",
          "value": "0xff59ee833b30000"   // 1.15 ETH
        }
      ]
    },
    {
      "from": "0xb314fd4ac6e10a7e27929cbc8db96743739c82b6",
      "to": "0x0000000a5aab7e0b99e8b30028d790de05da0f09",
      "type": "CALL",
      "value": "0xff59ee833b30000"       // forward 1.15 ETH to EOA
    }
  ]
}

This trace confirms the execution path: EOA → helper → FlippazOne → helper → EOA, with exactly 1.15 ETH moving out of the victim contract and ultimately into the attacker’s externally owned account.

3. Vulnerability Analysis & Root Cause Summary

The vulnerability is a classic “missing access control on privileged withdrawal functions.” FlippazOne’s owner withdrawal functions are implemented as ordinary public functions, with no restriction tying invocation to the contract owner or any authorized role. In particular, ownerWithdrawAllTo(address toAddress) blindly sends address(this).balance to the supplied toAddress for any caller, guarded only by a simple success check on the low-level call.

The intended invariant for a single-NFT auction like FlippazOne is that only the auction owner or a formally designated recipient can withdraw proceeds or the entire ETH balance accumulated in the contract. By failing to assert msg.sender == owner() (or equivalent) in ownerWithdraw* and ownerWithdrawAll*, the contract allows arbitrary, unprivileged accounts to drain its ETH holdings at any time the contract holds funds. This design mistake converts a privileged action into an “anyone-can-take” opportunity.

The adversary did not need to exploit any subtle reentrancy or price-manipulation behavior. Instead, they simply constructed a transaction via a helper contract that invoked ownerWithdrawAllTo(helper), collected the full contract balance, and forwarded it to their EOA, turning the latent access-control bug into a realized theft.

4. Detailed Root Cause Analysis

From an invariant perspective, the protocol should satisfy:

  • Withdrawal invariant: Only the contract owner (or a designated address under explicit policy) may withdraw auction proceeds or the full ETH balance from FlippazOne. Unprivileged callers must not be able to transfer ETH directly from the contract to arbitrary addresses.

The concrete breakpoint in the code is the family of unguarded, public withdrawal functions shown earlier. None of these functions enforce ownership or role checks. The most severe of them, ownerWithdrawAllTo(address toAddress), performs:

// Core breakpoint: anyone can drain full balance to any address
function ownerWithdrawAllTo(address toAddress) public  {
    (bool success, ) = toAddress.call{value: address(this).balance}("");
    require(success, "Failed to withdraw funds.");
}

Because this function is public, any account — including arbitrary EOAs and contracts — can call it. When the contract holds ETH (e.g., from auctions or bids), a single call to ownerWithdrawAllTo(attacker) will send the entire balance to the attacker-controlled address, regardless of who the contract owner is.

The on-chain state immediately before the exploit transaction (block 15084458) satisfies the preconditions of the vulnerability:

  • The FlippazOne contract holds exactly 1.15 ETH, as shown by native balance deltas for the victim:
// Native balance changes around the exploit transaction
{
  "address": "0xe85a08cf316f695ebe7c13736c8cc38a7cc3e944",
  "before_wei": "1150000000000000000",
  "after_wei": "0",
  "delta_wei": "-1150000000000000000"
}
  • The adversary controls EOA 0x0000000a5aab7e0b99e8b30028d790de05da0f09 and uses helper contract 0xb314fd4ac6e10a7e27929cbc8db96743739c82b6 as an orchestrator, with prior interactions between them observed in the EOA’s tx history.

In the exploit transaction 0x670da209fb1168941c4565a9a86f87d1011b24b857ea64f658b126a43f031fa0:

  • The EOA sends a type-2 transaction to the helper with zero ETH value and calldata encoded to instruct the helper to call FlippazOne with selector 0x84097393 (ownerWithdrawAllTo(address)), passing the helper’s address as the parameter.
  • The helper executes this call into FlippazOne. Because ownerWithdrawAllTo is public and unguarded, the call succeeds and transfers the entire contract balance (1.15 ETH) to the helper.
  • The helper then immediately sends the same 1.15 ETH to the EOA, finalizing the drain.

There is no reliance on special permissions, governance actions, mempool manipulation, or non-standard infrastructure. The exploit leverages a straightforward, permissionless call into a misconfigured public function.

5. Adversary Flow Analysis

The adversary-related cluster consists of:

  • EOA (attacker): 0x0000000a5aab7e0b99e8b30028d790de05da0f09
  • Helper contract (attacker-owned): 0xb314fd4ac6e10a7e27929cbc8db96743739c82b6
  • Victim contract: 0xe85a08cf316f695ebe7c13736c8cc38a7cc3e944 (FlippazOne NFT auction)

The end-to-end flow for the seed ACT transaction is:

  1. Initial setup (funding and helper use): Prior to the exploit, the attacker EOA has sufficient ETH and interacts with the helper contract, establishing the control relationship between the EOA and the helper.
  2. Exploit transaction submission (EOA → helper):
    • The attacker submits transaction 0x670da209fb1168941c4565a9a86f87d1011b24b857ea64f658b126a43f031fa0 on Ethereum mainnet (chainid 1), sending 0 ETH to the helper contract with calldata beginning 0x442d9a20….
    • This calldata encodes a helper function that in turn embeds the call data for ownerWithdrawAllTo(address) with the helper’s own address as parameter.
  3. Helper calls victim (helper → FlippazOne):
    • Inside the transaction, the helper calls the victim contract with:
      • to = 0xe85a08cf316f695ebe7c13736c8cc38a7cc3e944
      • input = 0x84097393 000000000000000000000000b314fd4ac6e10a7e27929cbc8db96743739c82b6
    • Selector 0x84097393 maps to ownerWithdrawAllTo(address) in the verified FlippazOne ABI, so this call invokes ownerWithdrawAllTo(helper).
  4. Victim sends ETH to helper (FlippazOne → helper):
    • FlippazOne executes ownerWithdrawAllTo(helper) and sends address(this).balance (1.15 ETH) to the helper.
    • The call trace records this as a CALL from the victim to the helper with value = 0xff59ee833b30000 wei.
  5. Helper forwards ETH to attacker (helper → EOA):
    • After receiving 1.15 ETH, the helper immediately calls the attacker EOA with the same value, forwarding all funds out of the helper.
    • The call trace shows a final CALL from helper to EOA with value = 0xff59ee833b30000 wei.

The net effect is a single helper-orchestrated, adversary-crafted transaction that drains the victim’s entire ETH balance to the attacker’s EOA without any privileged rights or governance actions. Because the vulnerable function is public and unprotected, any unprivileged account could replicate this strategy, satisfying the “anyone-can-take” (ACT) condition.

On profitability, the attacker’s balance and fee profile are:

// Attacker EOA native balance deltas and gas/fee computation
{
  "attacker_balance": {
    "before_wei": "12246898012606991696",
    "after_wei":  "13392209512606851041",
    "delta_wei":  "1145311499999859345"
  },
  "tx_fee": {
    "gasUsed": 46885,
    "effectiveGasPrice_wei": 100000000003,
    "fee_wei": "4688500000140655"
  },
  "derived_profit_ETH": {
    "before": "12.246898012606991696",
    "after":  "13.392209512606851041",
    "delta":  "1.145311499999859345",
    "fees":   "0.004688500000140655"
  }
}

These figures confirm that the adversary’s net portfolio value in ETH strictly increases after fees, satisfying a clear profit-based success predicate.

6. Impact & Losses

The direct on-chain impact is:

  • The FlippazOne victim contract at 0xe85a08cf316f695ebe7c13736c8cc38a7cc3e944 loses its entire ETH balance of 1.15 ETH during the exploit transaction.
  • The 1.15 ETH moves from the victim to the attacker-controlled helper contract and then to the attacker EOA 0x0000000a5aab7e0b99e8b30028d790de05da0f09.

Measured in ETH:

  • Total loss from victim contract: 1.15 ETH (1150000000000000000 wei)
  • Net profit to attacker after gas: approximately 1.145311499999859345 ETH

Because the vulnerable function withdraws the entire contract balance, the scale of loss is bounded only by how much ETH the contract happens to hold at call time. In this concrete incident, that amount is 1.15 ETH; under different conditions, the same bug could be used to drain larger balances as they accumulate.

7. References

  • Victim contract (FlippazOne NFT auction):
    • Address: 0xe85a08cf316f695ebe7c13736c8cc38a7cc3e944 (Ethereum mainnet)
    • Evidence: Verified source code (Contract.sol) including public ownerWithdraw* and ownerWithdrawAll* functions without access control.
  • Adversary-related accounts:
    • EOA (attacker): 0x0000000a5aab7e0b99e8b30028d790de05da0f09
    • Helper contract: 0xb314fd4ac6e10a7e27929cbc8db96743739c82b6 (unverified, analyzed via decompilation and traces).
  • Exploit transaction (ACT realization):
    • Hash: 0x670da209fb1168941c4565a9a86f87d1011b24b857ea64f658b126a43f031fa0
    • Chain: Ethereum mainnet (chainid 1)
    • Evidence: Etherscan-style transaction metadata (type-2 tx from attacker EOA to helper with 0 ETH value), QuickNode debug_traceTransaction callTracer output showing ownerWithdrawAllTo(helper) and subsequent ETH forwarding.
  • Balance and profitability evidence:
    • Native balance diffs around the exploit transaction showing:
      • Victim loses 1.15 ETH (1150000000000000000 wei)
      • Attacker gains 1.1453114999999859345 ETH
      • Coinbase receives 0.004688500000140655 ETH in fees
    • Transaction receipt giving gasUsed = 46885 and effectiveGasPrice = 100000000003 wei, from which the gas fee and profit figures are computed.

Collectively, these artifacts demonstrate that the incident is a genuine, reproducible ACT opportunity and that the root cause is a missing access-control guard on public withdrawal functions in the FlippazOne contract.