MultiSender Arbitrary From Theft
Exploit Transactions
0x346f65ac333eb6d69886f5614aaf569a561a53a8d93db4384bd7c0bec15ae9f6Victim Addresses
0xdb103fd28ca4b18115f5ce908baaeed7e0f1f101BSC0x003b724f9e1fa7350a7723bb8313acbdbe7188cbBSC0x390d9078cb06f3cd4a17a39693b489446724a093BSCLoss Breakdown
Similar Incidents
GymRouter Arbitrary Approved Token Spend
40%SellToken Arbitrary-Pair LP Drain
37%SwapX Arbitrary transferFrom Approval Drain on BNB Chain
36%FiberRouter Allowance Reuse Drain
35%FarmZAP Fee-Free Arbitrary-Farm Abuse
35%XDK Sell-Hook Reserve Theft on PancakePair
33%Root Cause Analysis
MultiSender Arbitrary From Theft
1. Incident Overview TL;DR
On BNB Chain block 29554344, transaction 0x346f65ac333eb6d69886f5614aaf569a561a53a8d93db4384bd7c0bec15ae9f6 exploited the public MultiSender helper at 0xdb103fd28ca4b18115f5ce908baaeed7e0f1f101. The caller satisfied MultiSender's allowance gate from its own address, set _from to an unrelated holder 0x003b724f9e1fa7350a7723bb8313acbdbe7188cb that had previously approved the helper, pulled 99999999999940000 raw MyAi units, sold the stolen MyAi through the public MyAi/WBNB Pancake pair 0x390d9078cb06f3cd4a17a39693b489446724a093, and ended with a net profit of 10.579374286685023016 BNB after gas.
The root cause is an authorization-subject mismatch in MultiSender's token path. The helper checks allowance(msg.sender, address(this)) but debits _from inside transferFrom, so any unprivileged actor can approve the helper from their own account and drain a third-party holder that has already approved MultiSender and still has enough balance. This is an ACT opportunity because the helper is public, the BNB fees are fixed, the vulnerable logic is on-chain and verified, and the liquidation path uses public PancakeSwap liquidity.
2. Key Background
MultiSender is a public batching helper that charges BNB fees and then either forwards token transfers or native-BNB transfers. On the relevant pre-state, validator RPC queries at block 29554343 confirmed:
- MultiSender
platformFees()=10000000000000000wei. - MultiSender
devFees()=2000000000000000wei. - MyAi token
0x40d1e011669c0dc7dc7c7fb93e623d6a661df5eehas9decimals. - The approved holder
0x003b724f9e1fa7350a7723bb8313acbdbe7188cbheld169984260220000000MyAi and had granted the same allowance to MultiSender. - The MyAi/WBNB Pancake pair held reserves
(3187138689840675 MyAi, 11119734748534322792 WBNB, 1688002569).
Those conditions matter because they are the full exploit prerequisites: a public helper, a third-party approval to that helper, and liquid public exit liquidity for the stolen token.
3. Vulnerability Analysis & Root Cause Summary
The vulnerability is a direct authorization bug in MultiSender's token-transfer helper, not a MEV-only pricing edge or an attacker-specific integration trick. MultiSender is supposed to move tokens on behalf of an approved sender, but the implementation validates the caller's allowance and then debits an arbitrary _from address. That means the subject being authorized and the subject being debited are different addresses.
Because the two addresses are independent inputs, an attacker can do three public actions in one transaction: approve MultiSender from the attacker's own address, point _from at any holder that has already approved MultiSender, and route the transferred tokens to an attacker-controlled recipient list. Once the tokens are in the attacker's possession, liquidation through PancakeSwap is ordinary permissionless trading. The invariant that should hold is simple: a helper must only debit the same address whose authorization it just validated. The code-level breakpoint is MultiSender's internal tokenTransfer function.
4. Detailed Root Cause Analysis
The verified MultiSender source on BscScan shows the vulnerable logic directly:
function tokenTransfer(
address _from,
address[] memory _address,
uint256[] memory _amounts,
address token,
uint256 totalAmount
) internal {
require(
IERC20(token).allowance(msg.sender, address(this)) >= totalAmount,
"allowance is not sufficient"
);
IERC20(token).transferFrom(_from, address(this), totalAmount);
uint256 tokenBalance = IERC20(token).balanceOf(address(this));
for (uint256 i = 0; i < _address.length; ++i) {
IERC20(token).transfer(
_address[i],
_amounts[i].mul(tokenBalance).div(totalAmount)
);
}
}
The bug is the mismatch between msg.sender and _from. If _from is not the caller, the function still debits _from as long as the caller has approved the helper and _from has also approved the helper at some earlier time.
The observed transaction uses exactly that path. The collected seed trace shows the attacker-controlled contract paying the helper fee, calling batchTokenTransfer, satisfying the allowance check from its own address, and then pulling MyAi from the unrelated approved holder:
MultiSender::batchTokenTransfer{value: 12000000000000000}(
0x003B724f9e1fa7350A7723BB8313ACBDbE7188CB,
[... attacker-controlled recipients ...],
[...],
0x40d1E011669c0dc7Dc7c7Fb93E623d6A661Df5Ee,
99999999999940000,
true
)
CoinToken::allowance(0x0D3aaFb9..., 0xDb103fd2...) -> max_uint
CoinToken::transferFrom(0x003B724f..., 0xDb103fd2..., 99999999999940000)
emit Approval(owner: 0x003B724f..., spender: 0xDb103fd2..., value: 69984260220060000)
That trace excerpt aligns with independent state queries around the incident. At block 29554343, the approved holder had both 169984260220000000 MyAi and 169984260220000000 allowance to MultiSender. At block 29554344, both values dropped to 69984260220060000. The drop exactly matches the unauthorized transferFrom path. No privileged signer, off-chain secret, or attacker-owned helper contract is needed to make that state transition happen.
After the unauthorized pull, the trace shows MultiSender forwarding the resulting MyAi balance to the attacker-controlled contract, which then liquidates the stolen MyAi into WBNB on PancakeSwap and unwraps the WBNB into native BNB. The balance-diff artifact records the profit consequence:
{
"address": "0xc47fcc9263b026033a94574ec432514c639a2d12",
"before_wei": "1843719942000000000",
"after_wei": "12423094228685023016",
"delta_wei": "10579374286685023016"
}
This establishes the full success predicate reported in root_cause.json: the exploit both violates the authorization invariant and realizes positive attacker profit in BNB terms.
5. Adversary Flow Analysis
The adversary flow is a single transaction with three deterministic stages:
- The attacker EOA
0xc47fcc9263b026033a94574ec432514c639a2d12sends tx0x346f65ac333eb6d69886f5614aaf569a561a53a8d93db4384bd7c0bec15ae9f6to attacker-controlled contract0x0d3aafb9ade835456b2595509ac1f58922e465b3with1.3BNB of value. That contract approves MultiSender and PancakeRouter from its own address. - The attacker-controlled contract calls
MultiSender.batchTokenTransfer(address,address[],uint256[],address,uint256,bool)with_from=0x003b724f9e1fa7350a7723bb8313acbdbe7188cb,token=MyAi,totalAmount=99999999999940000, andisToken=true. MultiSender accepts the caller-side approval, then debits the approved holder and forwards the resulting MyAi to attacker-controlled recipients. - The attacker-controlled contract repeatedly swaps the stolen MyAi through PancakeSwap, accumulates
10773405253685023016raw WBNB units, unwraps them, and pays out12061405253685023016wei to the attacker EOA. The attacker EOA retains10579374286685023016wei net after gas.
The observed exploit used a bespoke attacker contract to bundle those calls, but the strategy does not depend on that specific contract. Any unprivileged actor can reproduce the same sequence with a fresh EOA or locally deployed helper contract because the exploit uses only public approvals, public helper functions, and public AMM liquidity.
6. Impact & Losses
The incident caused both direct token theft from the approved holder and reserve depletion in the public exit market:
- Approved holder
0x003b724f9e1fa7350a7723bb8313acbdbe7188cblost99999999999940000raw MyAi units. - The MyAi/WBNB Pancake pair
0x390d9078cb06f3cd4a17a39693b489446724a093lost10773405253685023016raw WBNB units through the liquidation path. - The attacker EOA ended with a net native-balance increase of
10579374286685023016wei after paying182030967000000000wei in gas.
The loss summary in smallest on-chain units is therefore:
[
{
"token_symbol": "MyAi",
"amount": "99999999999940000",
"decimal": 9
},
{
"token_symbol": "WBNB",
"amount": "10773405253685023016",
"decimal": 18
}
]
7. References
- Seed exploit transaction:
0x346f65ac333eb6d69886f5614aaf569a561a53a8d93db4384bd7c0bec15ae9f6on BscScan. - Verified MultiSender contract source:
0xdb103fd28ca4b18115f5ce908baaeed7e0f1f101on BscScan. - Collected seed trace for the exploit transaction, showing
batchTokenTransfer,transferFrom, Pancake swaps, WBNB withdrawal, and payout. - Collected balance-diff artifact for the exploit transaction, showing holder depletion, pair WBNB depletion, and attacker EOA profit.
- Validator RPC checks at blocks
29554343and29554344for MultiSender fees, MyAi decimals, approved-holder balances and allowances, and Pancake pair reserves.