Issue: The admin account can arbitrarily update the oracle at any time. If the oracle is changed to a malicious or vulnerable oracle, the price may be manipulated to allow illegitimate siezure of the assets.
Consequences: Any user-deposited NFT with outstanding borrows can be liquidated and siezed at any time for trivially small costs.
Proof of Concept
A malicious admin or admin account compromise is used to inject a malicious NFT oracle
The malicious NFT oracle provides an extremely low but >0 price, ex. 1 wei
Attacker calls liquidateBorrowNft() on a cToken with the siezeIds[] and siezeAmounts[] inputs set to include all NFTs with borrows on that cToken
comptroller.liquidateBorrowAllowed is called
Comptroller call chain progresses to getHypotheticalAccountLiquidityInternal and the vars.sumCollateral = mul_ScalarTruncateAddUInt(mul_(vars.nftOraclePrice, markets[address(nftMarket)].collateralFactorMantissa), nftBalance, vars.sumCollateral); calculation
This NFT contribution to account liquidity term will be extremely small nonzero or will round down to zero due to low nftOraclePrice.
If any user is not providing fungible cToken assets >= their borrows, a shortfall will be returned.
liquidateBorrowAllowed() will pass
The NFT oracle price will have to be appropriately set to return the correct amount of siezeTokens from comptroller.liquidateCalculateSeizeTokens to pass the other checks
Attacker repays a trivially small amount of cToken underlying, siezes the NFTs of the victim in that cToken market
Attack may be repeated across other cTokens until all NFTs are stolen
Lines of code
https://github.com/bunkerfinance/bunker-protocol/blob/752126094691e7457d08fc62a6a5006df59bd2fe/contracts/Comptroller.sol#L764
Vulnerability details
Issue: The admin account can arbitrarily update the oracle at any time. If the oracle is changed to a malicious or vulnerable oracle, the price may be manipulated to allow illegitimate siezure of the assets.
Consequences: Any user-deposited NFT with outstanding borrows can be liquidated and siezed at any time for trivially small costs.
Proof of Concept
liquidateBorrowNft()
on a cToken with thesiezeIds[]
andsiezeAmounts[]
inputs set to include all NFTs with borrows on that cTokencomptroller.liquidateBorrowAllowed
is calledgetHypotheticalAccountLiquidityInternal
and thevars.sumCollateral = mul_ScalarTruncateAddUInt(mul_(vars.nftOraclePrice, markets[address(nftMarket)].collateralFactorMantissa), nftBalance, vars.sumCollateral);
calculationnftOraclePrice
.liquidateBorrowAllowed()
will passsiezeTokens
fromcomptroller.liquidateCalculateSeizeTokens
to pass the other checks