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