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
getUserMeowllateralInEuros(_user); should apply getUserMeowllateral
_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.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);
_vault.executeWhiskdrawal(_user, toDistribute + extraReward);
unchecked {
++i;
}
}
}