Lucene search

K
code423n4Code4renaCODE423N4:2022-11-LOOKSRARE-FINDINGS-ISSUES-261
HistoryNov 13, 2022 - 12:00 a.m.

Native funds on the aggregator contract balance is a free grab

2022-11-1300:00:00
Code4rena
github.com
5
looksrareaggregator
execute()
native funds
theft
vulnerability
user funds
bot
mitigation

Lines of code

Vulnerability details

Native funds on the aggregator contract balance is a free grabLooksRareAggregator’s execute() returns the native balance of the contract to the caller even when nothing was provided with the call.

This happens when LooksRareAggregator’s execute() is called directly by a user with no ERC20 transfers, i.e. when tokenTransfersLength == 0.

Bob the attacker can setup a bot that tracks aggregator’s native balance and calls execute() with isAtomic == false and some bogus trade to be reverted on the marketplace level, just to invoke _returnETHIfAny(Bob), which will transfer LooksRareAggregator’s native balance to Bob.

This way whenever any user mistakenly sends directly any native funds to the contract, an operational mistake that happens a lot with any router-like contracts and inexperienced users, these funds will be immediately stolen by Bob’s bot.

Net impact here is fund loss, which is conditional on user’s mistake, so setting the severity to medium.

Proof of Concept

execute() can be called directly and set originator = msg.sender, then proceeds with _returnETHIfAny(originator):

<https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/LooksRareAggregator.sol#L51-L112&gt;

    function execute(
        TokenTransfer[] calldata tokenTransfers,
        TradeData[] calldata tradeData,
        address originator,
        address recipient,
        bool isAtomic
    ) external payable nonReentrant {
        if (recipient == address(0)) revert ZeroAddress();
        uint256 tradeDataLength = tradeData.length;
        if (tradeDataLength == 0) revert InvalidOrderLength();

        uint256 tokenTransfersLength = tokenTransfers.length;
        if (tokenTransfersLength == 0) {
            originator = msg.sender;
        } else if (msg.sender != erc20EnabledLooksRareAggregator) {
            revert UseERC20EnabledLooksRareAggregator();
        }

        for (uint256 i; i &lt; tradeDataLength; ) {
				...
        }

        if (tokenTransfersLength &gt; 0) _returnERC20TokensIfAny(tokenTransfers, originator);
        _returnETHIfAny(originator);

        emit Sweep(originator);
    }

_returnETHIfAny() sends the whole native balance to the recipient:

<https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/lowLevelCallers/LowLevelETH.sol#L40-L49&gt;

    /**
     * @notice Return ETH back to the designated sender if any ETH is left in the payable call.
     */
    function _returnETHIfAny(address recipient) internal {
        assembly {
            if gt(selfbalance(), 0) {
                let status := call(gas(), recipient, selfbalance(), 0, 0, 0, 0)
            }
        }
    }

Funds can be send directly to the contract by any user:

<https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/LooksRareAggregator.sol#L219-L220&gt;

    receive() external payable {}

Recommended Mitigation Steps

As _returnETHIfAny() is to refund the native funds provided by the caller, consider recording msg.value of the call and send back min(msg.value, balance).


The text was updated successfully, but these errors were encountered:

All reactions