LooksRare Aggregator contest - chaduke's results

An NFT aggregator protocol.

General Information

Platform: Code4rena

Start Date: 08/11/2022

Pot Size: $60,500 USDC

Total HM: 6

Participants: 72

Period: 5 days

Judge: Picodes

Total Solo HM: 2

Id: 178

League: ETH

LooksRare

Findings Distribution

Researcher Performance

Rank: 22/72

Findings: 3

Award: $264.89

QA:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
edited-by-warden
duplicate-241

Awards

151.3257 USDC - $151.33

External Links

Lines of code

https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/LooksRareAggregator.sol#L245

Vulnerability details

Impact

Detailed description of the impact of this finding. The function execute() in LooksRareAggregator never checks the return value when transferring the ETH back to the user. As a result, the transaction will complete even though ETH has not been successfully transferred back to the user. The user will lose his ETH forever to the contract or to next user since there is no accounting for his ETH balance.

Proof of Concept

Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept. First, function _returnETHIfAny() will not revert when it fails

function _returnETHIfAny(address recipient) internal { assembly { if gt(selfbalance(), 0) { let status := call(gas(), recipient, selfbalance(), 0, 0, 0, 0) } } }

then in the following line, the return value has never been checked https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/LooksRareAggregator.sol#L109 As a result, even the transfer of ETH fails, the transaction will still proceed and completes. The user will lose his ETH in the contract or to other users forever as there is no accounting for his ETH balance in the account.

Tools Used

Remix

Reimplement function _returnETHIfAny to revert when it fails and also return a false boolean value when it fails.

#0 - c4-judge

2022-11-19T10:22:38Z

Picodes marked the issue as duplicate of #241

#1 - c4-judge

2022-12-16T13:48:37Z

Picodes marked the issue as satisfactory

#2 - c4-judge

2022-12-16T13:48:41Z

Picodes changed the severity to 2 (Med Risk)

Findings Information

Awards

77.2215 USDC - $77.22

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
duplicate-277

External Links

Lines of code

https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/LooksRareAggregator.sol#L108

Vulnerability details

Impact

Detailed description of the impact of this finding. L108 in LooksRareAggregator can be exploited to drain all ERC20 in the contract.

Proof of Concept

Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept. A malicious user Eve can monitor the balances of ERC20 tokens in contract LooksRareAggregator and if there are any, he will immediately call function

execute( TokenTransfer[] calldata tokenTransfers, ILooksRareAggregator.TradeData[] calldata tradeData, address recipient, bool isAtomic )

in contract ERC20EnabledLooksRareAggregator, which will call execute() in LooksRareAggregator. Note that we do not need to transfer any ERC20 to the contract since there is no check of amount > 0, finally via line 108. Alice will make sure all ERC20 tokens available in the contract will be listed in the argument TokenTransfer[]. In this way, L108 will return all ERC20 tokens that the contract LooksRareAggregator owns to the originator, attacker Alice.

Tools Used

Remix

We need to account for the balance for each ERC20 for each user in contract LooksRareAggregator. In this way, we will never refund a user with ERC20 tokens that belong to another user.

#0 - c4-judge

2022-11-19T10:05:33Z

Picodes marked the issue as duplicate of #277

#1 - c4-judge

2022-12-16T13:53:45Z

Picodes marked the issue as satisfactory

#2 - c4-judge

2022-12-16T13:53:50Z

Picodes changed the severity to 2 (Med Risk)

Awards

36.3434 USDC - $36.34

Labels

bug
grade-b
judge review requested
QA (Quality Assurance)
edited-by-warden
Q-01

External Links

QA1. Lock the most recent version of solidity 0.8.17consistently (currrently, some use 0.8.14, some use 0.8.17).

QA2. https://github.com/code-423n4/2022-11-looksrare/blob/d8949e1c527e0544027370969f970c4194b10640/contracts/proxies/SeaportProxy.sol#L88 https://github.com/code-423n4/2022-11-looksrare/blob/d8949e1c527e0544027370969f970c4194b10640/contracts/proxies/SeaportProxy.sol#L178

There is no need to implement the function _executeAtomicOrders separately, instead one can easily add a revert statement into the catch{} part in _executeNonAtomicOrders to support the atomic case, in which we need to revert the whole transaction when the execution of one order fails as follows:

try{ } catch{ if(isAtomic) revert ExecuteAtomicOrdersFailure(); }

This will save much audit effort and code duplication. As a result, the fucntion _handleFees can be eliminated as well.

QA3: Simply the function _executeSingleOrder() as follows

