57169 sc low zeroxswapverifier policy bypass via rfq filldata prefix token amount spoof
Submitted on Oct 24th 2025 at 03:21:39 UTC by @edantes for Audit Comp | Alchemix V3
Report ID: #57169
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/utils/ZeroXSwapVerifier.sol
Impacts:
Protocol insolvency
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Smart contract unable to operate due to lack of token funds
Description
Brief/Intro
ZeroXSwapVerifier._verifyRFQVIP trusts a flat (address token, uint256 amount) prefix when extracting (sellToken, amount) from fillData. Because RFQ fillData is an arbitrary bytes blob, an attacker can prepend a fake (token, amount) pair that passes all verifier checks while the actual RFQ payload later in the blob sells a different token and amount. This enables policy bypass (e.g., whitelist, amount bounds) and can route swaps using unintended assets, risking inventory drain or mis-accounting when integrated into settlement flows.
Vulnerability Details
Root cause
Affected code path:
ZeroXSwapVerifier._verifyRFQVIP(bytes memory action, …) decodes (uint256 info, bytes fillData) and then calls _extractTokenAndAmountFromRFQ(fillData).
_extractTokenAndAmountFromRFQ does abi.decode(_slice(fillData, 0, 64), (address, uint256)), it assumes the first 64 bytes of fillData encode (address token, uint256 amount).
RFQ fillData is not guaranteed to be shaped with (token, amount) at offset 0. By prepending an attacker-chosen (token, amount) pair that meets policy, the verifier greenlights the action even if the real values consumed by execution are elsewhere in the blob.
Impact Details
If this library gates which swaps are permitted for protocol-owned funds or user funds under protocol control (e.g., via allowances/escrows), an attacker can:
Bypass token whitelist / policy by spoofing the prefix and executing with a different token.
Bypass amount constraints (none are enforced for RFQ VIP) and swap unexpected sizes.
Potentially drain or mis-route inventory, cause invariant breaks, and/or mis-account debt/credit if downstream components trust the verifier’s outcome.
Risk Breakdown
Critical: This PoC shows a parsing mismatch that lets a crafted RFQ action pass ZeroXSwapVerifier while the executor settles a different token/amount. If the protocol relies on this verifier before sending swaps with approvals/custodyed assets (as Alchemix states), an attacker can route a trade that moves the wrong token or drains value.
Recommendation
1. Decode RFQ fillData canonically: match 0x’s actual RFQ fill encoding instead of assuming a prefix. Respect dynamic offset pointers when decoding nested bytes.
2. Bind checks to what execution will actually use:
Verify sellToken and amount at the same offsets the executor/settler will consume.
Enforce amount bounds / slippage on RFQ VIP actions (today, maxSlippageBps is unused on this path).
3. Fail-closed on structure mismatch: validate fillData.length and internal offsets; reject if the expected struct layout is not satisfied.
4. Defense-in-depth:
Cross-check saa.buyToken with extracted sell/buy token semantics for RFQ.
Consider re-encoding normalized values and comparing against the original fillData (or verifying a signed quote hash) to prevent prefix/tail spoofing.
Proof of Concept
Proof of Concept
By spoofing a (token, amount) prefix in RFQ fillData, an attacker can pass the verifier while the actual execution uses a different token/amount, bypassing policy and risking inventory drain.
How to run:
File: src/test/PoC_ZeroXSwapVerifier_RfqPrefixSpoof.t.sol
Was this helpful?