57697 sc low missing recipient from checks in zeroxswapverifier enable direct asset theft
Submitted on Oct 28th 2025 at 09:02:22 UTC by @yesofcourse for Audit Comp | Alchemix V3
Report ID: #57697
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/utils/ZeroXSwapVerifier.sol
Impacts:
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Description
Brief/Intro
ZeroXSwapVerifier - the library intended to pre-verify 0x swap calldata—omits critical checks on the recipient and the from address and uses spoofable parsing for VIP routes.
As a result, attacker-crafted calldata can pass verification while (a) pulling tokens from the owner/strategy and (b) sending swap proceeds to the attacker.
If integrated in production (as intended for future strategies), this becomes a straight path to direct theft of user/strategy funds.
Vulnerability Details
The verifier is meant to block unsafe 0x calldata before execution. However:
Recipient and
buyTokennot enforced in the top-level verifier:There is no assertion that
saa.recipient == owner(or whitelisted) and no binding thatsaa.buyTokenequals the expected asset.TRANSFER_FROMignores the true seller (from):Any calldata that pulls funds from any address passes, provided the token matches
targetToken. This defeats the point of verifying ownership of funds being spent.VIP route token parsing is spoofable:
For Uniswap V3 (and similar “VIP” paths), the verifier accepts the first 32 bytes of
fillsas the token-easy to forge so the verifier “sees” the expected token while the real route pays out elsewhere.
A typical integration would do:
require(ZeroXSwapVerifier.verifySwapCalldata(...));Call the 0x executor/settler with the same calldata (and the strategy has allowances set).
An attacker builds calldata that:
Sets
recipient = attacker(unchecked).Includes a
TRANSFER_FROMthat pulls the strategy’s tokens (uncheckedfrom).Uses VIP
fillsthat decode to the expected token in the verifier but route value to the attacker in execution.
The PoC in the next section tests and proves:
test_Verifier_Ignores_From_In_TransferFrom– Verifier passes a payload that pulls from a non-owner.test_Verifier_Allows_Attacker_Recipient_On_VIP– Verifier passes a VIP payload withrecipient = attackerand spoofedbuyToken.
This demonstrates the verification bypass decisively.
Even though there are currently no strategies utilizing ZeroXSwapVerifier, the team explicitly stated that it is in scope:
The intent is the ZeroXSwapVerifier is in scope as it is intended to be used in future strategies. We will validate reports that do not involve an impossible/OOS scenario and find logical errors in the contract. A PoC should be submitted using the contract entry points, demonstrating errors in the contracts flows.
This report demonstrates logical errors in the library via its public entry points (verification functions), with a PoC that shows malicious calldata is accepted.
Given the stated intent to integrate this verifier in future strategies, the flaw translates directly into critical, one-call drains once adopted, and should be treated as Critical severity now to avoid future incidents.
Impact Details
Direct asset loss. Given standard allowances, an attacker can get “verified” calldata executed that:
Pulls
tokenInfrom the owner/strategy (uncheckedfrom).Pays
tokenOutto the attacker (uncheckedrecipient/buyToken).
Any vault/strategy/module that trusts this verifier before forwarding calldata to a 0x executor is exposed. Loss scales with approved balances/limits.
References
Affected library:
src/utils/ZeroXSwapVerifier.sol_verifyExecuteCalldata– no recipient /buyTokenenforcement. https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/utils/ZeroXSwapVerifier.sol#L125-L130_verifyTransferFrom– ignoresfromaddress. https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/utils/ZeroXSwapVerifier.sol#L238-L246VIP helpers (e.g.,
_extractTokenFromUniswapFills) – spoofable decoding. https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/utils/ZeroXSwapVerifier.sol#L281-L287
Proof of Concept
Proof of Concept
Paste the following file in src/test/ZeroXSwapVerifier_PoC.t.sol and run with forge test --match-contract ZeroXSwapVerifier_PoC -vv:
Result:
Was this helpful?