Skip to content

Latest commit

 

History

History
92 lines (75 loc) · 5.17 KB

044.md

File metadata and controls

92 lines (75 loc) · 5.17 KB

Brief Honey Penguin

Medium

TimeBuffer Exploit Leading to Continuous Auction Extension

Summary

The function createBid() in the contract NounsAuctionHouseV3 allows bidders to extend the auction duration by repeatedly submitting bids within the _timeBuffer period just before the auction ends. The current logic:

 bool extended = _auction.endTime - block.timestamp < _timeBuffer;
if (extended) {
    auctionStorage.endTime = _auction.endTime = uint40(block.timestamp + _timeBuffer);
    emit AuctionExtended(_auction.nounId, _auction.endTime);
}

means that any bid received within the _timeBuffer period before the auction end will extend the auction by _timeBuffer seconds. This creates a vulnerability that allows the auction to be indefinitely extended, clipping into other auction periods causing a DOS due to the inability to settle the auction or create a new one.

Root Cause

https://github.com/sherlock-audit/2024-11-nounsdao/blob/main/nouns-monorepo/packages/nouns-contracts/contracts/NounsAuctionHouseV3.sol#L145-L183 in NounsAuctionHouseV3.sol#NounsAuctionHouseV3::createBid()

bool extended = _auction.endTime - block.timestamp < _timeBuffer;
if (extended) {
    auctionStorage.endTime = _auction.endTime = uint40(block.timestamp + _timeBuffer);
    emit AuctionExtended(_auction.nounId, _auction.endTime);
}

Attack Path

  1. maliciousBidder creates a bid (the bid amount will always be incremented by the minimum bid percent) close to the end of the current auction bid endtime.
  2. since the bid was created in within the time buffer period, the current auction end time increases
  3. maliciousBidder repeats the process and cause the current auction end time to be extended indefinitely

Impact

A single malicious bidder can exploit the _timeBuffer mechanism to indefinitely delay the conclusion of the auction, effectively causing a Denial of Service (DoS) for other participants. By placing bids just before the end of the auction, the attacker can ensure that the auction is continuously extended, making it impossible for other participants to finalize the auction and claim the asset. The protocol is meant to auction an NFT every 24 hours. If the current auction keeps getting extended, it will overlap into the scheduled time for the next auction, which could cause delays and disrupt the entire auction schedule. This continuous delay may damage the protocol's reputation and undermine its reliability, as users could lose faith in the platform's ability to maintain a consistent and predictable auction schedule. This can also impact the 24hr scheduling and forwarding of streams

PoC

copy and paste this in NounsAuctionHouseV3Test

 function test_AuctionEndTimeExtensionLimit() public {
        address bidder = makeAddr('bidder');
        // we give the bidder 1eth to start
        vm.deal((bidder), 1 ether);
        vm.startPrank(bidder);
        uint128 nounId = auction.auction().nounId;

        uint40 startTime = auction.auction().startTime;
        // we wait till just shy of the end time and then place a bid
        vm.warp(startTime + 86398 /*shy of 24 hrs (86400)*/);
        for (uint256 index = 0; index < 1000 /*do this 1000 time*/; index++) {
            uint128 auction_bid = auction.auction().amount;

            uint128 bid = auction_bid + ((auction_bid * auction.minBidIncrementPercentage()) / 100); //increase by 2%
            //after creating a bid, it increases by the timeBuffer giving us more time
            auction.createBid{ value: bid + 1 }(nounId);
            // we wait till just shy of the new end time and then we place a bid again
            vm.warp(block.timestamp + 288 /*shy of 5 mins (300)*/);
        }
        uint40 endTimeAfterBid = auction.auction().endTime;
        console.log((endTimeAfterBid - startTime), auction.auction().amount);
        console.log(address(bidder).balance, address(auction).balance);
        // while this is on going, we are unable to settle and create a new auction,
        // potentially DOS the auction and delaying the forwarding of stream
        vm.expectRevert("Auction hasn't completed");
        auction.settleCurrentAndCreateNewAuction();
        vm.stopPrank();
        // allowing use to indefinitely increase the current auction endtime
    }

then run the test forge test --mt test_AuctionEndTimeSurpasses24hrs -vvv

Mitigation

  • Limit Extensions: Set a maximum number of extensions that can be allowed for an auction. For example, allow the auction to be extended only 5 times before the end time is considered final.
mapping(uint256 => uint8) public extensionCount;
uint8 constant MAX_EXTENSIONS = 5;

if (_auction.endTime - block.timestamp < _timeBuffer && extensionCount[_auction.nounId] < MAX_EXTENSIONS) {
    extensionCount[_auction.nounId]++;
    auctionStorage.endTime = _auction.endTime = uint40(block.timestamp + _timeBuffer);
    emit AuctionExtended(_auction.nounId, _auction.endTime);
}
  • Dynamically Decrease Buffer Time Gradually: Reduce the _timeBuffer value progressively after each extension or Implement diminishing returns for extensions, which would limit the time window for new bids to extend the auction further and prevent indefinite extensions.