Using arbitrary path data and executor address with OogaBooga aggregator can allow Zapper's users to bypass token whitelist
Vulnerability Details
The tokens whitelist in the Zapper contract can be updated by the owner so that users are allowed to zap in, or zap out with the whitelisted tokens.
However, the current integration with OogaBooga aggregator is flawed that the user can use arbitrary path data and executor address, which is calldata passed to OogaBooga Router contract. By using arbitrary path data and executor address, the user can effectively swap input tokens for arbitrary output tokens, which bypasses the token whitelist checks.
Below is the implementation of OogaBooga Router contract to perform a swap. The Router does allow arbitrary external call to executor, which allows users to control the input tokens of the swap.
function _swap(swapTokenInfo memory tokenInfo, bytes calldata pathDefinition, address executor, uint32 referralCode)
internal
returns (uint256 amountOut)
{
// Check for valid output specifications
require(
tokenInfo.outputMin <= tokenInfo.outputQuote,
MinimumOutputGreaterThanQuote(tokenInfo.outputMin, tokenInfo.outputQuote)
);
require(tokenInfo.outputMin > 0, MinimumOutputIsZero());
require(tokenInfo.inputToken != tokenInfo.outputToken, SameTokenInAndOut(tokenInfo.inputToken));
uint256 balanceBefore = tokenInfo.outputToken.universalBalance();
// Delegate the execution of the path to the specified OBExecutor
uint256[] memory amountsIn = new uint256[](1);
amountsIn[0] = tokenInfo.inputAmount;
@> external call to executor with arbitrary path >> IOBExecutor(executor).executePath{value: msg.value}(pathDefinition);
amountOut = tokenInfo.outputToken.universalBalance() - balanceBefore;
if (referralCode > REFERRAL_WITH_FEE_THRESHOLD) {
referralInfo memory thisReferralInfo = referralLookup[referralCode];
if (thisReferralInfo.beneficiary != address(this)) {
tokenInfo.outputToken.universalTransfer(
thisReferralInfo.beneficiary, amountOut * thisReferralInfo.referralFee * 8 / (FEE_DENOM * 10)
);
}
// Takes the fees and keeps them in this contract
amountOut = amountOut * (FEE_DENOM - thisReferralInfo.referralFee) / FEE_DENOM;
}
int256 slippage = int256(amountOut) - int256(tokenInfo.outputQuote);
if (slippage > 0) {
amountOut = tokenInfo.outputQuote;
}
require(amountOut >= tokenInfo.outputMin, SlippageExceeded(amountOut, tokenInfo.outputMin));
// Transfer out the final output to the end user
tokenInfo.outputToken.universalTransfer(tokenInfo.outputReceiver, amountOut);
emit Swap(
msg.sender,
tokenInfo.inputAmount,
tokenInfo.inputToken,
amountOut,
tokenInfo.outputToken,
slippage,
referralCode
);
}
For example:
The Zapper contract does not whitelist HONEY tokens for zapping.
There is a user wants to claim rewards in HONEY tokens --> This is not allowed
The user can bypass the whitelist by using function claimRewardsInToken0 with minOutput = 1 and wanted path data together with executor address so that executor can swap token1 -> HONEY -> HONEY can be sent to the user as long as the 1 wei of token0 is sent to the Router contract