Platform: Code4rena
Start Date: 07/04/2023
Pot Size: $47,000 USDC
Total HM: 20
Participants: 120
Period: 6 days
Judge: GalloDaSballo
Total Solo HM: 4
Id: 230
League: ETH
Rank: 75/120
Findings: 1
Award: $26.76
🌟 Selected for report: 0
🚀 Solo Findings: 0
26.761 USDC - $26.76
https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L256 https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L331 https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L442 https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L459-L476
Since end users will have to approve the PrivatePool contract to spend their erc20 tokens, or nfts before calling the buy()
, sell()
, or change()
functions, malicious PrivatePool owner who sees the transactions in the mempool can frontrun those functions by calling the execute()
function to illegally transfer some or all of the approved tokens to the contract. This would cause the user's call to be reverted because approved tokens for the operation is now less than the required price.
To attract many users to interact with a PrivatePool, the malicious owner could make the price of the nft relatively cheap to attract buyers.
This is the execute function:
function execute( address target, bytes memory data ) public payable onlyOwner returns (bytes memory) { // call the target with the value and data (bool success, bytes memory returnData) = target.call{value: msg.value}( data ); // if the call succeeded return the return data if (success) return returnData; // if we got an error bubble up the error message if (returnData.length > 0) { // solhint-disable-next-line no-inline-assembly assembly { let returnData_size := mload(returnData) revert(add(32, returnData), returnData_size) } } else { revert(); } }
Here is a scenario:
buy(tokenId, tokenWeight, proof)
, expecting to receive a Milady nft in exchange for USDC.execute(USDCAddress,attackData)
with higher gas; where attackData=abi.encodeWithSignature("transferFrom(address,address,uint)", AliceAddress, PrivatePoolAddress,400)
. This will pass because msg.sender in USDC.transferFrom() call is PrivatePool address(which has been approved by Alice).withdraw()
the stolen funds later.NOTE: sell()
, and change()
functions are equally vulnerable.
Manual Review
execute()
function should be restricted or removed because it exposes many attack vectors.
#0 - c4-pre-sort
2023-04-20T16:39:53Z
0xSorryNotSorry marked the issue as duplicate of #184
#1 - c4-judge
2023-05-01T19:21:18Z
GalloDaSballo marked the issue as satisfactory