The implementation of UserEscrow.transferOut may cause the token of destination to be stolen by the receiver #443
Labels
3 (High Risk)
Assets can be stolen/lost/compromised directly
bug
Something isn't working
duplicate-217
sufficient quality report
This report is of sufficient quality
unsatisfactory
does not satisfy C4 submission criteria; not eligible for awards
Lines of code
https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/UserEscrow.sol#L43
Vulnerability details
Impact
User withdraws assets via LiquidityPool.withdraw/redeem, which can specify a receiver to receive assets and ultimately send assets to the receiver via userEscrow.transferOut. When the receiver and destination(caller) are different, transferOut will forcefully check whether the destination approves the maximum to the receiver. If not, then tx will be revert. This means that user needs to call
asset.approve(receiver, type(uint256).max)
in advance. In some cases, the receiver may not be the user's own wallet address. In this way, the receiver can transfer all the asset held by user's wallet, causing the user to suffer funds loss.Proof of Concept
Let's take a look at the flow of
withdraw
, which is similar toredeem
.L43, comments from dev: The check is just an additional protection to secure destination funds in case of compromized auth. This assumes that
receiver
is owned by the caller, however, this assumption does not apply to all cases. Consider the following scenario, assuming the asset is USDC:USDC.approve(bob, type(uint256).max)
withdraw(5000e6, bob, A)
withdraw(5000e6, A, A)
USDC.transferFrom(A, bob, allBalance)
, and bob gets all USDC.USC.transfer(alice, allBalance)
, then tx revert. alice got nothing.Alice is a non-technical user. So forcing the approve maximum caused Alice to suffer losses.
Tools Used
Manual Review
Recommended Mitigation Steps
Please do not use this method to force users to approve the maximum value to receiver.
Assessed type
Context
The text was updated successfully, but these errors were encountered: