Platform: Code4rena
Start Date: 04/03/2024
Pot Size: $88,500 USDC
Total HM: 31
Participants: 105
Period: 11 days
Judge: ronnyx2017
Total Solo HM: 7
Id: 342
League: ETH
Rank: 75/105
Findings: 1
Award: $42.78
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Bauchibred
Also found by: 0x11singh99, 0x175, 0xAlix2, 0xDemon, 0xGreyWolf, 0xPhantom, 0xspryon, 14si2o_Flint, Arabadzhiev, Aymen0909, Bigsam, BowTiedOriole, CRYP70, DanielArmstrong, FastChecker, JecikPo, KupiaSec, MohammedRizwan, Norah, Timenov, Topmark, VAD37, adeolu, btk, crypticdefense, cryptphi, givn, grearlake, jnforja, kennedy1030, kfx, ktg, lanrebayode77, n1punp, santiellena, stonejiajia, t4sk, thank_you, tpiliposian, wangxx2026, y0ng0p3, zaevlad
42.7786 USDC - $42.78
https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/utils/Swapper.sol#L172 https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/utils/Swapper.sol#L161 https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/PoolAddress.sol#L33
A malicious user can steal tokens from the Swapper by providing data with a fake token
Swapper contract accept callbacks from uniswap pools where amount for swap is payed:
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external override { require(amount0Delta > 0 || amount1Delta > 0); // swaps entirely within 0-liquidity regions are not supported // check if really called from pool (address tokenIn, address tokenOut, uint24 fee) = abi.decode(data, (address, address, uint24)); @> if (address(_getPool(tokenIn, tokenOut, fee)) != msg.sender) { revert Unauthorized(); } // transfer needed amount of tokenIn uint256 amountToPay = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); SafeERC20.safeTransfer(IERC20(tokenIn), msg.sender, amountToPay); }
It tries to verify that a call was made from the real pool by calling _getPool
and calculate the real address:
function _getPool(address tokenA, address tokenB, uint24 fee) internal view returns (IUniswapV3Pool) { return IUniswapV3Pool(PoolAddress.computeAddress(address(factory), PoolAddress.getPoolKey(tokenA, tokenB, fee))); }
The problem is it never verifies that the pool exist, but only tries to get the address:
struct PoolKey { address token0; address token1; uint24 fee; } function getPoolKey( address tokenA, address tokenB, uint24 fee ) internal pure returns (PoolKey memory) { if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); return PoolKey({token0: tokenA, token1: tokenB, fee: fee}); } function computeAddress(address factory, PoolKey memory key) internal pure returns (address pool) { require(key.token0 < key.token1); pool = address( uint160( uint256( keccak256( abi.encodePacked( hex'ff', factory, keccak256(abi.encode(key.token0, key.token1, key.fee)), POOL_INIT_CODE_HASH ) ) ) ) ); }
So a malicious user can calculate and delopy the contract that will match the calculated address with the information about a token he wants to steal and a fake token.
It will require some serious computaions for the hacker to break the right hash and build the valid address, but it is still possible with modern tools.
Manual review
Verify with the factory that msg.sender is a valid pool
Invalid Validation
#0 - c4-pre-sort
2024-03-22T08:52:52Z
0xEVom marked the issue as duplicate of #188
#1 - c4-pre-sort
2024-03-22T08:52:58Z
0xEVom marked the issue as insufficient quality report
#2 - 0xEVom
2024-03-22T08:53:17Z
Insufficient proof
#3 - c4-judge
2024-04-01T11:49:21Z
jhsagd76 changed the severity to QA (Quality Assurance)
#4 - c4-judge
2024-04-01T11:50:08Z
jhsagd76 marked the issue as grade-a