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 V3arrow-up-right

  • 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?