Background
On January 13, 2025, according to the SlowMist MistEye security monitoring system, UniLend on the EVM chain was attacked, with a loss of approximately US$197,000. The SlowMist security team analyzed the incident and shared the results as follows:
(https://x.com/SlowMist_Team/status/1878651772375572573)
Related information
Attacker address: 0x55f5f8058816d5376df310770ca3a2e294089c33
Attack process
1. Pre-pledge assets: The attacker pre-pledged 200 USDC to UnilendV2Pool through a pre-transaction (0xdaf42127499f878b62fc5ba2103135de1c36e1646487cee309c077296814f5ff) and obtained USDC lendShare 150,237,398. Subsequently, the attacker transferred LP Tokens to prepare for the subsequent redemption of funds.
2. Borrow assets using flash loans:The attacker borrows 60M USDC and 5 wstETH through flash loans, and converts wstETH into 6 stETH.
3. Deposit assets to obtain lending shares:The attacker calls the lend function twice, depositing USDC and stETH into the previously prepared LP to obtain the corresponding shares. 4. Borrow target assets: Since the attacker has deposited a large amount of USDC in advance, he can borrow 60 stETH normally by calling the borrow function. At this time, due to borrowing, stETH borrowShare increases to 60239272000126842038.
5. Redeem the pledged stETH:The attacker calls the redeemUnderlying function to redeem all the pledged stETH. Since the attacker has never borrowed USDC, USDC borrowShare is 0, so all stETH can be redeemed directly, and stETH lendShare returns to zero.
6. Redeem the pledged USDC:The attacker calls the redeemUnderlying function again to redeem all the pledged USDC. In the redeemUnderlying function, the _burnLPposition function is first called to destroy the corresponding USDC lendShare. At this time, there are 150237398 USDC lendShares left. Subsequently, the contract checks the health factor in the checkHealthFactorLtv1 function, and finally transfers the redeemed USDC to the user.
In theory, the attacker has borrowed part of stETH with the pledged USDC. When he wants to redeem all the USDC, the health factor check should not pass. However, the actual situation is not like this. Let's follow up with the checkHealthFactorLtv1 function:
We can easily find from the above figure that the function first obtains the current USDC lendBalance and stETH borrowBalance through userBalanceOftoken0 and userBalanceOftoken1 functions, then calculates the USDC health factor, and compares it with the security threshold to determine whether redemption of USDC is allowed.
Continue to check the userBalanceOftoken0 function:
Obviously, this is the crux of the problem. The contract directly uses the current USDC balance of the pool and USDC lendShare to calculate lendBalance. However, this part of the balance includes the amount of USDC that the user is ready to redeem, but USDC lendShare has already deducted the share that the user is ready to redeem in the previous _burnLPposition function. Therefore, USDC lendBalance changes from the expected 150237398*(728895404+4829907565)/4175666009 = 200001650 to 150237398*(60000728895404+4829907565)/4175666009 = 2158955960717, which is obviously too large, causing the return value of the health factor to be much higher than expected and successfully pass the verification.
7. Complete the attack and make a profit:Finally, the attacker returns the USDC and wstETH borrowed by the flash loan and leaves with a profit. Due to the loophole, the attacker only pledged 200 USDC to get 60 stEth.
Summary
The core of this attack is that the attacker used the old token balance of the pool to calculate the health factor using the redeemUnderlying function, while the user's token had not yet been transferred out of the pool, resulting in the health factor calculation result being higher than the actual situation. The system mistakenly believed that the user's lending status was safe. The attacker was therefore able to bypass the correct verification of the health factor and illegally obtain the target assets. The SlowMist Security Team recommends that project parties ensure real-time updates of asset status during the health factor calculation process to avoid similar situations.