Open code423n4 opened 1 year ago
0xSorryNotSorry marked the issue as high quality report
0xSorryNotSorry marked the issue as primary issue
outdoteth marked the issue as disagree with severity
outdoteth marked the issue as sponsor acknowledged
outdoteth marked the issue as sponsor confirmed
I think a potential fix is to prevent execute() from being able to call the baseToken the nft that is associated with the contract, which would stop the malicious approvals.
Fixed in: https://github.com/outdoteth/caviar-private-pools/pull/2
Proposed fix is to add a check in the execute() function that will revert if the target contract is the baseToken or nft.
if (target == address(baseToken) || target == address(nft)) revert InvalidTarget();
The Warden has shown how, due to composability, it's possible for the previous owner to set the PrivatePool to grant approvals for all it's tokens, in a way that will allow the previous owner to steal them back
There are many considerations to this:
Nonetheless the Approval Farming can be performed and the attack can be done in that way, however the buyer would have to buy a Pool for which the approvals have been setup and they would have to do so without revoking them (they could buy and revoke in the same tx)
Because of this, I belive that the finding is valid but of Medium Severity
GalloDaSballo changed the severity to 2 (Med Risk)
GalloDaSballo marked the issue as selected for report
Lines of code
https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L461 https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L623-L654
Vulnerability details
Impact
PrivatePool.sol
ERC721 and ERC20 tokens can be stolen by the previous owner viaexecute
andflashLoan
functions (or by malicious approval by the current owner viaexecute
)Proof of Concept
Let's say that Bob is the attacker and Alice is a regular user.
1.Bob creates a
PrivatePool.sol
where he deposits 5 ERC721 tokens and 500 USDC. 2.Then Bob creates a malicious contract (let's call itPrivatePoolExploit.sol
) and this contract containsonFlashLoan
(IERC3156FlashBorrower),transferFrom
,ownerOf
,onERC721Received
functions (like ERC721 does) and an additionalattack
function. 3.ViaPrivatePool.execute
function Bob approves USDC spending (type(uint).max
) andsetApprovalForAll
for ERC721 tokens 4.Since the ownership ofPrivatePool
is stored inFactory.sol
as an ERC721 token, ownership can be sold on any ERC721 marketplace. Alice decides to buy Bob'sPrivatePool
and ownership is transferred to Alice. 5.Right after the ownership is transferred, Bob runsPrivatePoolExploit.attack
function, which callsPrivatePool.flashLoan
wherePrivatePoolExploit.transferFrom
will be called since the flash loan can be called on any address.PrivatePool
is left with nothing.Here is a PoC example:
To run the test:
1.Save
PrivatePoolExploit.sol
under pathsrc/attacks/PrivatePoolExploit.sol
2.SaveAttack.t.sol
under pathsrc/test/PrivatePool/Attack.t.sol
3.Run the test with the commandforge test --match-contract AttackTest
PrivatePoolExploit.sol
Attack.t.sol
Result of the test
Tools Used
Foundry/VSCode
Recommended Mitigation Steps
The contract caller should not be able to choose the token address in the
PrivatePool.flashLoan
function because there is no way to know if the token contract is actually an ERC721 contract.Suggest removing
token
from function input parameters and usingnft
token everywhere, wheretoken
was used.