Swapper.sol function _executeSwaps() call in a loop that use the same msg.value sent by user. This allows anyone to call _executeSwaps() which can control Li.Fi contract ETH balance on raw call through function LibSwap.swap().
Note: Lines of code above include all facets swap function check wrong input token before swap. Which anyone can freely call whatever whitelist function on whitelist address without spend their token.
Impact
Attacker can withdraw main token (ETH) from LiFi contract. Dex Whitelist and Function whitelist cannot prevent this
Other User funds are not affected.
Proof of Concept
Attacker can exploit a function that does not check main token balance after _executeSwaps like in AnySwapFacet.sol.
These lines of code here only check input balance of AnyswapData memory _anyswapData after execute and not balance of token inside LibSwap.SwapData[] calldata _swapData.
(same go for GenericSwapFacet.sol with swapTokensGeneric() and every other facets)
Attacker can craft special msg to DODO RouteProxy (whitelist in dex.ts) and swap Li.Fi ETH to other token and transfer it to another address.
Lifi contract have 1 ETH balance.
Attacker call cross chain bridge swapAndStartBridgeTokensViaAnyswap with 1 ETH and another random token.
Attacker craft Swap data with msg.value with 1 ETH and 2 swap with DODO router function.
Swapper.sol call in array loop
LibSwap.sol only take ERC20 from sender but not check or reduce amount from msg.value
router swap 1 ETH send by sender
router swap 1 ETH use LiFi contract balance
Lifi contract have 0 ETH balance.
LiFi start call _startBridge function to end bridge token with no relation to Dodo swap at all.
Sender get 2 ETH worth of token.
The simpler way is to just swap 1 ETH to USDC with swapTokensGeneric() will just send back 2 ETH worth of USDC with same exploit above.
Note: This concept use other attack approach from different facets instead of attack through Uniswap like other report.
Due to some other bugs exist in LibSwap.sol and Swapper.sol that I already found. It is possible swap ETH directly for some fake token.
Tools Used
Manual
Recommended Mitigation Steps
track msg.value in Swapper.sol loop and check balance after each swap.
Stop using the same LibSwap.SwapData[] as one data fit all for facet input.
It would be the best to not use array in Swapper.sol and replace it with multicall function.
Migrate this might require change all logic in facets.
I would highly recommend create adhoc data function and execution for each facet instead of 1 Swapper.sol for all. This help reduces complexity and help reduce development time in long term. Also, 1 broken library does not require refactoring all facets code. Each facet should be modular and independent of each other.
Each facet should have its own unique SwapData or inputData.
LibSwap.SwapData[] should be a subset data of this SwapData and not a direct function input.
If possible, create new SwapData inside function and not input send by user. Input should be used to create data to pass on LibSwap.sol.
Lines of code
https://github.com/code-423n4/2022-03-lifinance/blob/699c2305fcfb6fe8862b75b26d1d8a2f46a551e6/src/Facets/Swapper.sol#L14 https://github.com/code-423n4/2022-03-lifinance/blob/699c2305fcfb6fe8862b75b26d1d8a2f46a551e6/src/Libraries/LibSwap.sol#L42 https://github.com/code-423n4/2022-03-lifinance/blob/699c2305fcfb6fe8862b75b26d1d8a2f46a551e6/src/Facets/AnyswapFacet.sol#L85 https://github.com/code-423n4/2022-03-lifinance/blob/699c2305fcfb6fe8862b75b26d1d8a2f46a551e6/src/Facets/CBridgeFacet.sol#L98 https://github.com/code-423n4/2022-03-lifinance/blob/699c2305fcfb6fe8862b75b26d1d8a2f46a551e6/src/Facets/GenericSwapFacet.sol#L23 https://github.com/code-423n4/2022-03-lifinance/blob/699c2305fcfb6fe8862b75b26d1d8a2f46a551e6/src/Facets/NXTPFacet.sol#L91
Vulnerability details
Swapper.sol
function_executeSwaps()
call in a loop that use the samemsg.value
sent by user. This allows anyone to call_executeSwaps()
which can control Li.Fi contract ETH balance on raw call through functionLibSwap.swap()
.Note: Lines of code above include all facets swap function check wrong input token before swap. Which anyone can freely call whatever whitelist function on whitelist address without spend their token.
Impact
Attacker can withdraw main token (ETH) from LiFi contract. Dex Whitelist and Function whitelist cannot prevent this
Other User funds are not affected.
Proof of Concept
Attacker can exploit a function that does not check main token balance after
_executeSwaps
like inAnySwapFacet.sol
. These lines of code here only check input balance ofAnyswapData memory _anyswapData
after execute and not balance of token insideLibSwap.SwapData[] calldata _swapData
. (same go forGenericSwapFacet.sol
withswapTokensGeneric()
and every other facets)Attacker can craft special msg to
DODO RouteProxy
(whitelist index.ts
) and swap Li.Fi ETH to other token and transfer it to another address.swapAndStartBridgeTokensViaAnyswap
with 1 ETH and another random token.msg.value
with 1 ETH and 2 swap with DODO router function.Swapper.sol
call in array loopLibSwap.sol
only take ERC20 from sender but not check or reduce amount frommsg.value
_startBridge
function to end bridge token with no relation to Dodo swap at all.The simpler way is to just swap 1 ETH to USDC with
swapTokensGeneric()
will just send back 2 ETH worth of USDC with same exploit above.Note: This concept use other attack approach from different facets instead of attack through Uniswap like other report. Due to some other bugs exist in
LibSwap.sol
andSwapper.sol
that I already found. It is possible swap ETH directly for some fake token.Tools Used
Manual
Recommended Mitigation Steps
msg.value
inSwapper.sol
loop and check balance after each swap.LibSwap.SwapData[]
as one data fit all for facet input.Swapper.sol
and replace it with multicall function.Migrate this might require change all logic in facets.
I would highly recommend create adhoc data function and execution for each facet instead of 1
Swapper.sol
for all. This help reduces complexity and help reduce development time in long term. Also, 1 broken library does not require refactoring all facets code. Each facet should be modular and independent of each other.Each facet should have its own unique
SwapData
orinputData
.LibSwap.SwapData[]
should be a subset data of thisSwapData
and not a direct function input.If possible, create new
SwapData
inside function and not input send by user. Input should be used to create data to pass onLibSwap.sol
.