function _executeSingleOrder( OrderTypes.TakerOrder memory takerBid, OrderTypes.MakerOrder memory makerAsk, address recipient, CollectionType collectionType, bool isAtomic ) private { try marketplace.matchAskWithTakerBidUsingETHAndWETH{value: takerBid.price}(takerBid, makerAsk) { _transferTokenToRecipient( collectionType, recipient, makerAsk.collection, makerAsk.tokenId, makerAsk.amount ); } catch { if(isAtomic) revert ExecuteSingleOrderFailure(); } }

QA3: https://github.com/code-423n4/2022-11-looksrare/blob/d8949e1c527e0544027370969f970c4194b10640/contracts/ERC20EnabledLooksRareAggregator.sol#L21 It will be helpful to make the contract pausible in case the owner of the LooksRareAggregator contract is compromised.

QA3: the LooksRareAggregator gives all power to ONE owner, although the owner could be a mult-sig contract, the management is still too centralized and risky. It is recommended to have a balance-and-check mechanism: for example, 1) setERC20EnabledLooksRareAggregator is manged by one role; 2) the rescue functions are managed by a second role; and 3) those that are related to business, such as setFee() and market setup by a third role.

QA4: ERC20EnabledLooksRareAggregator needs a whitelist mechanism for ERC20 tokens to avoid people use fake tokens to exchange for good tokens. One way of doing that is right before _pullERC20Tokens(tokenTransfers, msg.sender);, call a function isWhiteListed(tokenTransfers) in LooksRareAggregator to verify.

QA5: the low level transfer functions at https://github.com/code-423n4/2022-11-looksrare/tree/main/contracts/lowLevelCallers have not checked if the token address is a valid smart contract. If there is no code at the target address, all transfers will still return a success return even though no operation has been performed.

QA6: https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/ReentrancyGuard.sol#L12 https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/LooksRareAggregator.sol#L51 The execute() of the LookRareAggregator is protected from reentrancy attack by the modifier nonReentrant which relies on the settting and resetting of state variable _status. Unfortunately, via delegate all in line 88, the callee contract might also change this state variable _status. The reason is that in a delegatecall, both the the callee contract can only read and write to the state variables in the calling contract. In this case, these two contracts do not have the same state variable layout, and the _status variable corresponds to the slot taken by marketplace in proxy contracts, SeapotProxy and LooksRareProxy, which will be initialized to the corresponding market address. In this case, the modifier nonReentrant will check the wrong status and its funcionality will be compromised. Mitigation: make sure the calling contract and the callee contract via a delegatecal always have the same state variable layout, a common pattern to use is the AppStorage pattern: https://eip2535diamonds.substack.com/p/understanding-delegatecall-and-how

QA7: the owner of LooksRareAggregator should be prevented from calling the execute() function in LooksRareAggregator and in erc20EnabledLooksRareAggregator due to its excessive power, in particular the power to call approve on any address, currency and amount. This can be performed by inserting a check in the execute() function as follows

if(msg.sender == owner) || originator == owner) revert NotcallableByOwner();

This should prevent many attacks in the case that the owner's account is compromised, an compromised owner with approve privilege and calling of proxy contract with delegatecall is very troubling.

QA8: https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/LooksRareAggregator.sol#L171 marketplace must be whitelisted, otherwise, this approve function can approve any marketplace/address as the spender.

QA9: https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/LooksRareAggregator.sol#L44 erc20EnabledLooksRareAggregator should be declared as immutable and maybe private as well

address private immutable erc20EnabledLooksRareAggregator;

#0 - c4-judge

2022-11-21T19:55:42Z

Picodes marked the issue as grade-b

#1 - 0xhiroshi

2022-11-22T17:34:59Z

QA1. All contracts with floating pragma come from our contracts-libs repo and they are meant to be shared by our other projects. QA2. For atomic orders, we decided to go with a single fulfillAvailableAdvancedOrders call instead of calling fulfillAdvancedOrder n times as external calls are expensive. QA3. _executeSingleOrder -> If we follow this suggestion, it is cheaper when all the orders go through, but more expensive when the orders are partially filled. So it's good for low volume collections but not so good when it's the first day of a very hot collection. (TBD, @0xJurassicPunk wdyt?) QA3. Pausable -> we don't store any funds, if the contract is compromised we will just ask people to stop using it and revoke approvals QA3. Centralization risk -> we are not a DAO or a team with many signers QA4. I am not following how fake ERC20 tokens can be exchanged for legit ERC20 tokens. QA5. Ok, we will check the code size before making the transfer. QA6. The proxies have no storage variables so there isn't a storage layout issue. The marketplace and aggregator variables are immutable so they are not written to slots. QA7. I don't understand what the issue is, the report isn't being very clear.

#2 - c4-sponsor

2022-11-22T17:35:15Z

0xhiroshi requested judge review

#3 - 0xhiroshi

2022-12-12T23:02:42Z

@Picodes what is the edict of this report?

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter