Core Contracts

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

Fake Stability Pool in LendingPool.sol leading to Collateral Theft

Summary

An attacker can deploy a fake Stability Pool to steal liquidated collateral from borrowers when their loans are liquidated. The core issue is that there is no validation to check whether the stabilityPool is a trusted contract before transferring NFTs or funds. This allows attackers to intercept liquidated assets without repaying the associated debt, leading to borrower losses, protocol insolvency, and bad debt accumulation.

Vulnerability Details

Root Cause

The function finalizeLiquidation() in LendingPool.sol lacks proper verification that the stabilityPool is an authorized and trusted contract before transferring NFTs.

Vulnerable Function: finalizeLiquidation()

function finalizeLiquidation(address userAddress) external nonReentrant onlyStabilityPool {
if (!isUnderLiquidation[userAddress]) revert NotUnderLiquidation();
// Update state before liquidation
ReserveLibrary.updateReserveState(reserve, rateData);
if (block.timestamp <= liquidationStartTime[userAddress] + liquidationGracePeriod) {
revert GracePeriodNotExpired();
}
UserData storage user = userData[userAddress];
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
// ⚠️ RISK: If stabilityPool is a fake contract, the attacker gains control over NFTs
for (uint256 i = 0; i < user.nftTokenIds.length; i++) {
uint256 tokenId = user.nftTokenIds[i];
user.depositedNFTs[tokenId] = false;
raacNFT.transferFrom(address(this), stabilityPool, tokenId);
}
delete user.nftTokenIds;
// Burn DebtTokens (⚠️ RISK: Attacker does not actually repay debt)
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(userAddress, userDebt, reserve.usageIndex);
// Transfer funds assuming debt is paid (⚠️ Can be exploited)
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
user.scaledDebtBalance -= amountBurned;
reserve.totalUsage = newTotalSupply;
emit LiquidationFinalized(stabilityPool, userAddress, userDebt, getUserCollateralValue(userAddress));
}

Attack Scenario

  1. Attacker Deploys a Fake Stability Pool Contract

contract FakeStabilityPool {
function receiveNFT(address nftContract, uint256 tokenId) public {
IERC721(nftContract).transferFrom(msg.sender, tx.origin, tokenId); // Transfers to attacker
}
}
  1. Attacker Registers Fake Stability Pool

solidity
  1. Victim (Alice) Fails to Repay Loan & Gets Liquidated

  2. Attacker Calls finalizeLiquidation()

lendingPool.setStabilityPool(address(fakeStabilityPool)); // If not properly restricted

Fake Stability Pool Intercepts NFTs & Transfers Them to Attacker

Attacker Sells Stolen NFTs on a Marketplace

Impact

Borrowers Lose Collateral: Stolen NFTs never repay debt.

Protocol Assumes Debt is Paid: The system remains insolvent.

Bad Debt Accumulates: Over time, attackers can bankrupt the lending protocol.

Tools Used

Manual Review

Recommendations

Whitelist and Validate the Stability Pool

+ require(stabilityPool == expectedStabilityPool, "Invalid Stability Pool");

Ensure Only Authorized Contracts Can Handle Liquidations
Implement Multi-Signature or DAO Governance to Modify Stability Pool Address

This fix ensures attackers cannot register fake contracts to steal collateral.

Updates

Lead Judging Commences

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

Support

FAQs

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