Bought/Purchased Token Can Be Sent To Attacker's Wallet Using 0x Adaptor
The lack of recipient validation against the 0x order within the 0x adaptor (ZeroExAdapter) allows the purchased/output tokens of the trade to be sent to the attacker's wallet.\\nBackground\\nHow does the emergency vault settlement process work?\\nAnyone can call the `settleVaultEmergency` function to trigger the emergency vault settlement as it is permissionless\\nThe `_getEmergencySettlementParams` function will calculate the excess BPT tokens within the vault to be settled/sold\\nThe amount of excess BPT tokens will be converted to an equivalence amount of strategy tokens to be settled\\nThe strategy tokens will be settled by withdrawing staked BPT tokens from Aura Finance back to the vault for redemption.\\nThe vault will then redeem the BTP tokens from Balancer to redeem its underlying assets (WETH and stETH)\\nThe primary and secondary assets of the vault are WETH and stETH respectively. The secondary asset (stETH) will be traded for the primary asset (WETH) in one of the supported DEXes. In the end, only the primary assets (WETH) should remain within the vault.\\nThe WETH within the vault will be sent to Notional, and Notional will mint the asset tokens (cEther) for the vault in return.\\nAfter completing the emergency vault settlement process, the vault will gain asset tokens (cEther) after settling/selling its excess BPT tokens.\\nIssue Description\\nThe caller of the `settleVaultEmergency` function can specify the trade parameters to sell the secondary tokens (stETH) for primary tokens (WETH) in any of the supported 5 DEX protocols (Curve, Balancer V2, Uniswap V2 & V3 and 0x) in Step 5 of the above emergency vault settlement process.\\nAfter analyzing the adaptors of 5 DEX protocols (Curve, Balancer V2, Uniswap V2 & V3 and 0x), it was observed that Curve, Balancer V2, Uniswap V2, and Uniswap V3 are designed in a way that the purchased tokens can only be returned to the vault.\\nTake the Uniswap V2 adaptor as an example. When the vault triggers the trade execution, it will always pass its own address `address(this)` `to` the `from` parameter of the `getExecutionData` function. The value of `from` parameter will be passed `to` the `to` parameter of Uniswap's `swapExactTokensForTokens` function, which indicates the recipient of the output/purchased tokens. Therefore, it is impossible for the caller `to` specify the recipient of the output tokens `to` another address. This is also the same for Curve, Balancer V2, and Uniswap V3.\\n```\\nFile: UniV2Adapter.sol\\n    function getExecutionData(address from, Trade calldata trade)\\n..SNIP..\\n            executionCallData = abi.encodeWithSelector(\\n                IUniV2Router2.swapExactTokensForTokens.selector,\\n                trade.amount,\\n                trade.limit,\\n                data.path,\\n                from,\\n                trade.deadline\\n            );\\n```\\n\\nHowever, this is not implemented for the 0x adaptor (ZeroExAdapter). The `from` of the `getExecutionData` is completely ignored, and the caller has the full flexibility of crafting an order that benefits the caller.\\n```\\nFile: ZeroExAdapter.sol\\nlibrary ZeroExAdapter {\\n    /// @dev executeTrade validates pre and post trade balances and also\\n    /// sets and revokes all approvals. We are also only calling a trusted\\n    /// zero ex proxy in this case. Therefore no order validation is done\\n    /// to allow for flexibility.\\n    function getExecutionData(address from, Trade calldata trade)\\n        internal view returns (\\n            address spender,\\n            address target,\\n            uint256 /* msgValue */,\\n            bytes memory executionCallData\\n        )\\n    {\\n        spender = Deployments.ZERO_EX;\\n        target = Deployments.ZERO_EX;\\n        // msgValue is always zero\\n        executionCallData = trade.exchangeData;\\n    }\\n}\\n```\\n\\nA number of features are supported by 0x. The full list of the supported features can be found here. Specifically, the following are the functions of attacker interest because it allows the attacker to configure the `recipient` parameter so that the bought tokens will be redirected to the attacker's wallet instead of the vault.\\nLiquidityProviderFeature - sellToLiquidityProvider\\n```\\n    /// @dev Sells `sellAmount` of `inputToken` to the liquidity provider\\n    ///      at the given `provider` address.\\n    /// @param inputToken The token being sold.\\n    /// @param outputToken The token being bought.\\n    /// @param provider The address of the on-chain liquidity provider\\n    ///        to trade with.\\n    /// @param recipient The recipient of the bought tokens. If equal to\\n    ///        address(0), `msg.sender` is assumed to be the recipient.\\n    /// @param sellAmount The amount of `inputToken` to sell.\\n    /// @param minBuyAmount The minimum acceptable amount of `outputToken` to\\n    ///        buy. Reverts if this amount is not satisfied.\\n    /// @param auxiliaryData Auxiliary data supplied to the `provider` contract.\\n    /// @return boughtAmount The amount of `outputToken` bought.\\n    function sellToLiquidityProvider(\\n        IERC20TokenV06 inputToken,\\n        IERC20TokenV06 outputToken,\\n        ILiquidityProvider provider,\\n        address recipient,\\n        uint256 sellAmount,\\n        uint256 minBuyAmount,\\n        bytes calldata auxiliaryData\\n    )\\n```\\n\\nUniswapV3Feature - sellTokenForTokenToUniswapV3\\n```\\n    /// @dev Sell a token for another token directly against uniswap v3.\\n    /// @param encodedPath Uniswap-encoded path.\\n    /// @param sellAmount amount of the first token in the path to sell.\\n    /// @param minBuyAmount Minimum amount of the last token in the path to buy.\\n    /// @param recipient The recipient of the bought tokens. Can be zero for sender.\\n    /// @return buyAmount Amount of the last token in the path bought.\\n    function sellTokenForTokenToUniswapV3(\\n        bytes memory encodedPath,\\n        uint256 sellAmount,\\n        uint256 minBuyAmount,\\n        address recipient\\n    )\\n```\\n\\nThe malicious user could perform the following actions to steal the assets:\\nAllow malicious users to specify the recipient of the output/purchased tokens to be themselves instead of the vault. This will cause the output/purchased tokens of the trade to be redirected to the malicious users instead of the vault\\nSpecify the `minBuyAmount` parameter of the order to `1 WEI` so that he only needs to provide `1 WEI` to fill the order to obtain all the secondary token (stETH) that need to be sold. This is allowed as there is no slippage control within 0x adaptor (Refer to my "No Slippage Control If The Trade Executes Via 0x DEX During Emergency Vault Settlement" issue write-up)
It is recommended to implement validation against the submitted 0x trade order to ensure that the recipient of the bought tokens is set to the vault when using the 0x DEX. Consider implementing the following validation checks.\\n```\\nlibrary ZeroExAdapter {\\n    /// @dev executeTrade validates pre and post trade balances and also\\n    /// sets and revokes all approvals. We are also only calling a trusted\\n    /// zero ex proxy in this case. Therefore no order validation is done\\n    /// to allow for flexibility.\\n    function getExecutionData(address from, Trade calldata trade)\\n        internal view returns (\\n            address spender,\\n            address target,\\n            uint256 /* msgValue */,\\n            bytes memory executionCallData\\n        )\\n    {\\n        spender = Deployments.ZERO_EX;\\n        target = Deployments.ZERO_EX;\\n  \\n  _validateExchangeData(from, trade);\\n  \\n        // msgValue is always zero\\n        executionCallData = trade.exchangeData;\\n    }\\n \\n function _validateExchangeData(address from, Trade calldata trade) internal pure {\\n        bytes calldata _data = trade.exchangeData;\\n\\n        address inputToken;\\n        address outputToken;\\n        address recipient;\\n        uint256 inputTokenAmount;\\n        uint256 minOutputTokenAmount;\\n\\n  require(_data.length >= 4, "Invalid calldata");\\n  bytes4 selector;\\n  assembly {\\n   selector := and(\\n    // Read the first 4 bytes of the _data array from calldata.\\n    calldataload(add(36, calldataload(164))), // 164 = 5 * 32 + 4\\n    0xffffffff00000000000000000000000000000000000000000000000000000000\\n   )\\n  }\\n  \\n  if (selector == 0xf7fcd384) {\\n   \\n   (\\n    inputToken, \\n    outputToken, \\n    , \\n    recipient, \\n    inputTokenAmount, \\n    minOutputTokenAmount\\n   ) = abi.decode(_data[4:], (address, address, address, address, uint256, uint256));\\n   require(recipient == from, "Mismatched recipient");\\n  } else if (selector == 0x6af479b2) {\\n   // sellTokenForTokenToUniswapV3()\\n   bytes memory encodedPath;\\n   // prettier-ignore\\n   (\\n    encodedPath,\\n    inputTokenAmount, \\n    minOutputTokenAmount, \\n    recipient\\n   ) = abi.decode(_data[4:], (bytes, uint256, uint256, address));\\n   require(recipient == from, "Mismatched recipient");\\n  }\\n }\\n}\\n```\\n
Attackers can craft a 0x order that redirects the assets to their wallet, leading to loss of assets for the vaults and their users.
```\\nFile: UniV2Adapter.sol\\n    function getExecutionData(address from, Trade calldata trade)\\n..SNIP..\\n            executionCallData = abi.encodeWithSelector(\\n                IUniV2Router2.swapExactTokensForTokens.selector,\\n                trade.amount,\\n                trade.limit,\\n                data.path,\\n                from,\\n                trade.deadline\\n            );\\n```\\n