58124 sc low direct theft of funds via malicious actions in execute call due to incorrect calldata verification

Submitted on Oct 30th 2025 at 19:34:39 UTC by @dldLambda for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58124

  • 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.verifySwapCalldata() verifies saa.actions from the SlippageAndActions struct, but the Settler executes the real actions[] parameter, which is not verified.

Vulnerability Details

The ZeroXSwapVerifier.verifySwapCalldata() function is intended to validate calldata from the 0x API before forwarding it to the 0x Settler contract for execution. However, it incorrectly decodes and verifies the execute() calldata by only checking the saa.actions field within the SlippageAndActions struct, while completely ignoring the actual actions[] parameter passed to the execute() function.

The real execute() function in the 0x Settler contract has the following signature:

function execute(
    SlippageAndActions calldata slippage,
    bytes[] calldata actions,
    bytes32 affiliateData
) external;

It executes the actions[] array, not slippage.actions.

Since verifySwapCalldata() only checks saa.actions and never inspects the real actions[], an attacker can craft calldata where:

saa.actions contains a benign action (e.g., BASIC_SELL_TO_POOL) The actual actions[] parameter contains a malicious action (e.g., transferFrom to the attacker's address)

The verification will pass, but the Settler will execute the malicious transferFrom, stealing all user funds from the calling contract.

The AllowedSlippage struct contains only 3 fields (Settler.sol):

However, ZeroXSwapVerifier defines a custom struct:

And decodes calldata as:

The library checks saa.actions, while the real actions[] parameter is never verified.

The Settler contract executes the actions[] parameter (Settler.sol):

The attacker can compose a special calldata with a good and acceptable saa.actions, but with a bad malicious actions[], which will ultimately be executed.

Impact Details

An attacker can steal funds by replacing actions[] with transferFrom, bypassing verifySwapCalldata() since a non-executable parameter is checked.

References

https://github.com/0xProject/0x-settler/blob/c3bb63e1ba3514dc694455610ad91b52c78fe24b/src/Settler.sol#L114 - execute function

https://github.com/0xProject/0x-settler/blob/master/src/interfaces/ISettlerBase.sol

https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/utils/ZeroXSwapVerifier.sol

Proof of Concept

Proof of Concept

Add this test and run:

You will see:

Was this helpful?