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):
/**
* @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:
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).
Lines of code
https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/LooksRareAggregator.sol#L51-L112
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
_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
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
Recommended Mitigation Steps
As _returnETHIfAny() is to refund the native funds provided by the caller, consider recording
msg.value
of the call and send backmin(msg.value, balance)
.