Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Invalid

NFT Buyback Function Vulnerable to Griefing Attack

Relevant Context

The NFTLiquidator contract includes a buyback mechanism that allows users to recover their liquidated NFTs by paying 110% of the original debt. During a buyback, if there was a previous bid on the NFT, the contract attempts to refund the highest bidder before proceeding with the buyback transaction.

Finding Description

The buyBackNFT() function uses a direct transfer() call to refund the highest bidder's funds before processing the buyback. This creates a vulnerability where a malicious bidder can prevent buybacks by using a contract address that reverts when receiving ETH:

if (data.highestBidder != address(0)) {
payable(data.highestBidder).transfer(data.highestBid);
}

Impact Explanation

High. A malicious actor can completely block the NFT buyback mechanism, preventing legitimate users from recovering their NFTs. This could result in significant financial losses for users who would otherwise be willing and able to pay the premium to recover their assets.

Likelihood Explanation

High. The attack is straightforward to execute, requiring only a simple contract deployment that reverts on ETH receipt. There are no external dependencies, timing constraints, or complex prerequisites needed to exploit this vulnerability.

Proof of Concept

  1. Alice's NFT gets liquidated with a debt of 10 ETH

  2. Attacker deploys a contract that reverts when receiving ETH

  3. Attacker uses this contract to place a small bid on Alice's NFT

  4. Alice attempts to buy back her NFT by paying 11 ETH (110% of the debt)

  5. The buyBackNFT() transaction reverts when trying to refund the attacker's bid

  6. Alice is unable to recover her NFT, despite being willing to pay the premium

Recommendation

Implement a "pull over push" pattern for handling bid refunds. Instead of immediately transferring ETH to the previous highest bidder during the buyback process, store their refund in a mapping and let them withdraw it later:

// Add state variable
mapping(address => uint256) public pendingRefunds;
function buyBackNFT(uint256 tokenId) external payable {
TokenData storage data = tokenData[tokenId];
if (block.timestamp >= data.auctionEndTime) revert AuctionHasEnded();
if (nftContract.ownerOf(tokenId) != address(this)) revert NFTNotInLiquidation();
uint256 price = data.debt * 11 / 10; // 110% of the debt
if (msg.value < price) revert InsufficientPayment(price);
// Store refund instead of immediate transfer
if (data.highestBidder != address(0)) {
pendingRefunds[data.highestBidder] += data.highestBid;
}
delete tokenData[tokenId];
// ... rest of the function remains the same
}
// Add withdrawal function
function withdrawRefund() external {
uint256 amount = pendingRefunds[msg.sender];
require(amount > 0, "No refund available");
pendingRefunds[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope
inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.