First Flight #21: KittyFi

First Flight #21
Beginner FriendlyDeFiFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

Attacker can use the wrong liquidation logic related with purrgeBadPawsition by applying the Flashloan, mint so much kittyCoins without depositing colloteral.

Summary

There are two wrong logic places:

1) Use the getUserMeowllateralInEuros instead of actual Meowllateralal number

2) Withdaw the caller's the Meowllateralal instead of the liquidiated user's Meowllateralal.

Attacker can use above flaws skip the withdraw validation(no need check the COLLATERAL_PERCENT), withdraw all her Meowllateralal.

If apply flashloan, can mint so much kittyCoins without depositing Meowllateralal.

Vulnerability Details

  1. getUserMeowllateralInEuros(_user); should apply getUserMeowllateral

  2. _vault.executeWhiskdrawal(msg.sender, toDistribute + extraReward); should transfer the liquidated user's Meowllateralal, not the calller.

Attacking steps. (As time limitaion, below is under testing)

Below take 0.1 eth as example, can adjust to make more benefits based on the max flashloan amount.

1) Attacker1: deposited 0.1 eth to protocol and mint max kittyCoins. make the COLLATERAL_PERCENT closest to 169.

2) Attacker2: flashloan eth from aave, based on my current calculation. 0.1 ETH = 267022152292165420000 EURO. attacker2 can borrow 267022152292165420000(WEI), (267 ETH). Then based on the the huge ETH, mint max kittyCoins.

3) Because Attacker1's COLLATERAL_PERCENT is closet to 169. It's very easy to make under 169. Waiting the markert price change.

4) When the Attacker1's COLLATERAL_PERCENT under 169, Attacker2 can call purrgeBadPawsition.

As the above flaws, which will transfer Attacker2's collotral to herself. (Becaused the wrong calculation)

5) Attacker return the flashloan.

So attacker can mint so much kittyCoins based on 267 ETH. without depositing any ETH.

This number can be more bigger, which leads to the attacker2 can get huge funds from the protol, the protocol will be demaged.

/**
* @notice Liquidates the bad debt position of the user
* @param _user address of the user
*/
function purrgeBadPawsition(address _user) external returns (uint256 _totalAmountReceived) {
require(!(_hasEnoughMeowllateral(_user)), KittyPool__UserIsPurrfect());
uint256 totalDebt = kittyCoinMeownted[_user];
kittyCoinMeownted[_user] = 0;
i_kittyCoin.burn(msg.sender, totalDebt);
uint256 userMeowllateralInEuros = getUserMeowllateralInEuros(_user);
uint256 redeemPercent;
if (totalDebt >= userMeowllateralInEuros) {
redeemPercent = PRECISION;
}
else {
redeemPercent = totalDebt.mulDiv(PRECISION, userMeowllateralInEuros);
}
uint256 vaults_length = vaults.length;
for (uint256 i; i < vaults_length; ) {
IKittyVault _vault = IKittyVault(vaults[i]);
uint256 vaultCollateral = _vault.getUserVaultMeowllateralInEuros(_user);
uint256 toDistribute = vaultCollateral.mulDiv(redeemPercent, PRECISION);
uint256 extraCollateral = vaultCollateral - toDistribute;
uint256 extraReward = toDistribute.mulDiv(REWARD_PERCENT, PRECISION);
extraReward = Math.min(extraReward, extraCollateral);
_totalAmountReceived += (toDistribute + extraReward);
_vault.executeWhiskdrawal(msg.sender, toDistribute + extraReward);
unchecked {
++i;
}
}
}

Impact

Attacker can get huge funds from the protol, the protocol will be demaged.

Tools Used

Mannul

Recommendations

/**
* @notice Liquidates the bad debt position of the user
* @param _user address of the user
*/
function purrgeBadPawsition(address _user) external returns (uint256 _totalAmountReceived) {
require(!(_hasEnoughMeowllateral(_user)), KittyPool__UserIsPurrfect());
uint256 totalDebt = kittyCoinMeownted[_user];
kittyCoinMeownted[_user] = 0;
i_kittyCoin.burn(msg.sender, totalDebt);
uint256 userMeowllateralInEuros = getUserMeowllateralInEuros(_user);
uint256 redeemPercent;
if (totalDebt >= userMeowllateralInEuros) {
redeemPercent = PRECISION;
}
else {
redeemPercent = totalDebt.mulDiv(PRECISION, userMeowllateralInEuros);
}
uint256 vaults_length = vaults.length;
for (uint256 i; i < vaults_length; ) {
IKittyVault _vault = IKittyVault(vaults[i]);
//uint256 vaultCollateral = _vault.getUserVaultMeowllateralInEuros(_user);
// Use below logic
uint256 vaultCollateral = _vault.getUserMeowllateral(_user);
uint256 toDistribute = vaultCollateral.mulDiv(redeemPercent, PRECISION);
uint256 extraCollateral = vaultCollateral - toDistribute;
uint256 extraReward = toDistribute.mulDiv(REWARD_PERCENT, PRECISION);
extraReward = Math.min(extraReward, extraCollateral);
_totalAmountReceived += (toDistribute + extraReward);
// There should first transfer to porotcol, then transfer to liquidator
_vault.executeWhiskdrawal(_user, toDistribute + extraReward);
//_vault.executeWhiskdrawal(msg.sender, toDistribute + extraReward);
unchecked {
++i;
}
}
}
Updates

Lead Judging Commences

shikhar229169 Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Appeal created

bytesflow007 Submitter
10 months ago
shikhar229169 Lead Judge
10 months ago
bytesflow007 Submitter
10 months ago
shikhar229169 Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

`purrgeBadPawsition` doesn't reward the liquidator instead perform withdrawal of their own collateral or lead to a DoS

`purrgeBadPawsition` calculates collateral to be distributed in terms of euros, which is incorrect.

Support

FAQs

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