blockchainfreemedium

False Bidding

hackthebox

Task: Become keyOwner in AuctionHouse contract by calling claimPrize() as top bidder after timeout passes. Solution: Exploit uint32 overflow on timeout variable (Solidity 0.7.x has no overflow protection) by performing 16 bid-withdraw cycles, bypassing blacklist by rejecting ETH in receive() function.

$ ls tags/ techniques/
uint32_overflowselective_payability_togglebid_withdraw_loopsingle_transaction_attackblacklist_bypass_via_revert

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

False Bidding — HackTheBox

Description

After weeks of stealthy spying, Alex's efforts finally paid off. Not only did she manage to steal the key, but she also overheard the rival group talking about a secret auction being held in a nearby town. According to them, the third and final key to the coveted secret treasure would be up for grabs at the auction. The group was excited by the news and immediately set out for the town. Upon arrival, they found a bustling market filled with all sorts of strange and exotic items. They made their way to the auction house, where they found a large crowd of treasure hunters all vying for the final key. The group watched as the auctioneer began the bidding. They knew that they had to get the key no matter what it took.

Goal: Become keyOwner in the AuctionHouse contract — call claimPrize() as top bidder, provided that timeout has already passed.

Analysis

Contracts

Setup.sol — deploys AuctionHouse with 0.5 ETH. Checks solution: isSolved(player) returns true when TARGET.keyOwner() == player.

AuctionHouse.sol — auction contract with several critical vulnerabilities:

contract AuctionHouse { struct Key { address owner; } struct Bidder { address addr; uint64 bid; } Key private phoenixKey = Key(address(0)); uint32 public timeout; // ← uint32, max ~4.29 billion Bidder[] public bidders; mapping(address => bool) private blacklisted; uint32 public constant YEAR = 31556926; // ← ~31.5 million constructor() payable { timeout = uint32(block.timestamp); // ← ~3.82 billion (2026) _newBidder(msg.sender, 0.5 ether); } receive() external payable { if ((uint64(msg.value) >= 2 * topBidder().bid) && (msg.sender != topBidder().addr) && (!blacklisted[msg.sender]) && (_isPayable(msg.sender))) { _newBidder(msg.sender, uint64(msg.value)); timeout += YEAR; // ← overflow after ~16 iterations } } ...

$ grep --similar

Similar writeups